Skip to content

Commit 8be0054

Browse files
author
Jimmy Briggs
committed
Git-Extras
1 parent 3388bc3 commit 8be0054

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1690
-0
lines changed

Git-Extras/git-active-branches

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
DEFAULT_SINCE='3.weeks.ago'
5+
since=$DEFAULT_SINCE
6+
7+
usage() {
8+
echo "usage: git active-branches [-s|-a <date>]" >&2
9+
echo >&2
10+
echo "Options:" >&2
11+
echo "-s show branches active since <date> (as in 'git log --since')" >&2
12+
echo "-a (an alias for '-s')" >&2
13+
echo "<date> any date format recognized by 'git log'" >&2
14+
echo >&2
15+
echo "Unless specified, <date> defaults to \"$DEFAULT_SINCE\". For examples see:" >&2
16+
echo "https://git-scm.com/book/en/v2/Git-Basics-Viewing-the-Commit-History" >&2
17+
}
18+
19+
while [ $# -gt 0 ]; do
20+
if ! getopts a:s:h flag; then usage; exit 2; fi
21+
case "$flag" in
22+
a|s) since=$OPTARG; shift ;;
23+
\?) usage; exit 2 ;; # argument missing its option
24+
h) usage; exit 2 ;;
25+
esac
26+
shift
27+
done
28+
29+
( git local-branches; git remote-branches ) | while read branch; do
30+
# '--no-patch' = suppress diff output (long form of '-s')
31+
maybebranch=$(
32+
git log -1 --since="$since" --no-patch "$branch"
33+
)
34+
[ -n "$maybebranch" ] && echo "$branch"
35+
done

Git-Extras/git-autofixup

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/sh
2+
set -e
3+
4+
make_relative () {
5+
root="$(git root)"
6+
while read f; do
7+
echo "$(realpath --relative-to=. "$root/$f")"
8+
done
9+
}
10+
11+
FILES="$(git diff --no-prefix --cached -U0 | awk '/^--- / { print $2 }' | grep -vF /dev/null | make_relative)"
12+
if [ -z "$FILES" ]; then
13+
echo "No staged files found that have previously been committed." >&2
14+
exit 2
15+
fi
16+
17+
SHA="$(git last-commit-to-file $FILES)"
18+
19+
if [ -n "$SHA" ]; then
20+
git commit --fixup "$SHA" --no-verify
21+
else
22+
echo "Could not determine which commit last touched the staged files." >&2
23+
exit 3
24+
fi

Git-Extras/git-branches-containing

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/sh
2+
3+
git branch -a --contains "$@"

