Skip to content

Commit e1ff064

Browse files
trastgitster
authored andcommitted
contrib git-resurrect: find traces of a branch name and resurrect it
Add a tool 'git-resurrect.sh <branch>' that tries to find traces of the <branch> in the HEAD reflog and, optionally, all merge commits in the repository. It can then resurrect the branch, pointing it at the most recent of all candidate commits found. Signed-off-by: Thomas Rast <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent de81390 commit e1ff064

File tree

1 file changed

+180
-0
lines changed

1 file changed

+180
-0
lines changed

contrib/git-resurrect.sh

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#!/bin/sh
2+
3+
USAGE="[-a] [-r] [-m] [-t] [-n] [-b <newname>] <name>"
4+
LONG_USAGE="git-resurrect attempts to find traces of a branch tip
5+
called <name>, and tries to resurrect it. Currently, the reflog is
6+
searched for checkout messages, and with -r also merge messages. With
7+
-m and -t, the history of all refs is scanned for Merge <name> into
8+
other/Merge <other> into <name> (respectively) commit subjects, which
9+
is rather slow but allows you to resurrect other people's topic
10+
branches."
11+
12+
OPTIONS_SPEC="\
13+
git resurrect $USAGE
14+
--
15+
b,branch= save branch as <newname> instead of <name>
16+
a,all same as -l -r -m -t
17+
k,keep-going full rev-list scan (instead of first match)
18+
l,reflog scan reflog for checkouts (enabled by default)
19+
r,reflog-merges scan for merges recorded in reflog
20+
m,merges scan for merges into other branches (slow)
21+
t,merge-targets scan for merges of other branches into <name>
22+
n,dry-run don't recreate the branch"
23+
24+
. git-sh-setup
25+
26+
search_reflog () {
27+
sed -ne 's~^\([^ ]*\) .*\tcheckout: moving from '"$1"' .*~\1~p' \
28+
< "$GIT_DIR"/logs/HEAD
29+
}
30+
31+
search_reflog_merges () {
32+
git rev-parse $(
33+
sed -ne 's~^[^ ]* \([^ ]*\) .*\tmerge '"$1"':.*~\1^2~p' \
34+
< "$GIT_DIR"/logs/HEAD
35+
)
36+
}
37+
38+
_x40="[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]"
39+
_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
40+
41+
search_merges () {
42+
git rev-list --all --grep="Merge branch '$1'" \
43+
--pretty=tformat:"%P %s" |
44+
sed -ne "/^$_x40 \($_x40\) Merge .*/ {s//\1/p;$early_exit}"
45+
}
46+
47+
search_merge_targets () {
48+
git rev-list --all --grep="Merge branch '[^']*' into $branch\$" \
49+
--pretty=tformat:"%H %s" --all |
50+
sed -ne "/^\($_x40\) Merge .*/ {s//\1/p;$early_exit} "
51+
}
52+
53+
dry_run=
54+
early_exit=q
55+
scan_reflog=t
56+
scan_reflog_merges=
57+
scan_merges=
58+
scan_merge_targets=
59+
new_name=
60+
61+
while test "$#" != 0; do
62+
case "$1" in
63+
-b|--branch)
64+
shift
65+
new_name="$1"
66+
;;
67+
-n|--dry-run)
68+
dry_run=t
69+
;;
70+
--no-dry-run)
71+
dry_run=
72+
;;
73+
-k|--keep-going)
74+
early_exit=
75+
;;
76+
--no-keep-going)
77+
early_exit=q
78+
;;
79+
-m|--merges)
80+
scan_merges=t
81+
;;
82+
--no-merges)
83+
scan_merges=
84+
;;
85+
-l|--reflog)
86+
scan_reflog=t
87+
;;
88+
--no-reflog)
89+
scan_reflog=
90+
;;
91+
-r|--reflog_merges)
92+
scan_reflog_merges=t
93+
;;
94+
--no-reflog_merges)
95+
scan_reflog_merges=
96+
;;
97+
-t|--merge-targets)
98+
scan_merge_targets=t
99+
;;
100+
--no-merge-targets)
101+
scan_merge_targets=
102+
;;
103+
-a|--all)
104+
scan_reflog=t
105+
scan_reflog_merges=t
106+
scan_merges=t
107+
scan_merge_targets=t
108+
;;
109+
--)
110+
shift
111+
break
112+
;;
113+
*)
114+
usage
115+
;;
116+
esac
117+
shift
118+
done
119+
120+
test "$#" = 1 || usage
121+
122+
all_strategies="$scan_reflog$scan_reflog_merges$scan_merges$scan_merge_targets"
123+
if test -z "$all_strategies"; then
124+
die "must enable at least one of -lrmt"
125+
fi
126+
127+
branch="$1"
128+
test -z "$new_name" && new_name="$branch"
129+
130+
if test ! -z "$scan_reflog"; then
131+
if test -r "$GIT_DIR"/logs/HEAD; then
132+
candidates="$(search_reflog $branch)"
133+
else
134+
die 'reflog scanning requested, but' \
135+
'$GIT_DIR/logs/HEAD not readable'
136+
fi
137+
fi
138+
if test ! -z "$scan_reflog_merges"; then
139+
if test -r "$GIT_DIR"/logs/HEAD; then
140+
candidates="$candidates $(search_reflog_merges $branch)"
141+
else
142+
die 'reflog scanning requested, but' \
143+
'$GIT_DIR/logs/HEAD not readable'
144+
fi
145+
fi
146+
if test ! -z "$scan_merges"; then
147+
candidates="$candidates $(search_merges $branch)"
148+
fi
149+
if test ! -z "$scan_merge_targets"; then
150+
candidates="$candidates $(search_merge_targets $branch)"
151+
fi
152+
153+
candidates="$(git rev-parse $candidates | sort -u)"
154+
155+
if test -z "$candidates"; then
156+
hint=
157+
test "z$all_strategies" != "ztttt" \
158+
&& hint=" (maybe try again with -a)"
159+
die "no candidates for $branch found$hint"
160+
fi
161+
162+
echo "** Candidates for $branch **"
163+
for cmt in $candidates; do
164+
git --no-pager log --pretty=tformat:"%ct:%h [%cr] %s" --abbrev-commit -1 $cmt
165+
done \
166+
| sort -n | cut -d: -f2-
167+
168+
newest="$(git rev-list -1 $candidates)"
169+
if test ! -z "$dry_run"; then
170+
printf "** Most recent: "
171+
git --no-pager log -1 --pretty=tformat:"%h %s" $newest
172+
elif ! git rev-parse --verify --quiet $new_name >/dev/null; then
173+
printf "** Restoring $new_name to "
174+
git --no-pager log -1 --pretty=tformat:"%h %s" $newest
175+
git branch $new_name $newest
176+
else
177+
printf "Most recent: "
178+
git --no-pager log -1 --pretty=tformat:"%h %s" $newest
179+
echo "** $new_name already exists, doing nothing"
180+
fi

0 commit comments

Comments
 (0)