-
Notifications
You must be signed in to change notification settings - Fork 4
/
git-rm-merged-branches
executable file
·167 lines (134 loc) · 3.48 KB
/
git-rm-merged-branches
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
#!/bin/bash
set -euo pipefail
run() {
echo >&2 "+ $*"
"$@"
}
# usage: prompt_yn [PROMPT]
# Prompt the user for a yes/no response.
#
# Exit codes:
# 0: user entered yes
# 2: STDIN is not a TTY
# 10: user entered no
#
prompt_yn() {
local prompt ans
if [ $# -ge 1 ]; then
prompt="$1"
else
prompt="Continue?"
fi
if [ ! -t 0 ]; then
echo >&2 "$prompt [y/n]"
echo >&2 "prompt_yn: error: stdin is not a TTY!"
return 2
fi
while true; do
read -r -p "$prompt [y/n] " ans
case "$ans" in
Y|y|yes|YES|Yes)
return
;;
N|n|no|NO|No)
return 10
;;
esac
done
}
# Determine the main/master branch and echo its name.
git-main-branch() {
local ret
git rev-parse --verify --quiet main >/dev/null && ret=$? || ret=$?
case "$ret" in
0)
echo main
return
;;
1)
# pass
;;
*)
# probably not a git repo / some other error
return "$ret"
;;
esac
if git rev-parse --verify --quiet master >/dev/null; then
echo master
else
echo >&2 "Neither master nor main exists"
return 1
fi
}
git-is-branch-cherry-equivalent() {
local main branch
main="$1"
branch="$2"
local merge_base
merge_base="$(git merge-base "$main" "$branch")"
# get the tree content as of the branch
local tree
tree="$(git rev-parse "$branch^{tree}")"
# make a commit that's a single squashed commit of the tree, with
# the main/master merge base as the parent
local tmp_commit
tmp_commit="$(git commit-tree "$tree" -p "$merge_base" -m "tmp from $0")"
# TODO: is there any way to gc this tmp commit when we're done?
# determine whether cherry-pick would do anything
if [[ $(git cherry "$main" "$tmp_commit") == "-"* ]]; then
# - prefix from git cherry means cherry-pick would be a no-op
return 0
fi
return 1
}
# usage: is-branch-excluded BRANCH CURRENT_BRANCH
is-branch-excluded() {
local branch current_branch
branch="$1"
current_branch="$2"
# branches excluded from pruning
case "$branch" in
master|main) return 0 ;;
stages/*) return 0 ;;
env/*) return 0 ;;
"$current_branch") return 0 ;;
esac
return 1
}
main() {
local branch current_branch
local nothing_to_do
current_branch="$(git branch --show-current)"
nothing_to_do=1
# prune merged branches
for branch in $(git branch --format='%(refname:short)' --merged); do
if is-branch-excluded "$branch" "$current_branch"; then
continue
fi
nothing_to_do=
if prompt_yn "Delete '$branch'?"; then
run git branch -d "$branch"
fi
done
local main
main="$(git-main-branch)"
# prune squashed branches
for branch in $(git for-each-ref refs/heads/ "--format=%(refname:short)")
do
if is-branch-excluded "$branch" "$current_branch"; then
continue
fi
if git-is-branch-cherry-equivalent "$main" "$branch"; then
nothing_to_do=
if prompt_yn "Delete squashed '$branch'?"; then
run git branch -D "$branch"
fi
fi
done
if [ -n "$nothing_to_do" ]; then
echo "Nothing to do"
else
echo "Done"
fi
}
main