Git-Extras/git-cherry-pick-to

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/bin/sh
2+
set -e
3+
4+
usage () {
5+
echo "usage: git cherry-pick-to <branch> ..." >&2
6+
}
7+
8+
if [ $# -eq 0 ]; then
9+
usage
10+
exit 2
11+
fi
12+
13+
branch="$1"
14+
shift 1
15+
16+
if git is-dirty -a; then
17+
echo 'Cannot use this command safely when you have local files marked "skipped".' >&2
18+
exit 2
19+
fi
20+
21+
# TODO: FIXME: There is a lot that can go wrong here. Basically, that's not
22+
# a problem at all in itself, but we need a mechanism to reliably recover back
23+
# to the starting position.
24+
git stash-everything
25+
commit=$(git sha HEAD)
26+
orig_branch=$(git current-branch)
27+
git checkout "$branch"
28+
git cherry-pick "$commit"
29+
git checkout "$orig_branch"
30+
git stash pop

Git-Extras/git-cleanup

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
#!/bin/sh
2+
set -e
3+
4+
usage () {
5+
echo "usage: git cleanup [-nh]" >&2
6+
echo >&2
7+
echo "Deletes all branches that have already been merged into the main branch." >&2
8+
echo "Removes those branches both locally and in the origin remote. Will be " >&2
9+
echo "most conservative with deletions." >&2
10+
echo >&2
11+
echo "Options:" >&2
12+
echo "-n Dry-run" >&2
13+
echo "-s Squashed" >&2
14+
echo "-l Local branches only, don't touch the remotes" >&2
15+
echo "-v Be verbose (show what's skipped)" >&2
16+
echo "-h Show this help" >&2
17+
}
18+
19+
dryrun=0
20+
remotes=1
21+
squashed=0
22+
verbose=0
23+
while getopts nlsvh flag; do
24+
case "$flag" in
25+
n) dryrun=1;;
26+
l) remotes=0;;
27+
s) squashed=1;;
28+
v) verbose=1;;
29+
h) usage; exit 2;;
30+
esac
31+
done
32+
shift $(($OPTIND - 1))
33+
34+
#
35+
# This will clean up any branch (both locally and remotely) that has been
36+
# merged into any of the known "trunks". Trunks are any of:
37+
#
38+
# - main (local) + origin/main
39+
# - master (local) + origin/master
40+
#
41+
42+
safegit () {
43+
if [ "$dryrun" -eq 1 ]; then
44+
echo git "$@"
45+
else
46+
git "$@"
47+
fi
48+
}
49+
50+
#
51+
# The Algorithm[tm]:
52+
# - Find the smallest set of common ancestors for those trunks. (There can
53+
# actually be multiple, although unlikely.)
54+
# - For each local branch, check if any of the common ancestors contains it,
55+
# but not vice-versa (prevents newly-created branches from being deleted)
56+
# - Idem for each remote branch
57+
#
58+
59+
find_common_base () {
60+
if [ $# -eq 1 ]; then
61+
git sha "$1"
62+
else
63+
git merge-base "$1" "$2"
64+
fi
65+
}
66+
67+
find_branch_base () {
68+
branch="$1"
69+
base_point=""
70+
71+
if git local-branch-exists "$branch"; then
72+
base_point=$(find_common_base "$branch" $base_point)
73+
fi
74+
75+
if git remote-branch-exists origin "$branch"; then
76+
base_point=$(find_common_base "origin/$branch" $base_point)
77+
fi
78+
79+
if [ -n "$base_point" ]; then
80+
echo "$base_point"
81+
fi
82+
}
83+
84+
main="$(git main-branch)"
85+
86+
find_bases () {
87+
find_branch_base "$main"
88+
}
89+
90+
bases=$(find_bases)
91+
92+
#
93+
# The Clean Squashed Algorithm[tm]
94+
# - Create a temporary dangling squashed commit with git commit-tree
95+
# - Then use git cherry to check if the squashed commit has already been
96+
# applied to the main branch
97+
# - If it has, then delete the branch
98+
#
99+
100+
clean_squashed () {
101+
branch="$1"
102+
103+
# Find the merge base for this branch
104+
merge_base=$(git merge-base "$main" "$branch")
105+
106+
# Get the tree object of the branch
107+
branch_tree="$(git rev-parse "$branch^{tree}")"
108+
109+
# Create a squashed commit object of the branch tree with parent
110+
# of $base with a commit message of "_"
111+
dangling_squashed_commit="$(git commit-tree "$branch_tree" -p "$merge_base" -m _)"
112+
113+
# Show a summary of what has yet to be applied
114+
cherry_commit="$(git cherry "$main" "$dangling_squashed_commit")"
115+
116+
if [ "$cherry_commit" = "- $dangling_squashed_commit" ]; then
117+
# If "- <commit-sha>", (ex. - "- 851cb44727") this means the
118+
# commit is in main and can be dropped if you rebased
119+
# against main
120+
safegit branch -D "$branch"
121+
elif [ $verbose -eq 1 ]; then
122+
# If "+ <commit-sha>", (ex. - "+ 851cb44727") this means the
123+
# commit still needs to be kept so that it will be applied to
124+
# main
125+
echo "Skipped $branch (no similar squash found)"
126+
fi
127+
}
128+
129+
for branch in $(git local-branches \
130+
| grep -vxF "$main"); do
131+
for base in $bases; do
132+
if git contains "$base" "$branch"; then
133+
if ! git contains "$branch" "$base"; then
134+
# Actually delete
135+
if ! safegit branch -D "$branch"; then
136+
echo "Errors deleting local branch $branch" >&2
137+
fi
138+
break
139+
fi
140+
else
141+
# This is the case where the branches are in fact legit
142+
# local WIP branches or they are squashed merges, and we
143+
# need to check if they have been squashed-merged into
144+
# main. NOTE - this assumes main is up-to-date locally
145+
if [ "$squashed" -eq 1 ]; then
146+
clean_squashed "$branch"
147+
fi
148+
fi
149+
done
150+
done
151+
152+
# Pruning first will remove any remote tracking branches that don't exist in
153+
# the remote anymore anyway.
154+
155+
#XXX: FIXME: This gave trouble, as it tried to remove branches from Heroku remotes... :(
156+
#for remote in $(git remote); do
157+
for remote in origin; do
158+
safegit remote prune "$remote" >/dev/null 2>/dev/null
159+
160+
if [ $remotes -eq 1 ]; then
161+
branches_to_remove=""
162+
for branch in $(git remote-branches "$remote" | grep -vEe "/($main)\$"); do
163+
for base in $bases; do
164+
if git contains "$base" "$branch"; then
165+
if ! git contains "$branch" "$base"; then
166+
branchname=$(echo "$branch" | cut -d/ -f2-)
167+
branches_to_remove="$branches_to_remove $branchname"
168+
break
169+
fi
170+
fi
171+
done
172+
done
173+
174+
if [ -n "$branches_to_remove" ]; then
175+
if ! safegit push "$remote" --delete $branches_to_remove; then
176+
echo "Errors deleting branches $branches_to_remove from remote '$remote'" >&2
177+
fi
178+
fi
179+
fi
180+
done
181+
182+
# Delete any remaining local remote-tracking branches of remotes that are gone
183+
# This is an atypical situation that has occurred to me personally after having
184+
# used the command:
185+
#
186+
# $ hub merge <some-github-url>
187+
#
188+
branches_to_remove=""
189+
for branch in $(git remote-branches); do
190+
for base in $bases; do
191+
if git contains "$base" "$branch"; then
192+
if ! git contains "$branch" "$base"; then
193+
branches_to_remove="$branches_to_remove $branch"
194+
break
195+
fi
196+
fi
197+
done
198+
done
199+
200+
if [ -n "$branches_to_remove" ]; then
201+
safegit branch -dr $branches_to_remove
202+
fi

Git-Extras/git-commit-to

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
usage () {
5+
echo "usage: git commit-to <branch> ..." >&2
6+
}
7+
8+
if [ $# -eq 0 ]; then
9+
usage
10+
exit 2
11+
fi
12+
13+
branch="$1"
14+
shift 1
15+
16+
if git is-dirty -a; then
17+
echo 'Cannot use this command safely when you have local files marked "skipped".' >&2
18+
exit 2
19+
fi
20+
21+
# TODO: FIXME: There is a lot that can go wrong here. Basically, that's not
22+
# a problem at all in itself, but we need a mechanism to reliably recover back
23+
# to the starting position.
24+
git commit "$@"
25+
git cherry-pick-to "$branch"
26+
git reset --hard HEAD~1

0 commit comments

Comments
 (0)