-
Notifications
You must be signed in to change notification settings - Fork 0
/
git_mass.sh
executable file
·218 lines (185 loc) · 6.5 KB
/
git_mass.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#!/usr/bin/env bash
#
# Author: Stanislav Zhuk <[email protected]>
#
#{{{ bash settings
set -o errexit
set -o nounset
set -o pipefail
#}}}
# shellcheck disable=SC1090
source "$(dirname "$(realpath "${BASH_SOURCE[0]}")")/output_helpers.sh"
# shellcheck disable=SC2034
script_name="Git Mass"
# shellcheck disable=SC2034
script_version="1.1.2"
function usage() {
info "Usage: git-mass [arg]
Arguments:
checkout (switch branches or restore working tree files)
gc (cleanup unnecessary files and optimize the local repository)
gone (interactive delete for local branches that were deleted from a remote)
pull (fetch from and integrate with another repository or a local branch)
sync (hub-sync: fetch git objects from upstream and update local branches)
git ... (any other git command to run in sub-folders)"
}
function checkout() { (
[[ "${PWD}" != "${1}" ]] && cd "${1}"
# switch from detached HEAD to the branch
if ! git symbolic-ref -q HEAD >/dev/null 2>&1; then
latest_branch="$(git for-each-ref --sort=-committerdate --format='%(refname:short)' --count=1 refs/heads/ 2>/dev/null || true)"
if [[ "${latest_branch}" != "" ]]; then
info "\nCheckout '${1}'...\n"
git checkout "${latest_branch}"
fi
fi
); }
function run() {
local run_custom_command="${run_custom_command:-}"
local interactive="${interactive:-}"
local repos=() submodules=()
mapfile -t repos < <(find . -mindepth 1 -maxdepth 2 -name '*.git' -printf "${PWD}/%P\n" | sed 's/\/.git//' | sort)
if [[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" == "true" ]]; then
mapfile -t submodules < <(git submodule status 2>/dev/null | awk '{print $2}' | xargs --no-run-if-empty realpath)
fi
# Filter out submodules from repos
local filtered_repos=() repo
for repo in "${repos[@]}"; do
local is_submodule=false submodule
for submodule in "${submodules[@]}"; do
if [[ "${repo}" == "${submodule}" ]]; then
is_submodule=true
break
fi
done
if ! ${is_submodule}; then
filtered_repos+=("${repo}")
fi
done
# Replace repos with the filtered list
repos=("${filtered_repos[@]}")
local repos_count
repos_count=${#repos[@]}
if [[ "${repos_count}" == 0 ]]; then
warning "Git repo(s) in '${PWD}' not found."
fi
for repo in "${repos[@]}"; do
checkout "${repo}"
done
if [[ "${interactive}" == "yes" ]]; then
# run each repo sequentially
for repo in "${repos[@]}"; do
(cd "${repo}" && "${@}")
done
elif [[ "${run_custom_command}" == "yes" ]]; then
# run each repo sequentially
for repo in "${repos[@]}"; do
info "\nRunning '${*}' in '${repo}'..."
if ! (cd "${repo}" && "${@}"); then
warning "Unable to '${*}' for '${repo}'."
fi
done
else
# run all repos at once
printf "%s\n" "${repos[@]}" | xargs -P"${repos_count}" -I{} "${@}"
fi
success "'${PWD}' done ${repos_count} repo(s)."
}
function git-mass-checkout() {
git -C "${1}" checkout .
}
function git-mass-gc() {
git -C "${1}" gc --aggressive
}
function git-mass-pull() {
local result
if result="$(git -C "${1}" pull --ff-only --progress --all 2>&1)"; then
if [[ "$(echo "${result}" | grep -v -e "^Already up to date.$" -e "^Fetching")" != "" ]]; then
info "Pull '${1}'"
printf "\n%s\n\n" "${result}"
# sync all branches in the repo
if command -v hub >/dev/null 2>&1; then
(git-mass-sync "${1}")
fi
fi
local submodule_update
submodule_update="$(git -C "${1}" submodule update --init --recursive 2>&1)"
if [[ "${submodule_update}" != "" ]]; then
info "Submodules update '${1}'"
printf "\n%s\n\n" "${submodule_update}"
fi
else
warning "Unable to pull '${1}'"
printf "\n%s\n\n" "${result}"
# try to sync instead
if command -v hub >/dev/null 2>&1; then
(git-mass-sync "${1}")
fi
fi
}
function git-mass-sync() {
local result
if result="$(hub -C "${1}" sync --color 2>&1)"; then
if [[ "${result}" != "" ]]; then
info "Sync '${1}'"
printf "\n%s\n\n" "${result}"
fi
else
warning "Unable to sync '${1}'"
printf "\n%s\n\n" "${result}"
fi
}
# remove gone branches https://stackoverflow.com/a/33548037
function git-mass-interactive-gone() {
local gone_branches=() gone_branch
mapfile -t gone_branches < <(git for-each-ref --format '%(refname) %(upstream:track)' refs/heads | awk '$2 == "[gone]" {sub("refs/heads/", "", $1); print $1}' 2>/dev/null)
for gone_branch in "${gone_branches[@]}"; do
info "Found gone branch '${gone_branch}' in '$(pwd)' with commit:"
echo
git log -1 "${gone_branch}" | cat
if confirm "Delete gone branch '${gone_branch}' in '$(pwd)'?" no; then
if result=$(git branch -D "${gone_branch}" 2>&1); then
if [[ "${result:-}" != "" ]]; then
info "Delete gone branches in '$(pwd)'"
printf "\n%s\n\n" "${result}"
fi
else
warning "Unable to delete gone branches in '$(pwd)'"
printf "\n%s\n\n" "${result}"
fi
else
info "Skipped deleting gone branch '${gone_branch}'"
echo
fi
done
}
for tool in git awk xargs; do
if ! command -v "${tool}" >/dev/null 2>&1; then
failure "Install '${tool}' to run this script."
fi
done
if [[ $(type -t "git-mass-interactive-${1-}") == function ]]; then
export -f git-mass-interactive-"${1}"
interactive=yes run "git-mass-interactive-${1}"
elif [[ $(type -t "git-mass-${1-}") == function ]]; then
if [[ "${1}" == "pull" ]]; then
if command -v hub >/dev/null 2>&1; then
export -f git-mass-sync
else
warning "Install 'hub' to use hub-sync."
fi
fi
if [[ "${1}" == "sync" ]] && ! command -v hub >/dev/null 2>&1; then
failure "Install 'hub' to use hub-sync."
fi
if [[ "${1}" == "checkout" ]] && ! confirm "Run checkout?" no; then
exit 1
fi
export -f git-mass-"${1}"
run bash -c ''"git-mass-${1}"' "${@}"' _ {}
elif [[ "${#}" -gt 0 ]]; then
run_custom_command=yes run "${@}"
else
script_intro
usage
fi