git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v3] contrib git-resurrect: find traces of a branch name and resurrect it

From
Thomas Rast <trast@student.ethz.ch>
Date
Feb 1, 2009, 21:34 UTC
Message-ID
<1233524085-25342-1-git-send-email-trast@student.ethz.ch>
In-Reply-To
<7vwschz2dc.fsf@gitster.siamese.dyndns.org>

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 <trast@student.ethz.ch>
---
Junio C Hamano wrote:
> I hate to paint bikeshed, but -H "try-hard" looks somewhat unusual doesn't
> it?  It sounds more like --all (find from all possible sources).

Why not. I had '-h' but then found out the hard way that it's reserved...

Show 8 quoted lines
> > +search_reflog_merges () {
> > +        sed -n 's~^[^ ]* \([^ ]*\) .*\tmerge '"$1"':~\1~p' \
> > +                < .git/logs/HEAD
> > +}
> 
> The two commits both point at the HEAD that merges the other branch into,
> so this finds a merge commit that has the tip of target branch as its
> second parent.  Is that really what you want?

Good point. Furthermore the sed expression was broken, it would not remove the remainder of the line. Sadly it's not possible to insert the reflog message and sha1 via --pretty=format, so I now use rev-parse.

Show 13 quoted lines
> Reading everything down to the root commit sounds like fun.  rev-list
> gives you the output from newer to older so you may want to break out once
> you have found enough candidates.
> 
> Anyway, if I were doing this script, I'd write this part like this without
> a shell loop:
> 
>         _x40="[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]"
>         _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
> 
> 	git rev-list --all --grep="Merge branch '$1'" \
>         	--pretty=tformat:"%H %P %s" |
> 	sed -ne "s/^$_x40 $_x40 \($_x40\) Merge .*/\1/p"

Nice trick. The same also works for scan_merge_targets() and gives it a nice speed boost too. Unfortunately my sed-fu is not good enough to figure out how to only print the first line (for resurrections from pu, we expect there to be a single match). All uses of 'q' I could come up with resulted in an early exit independent of the substitutions. Appending '| head -n 1' does not seem to make any difference either.

I also added the relative committer time to the candidate list, and made it sort according to time; it seems somewhat more readable now.

 contrib/git-resurrect.sh |  172 ++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 172 insertions(+), 0 deletions(-)
 create mode 100755 contrib/git-resurrect.sh
diff --git a/contrib/git-resurrect.sh b/contrib/git-resurrect.sh
new file mode 100755
index 0000000..3a040ae
--- /dev/null
+++ b/contrib/git-resurrect.sh
@@ -0,0 +1,172 @@
+#!/bin/sh
+
+USAGE="[-a] [-r] [-m] [-t] [-n] [-b <newname>] <name>"
+LONG_USAGE="git-resurrect attempts to find traces of a branch tip
+called <name>, and tries to resurrect it.  Currently, the reflog is
+searched for checkout messages, and with -r also merge messages.  With
+-m and -t, the history of all refs is scanned for Merge <name> into
+other/Merge <other> into <name> (respectively) commit subjects, which
+is rather slow but allows you to resurrect other people's topic
+branches."
+
+OPTIONS_SPEC="\
+git resurrect $USAGE
+--
+b,branch=            save branch as <newname> instead of <name>
+a,all                same as -l -r -m -t
+l,reflog             scan reflog for checkouts (enabled by default)
+r,reflog-merges      scan for merges recorded in reflog
+m,merges             scan for merges into other branches (slow)
+t,merge-targets      scan for merges of other branches into <name>
+n,dry-run            don't recreate the branch"
+
+. git-sh-setup
+
+search_reflog () {
+        sed -ne 's~^\([^ ]*\) .*\tcheckout: moving from '"$1"' .*~\1~p' \
+                < "$GIT_DIR"/logs/HEAD
+}
+
+search_reflog_merges () {
+	git rev-parse $(
+		sed -ne 's~^[^ ]* \([^ ]*\) .*\tmerge '"$1"':.*~\1^2~p' \
+			< "$GIT_DIR"/logs/HEAD
+	)
+}
+
+_x40="[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]"
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+
+search_merges () {
+        git rev-list --all --grep="Merge branch '$1'" \
+                --pretty=tformat:"%P %s" |
+        sed -ne "s/^$_x40 \($_x40\) Merge .*/\1/p"
+}
+
+search_merge_targets () {
+	git rev-list --all --grep="Merge branch '[^']*' into $branch\$" \
+		--pretty=tformat:"%H %s" --all |
+	sed -ne "s/^\($_x40\) Merge .*/\1/p"
+}
+
+dry_run=
+scan_reflog=t
+scan_reflog_merges=
+scan_merges=
+scan_merge_targets=
+new_name=
+
+while test "$#" != 0; do
+	case "$1" in
+	    -b|--branch)
+		shift
+		new_name="$1"
+		;;
+	    -n|--dry-run)
+		dry_run=t
+		;;
+	    --no-dry-run)
+		dry_run=
+		;;
+	    -m|--merges)
+		scan_merges=t
+		;;
+	    --no-merges)
+		scan_merges=
+		;;
+	    -l|--reflog)
+		scan_reflog=t
+		;;
+	    --no-reflog)
+		scan_reflog=
+		;;
+	    -r|--reflog_merges)
+		scan_reflog_merges=t
+		;;
+	    --no-reflog_merges)
+		scan_reflog_merges=
+		;;
+	    -t|--merge-targets)
+		scan_merge_targets=t
+		;;
+	    --no-merge-targets)
+		scan_merge_targets=
+		;;
+	    -a|--all)
+		scan_reflog=t
+		scan_reflog_merges=t
+		scan_merges=t
+		scan_merge_targets=t
+		;;
+	    --)
+		shift
+		break
+		;;
+	    *)
+		usage
+		;;
+	esac
+	shift
+done
+
+test "$#" = 1 || usage
+
+all_strategies="$scan_reflog$scan_reflog_merges$scan_merges$scan_merge_targets"
+if test -z "$all_strategies"; then
+	die "must enable at least one of -lrmt"
+fi
+
+branch="$1"
+test -z "$new_name" && new_name="$branch"
+
+if test ! -z "$scan_reflog"; then
+	if test -r "$GIT_DIR"/logs/HEAD; then
+		candidates="$(search_reflog $branch)"
+	else
+		die 'reflog scanning requested, but' \
+			'$GIT_DIR/logs/HEAD not readable'
+	fi
+fi
+if test ! -z "$scan_reflog_merges"; then
+	if test -r "$GIT_DIR"/logs/HEAD; then
+		candidates="$candidates $(search_reflog_merges $branch)"
+	else
+		die 'reflog scanning requested, but' \
+			'$GIT_DIR/logs/HEAD not readable'
+	fi
+fi
+if test ! -z "$scan_merges"; then
+	candidates="$candidates $(search_merges $branch)"
+fi
+if test ! -z "$scan_merge_targets"; then
+	candidates="$candidates $(search_merge_targets $branch)"
+fi
+
+candidates="$(git rev-parse $candidates | sort -u)"
+
+if test -z "$candidates"; then
+	hint=
+	test "z$all_strategies" != "ztttt" \
+		&& hint=" (maybe try again with -a)"
+	die "no candidates for $branch found$hint"
+fi
+
+echo "** Candidates for $branch **"
+for cmt in $candidates; do
+	git --no-pager log --pretty=tformat:"%ct:%h [%cr] %s" --abbrev-commit -1 $cmt
+done \
+| sort -n | cut -d: -f2-
+
+newest="$(git rev-list -1 $candidates)"
+if test ! -z "$dry_run"; then
+	printf "** Most recent: "
+	git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+elif ! git rev-parse --verify --quiet $new_name >/dev/null; then
+	printf "** Restoring $new_name to "
+	git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+	git branch $new_name $newest
+else
+	printf "Most recent: "
+	git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+	echo "** $new_name already exists, doing nothing"
+fi
-- 
1.6.1.2.495.gb8db2
Previous: Thomas RastNext: Junio C Hamano
Message 43 of 46 in “checkout: implement "-" shortcut name for last branch”
  1. checkout: implement "-" shortcut name for last branchThomas Rast, Jan 15, 2009
  2. checkout: implement "-" shortcut name for last branchThomas Rast, Jan 15, 2009
  3. Johannes SixtJan 15, 2009
  4. Johannes SchindelinJan 15, 2009
  5. Thomas RastJan 15, 2009
  6. Johannes SchindelinJan 15, 2009
  7. Johannes SchindelinJan 15, 2009
  8. Junio C HamanoJan 15, 2009
  9. Johannes SchindelinJan 15, 2009
  10. revision walker: include a detached HEAD in --allJohannes Schindelin, Jan 16, 2009
  11. Santi BéjarJan 16, 2009
  12. Johannes SchindelinJan 16, 2009
  13. David KastrupJan 16, 2009
  14. Santi BéjarJan 16, 2009
  15. Santi BéjarJan 16, 2009
  16. Junio C HamanoJan 18, 2009
  17. Junio C HamanoJan 18, 2009
  18. Johannes SchindelinJan 18, 2009
  19. Johannes SchindelinJan 18, 2009
  20. Johan HerlandJan 15, 2009
  21. Johannes SchindelinJan 15, 2009
  22. Junio C HamanoJan 15, 2009
  23. Junio C HamanoJan 15, 2009
  24. Johannes SchindelinJan 16, 2009
  25. Johannes SchindelinJan 15, 2009
  26. Thomas RastJan 15, 2009
  27. Johannes SchindelinJan 15, 2009
  28. Thomas RastJan 15, 2009
  29. Johannes SchindelinJan 15, 2009
  30. Thomas RastJan 16, 2009
  31. Johannes SchindelinJan 16, 2009
  32. git-resurrect: find traces of a branch name and resurrect itThomas Rast, Jan 18, 2009
  33. Johannes SchindelinJan 18, 2009
  34. Thomas RastJan 20, 2009
  35. Boyd Stephen Smith Jr.Jan 20, 2009
  36. Boyd Stephen Smith Jr.Jan 20, 2009
  37. contrib git-resurrect: find traces of a branch name and resurrect itThomas Rast, Jan 23, 2009
  38. Boyd Stephen Smith Jr.Jan 23, 2009
  39. Thomas RastJan 26, 2009
  40. contrib git-resurrect: find traces of a branch name and resurrect itThomas Rast, Jan 26, 2009
  41. Junio C HamanoJan 27, 2009
  42. Thomas RastJan 30, 2009
  43. contrib git-resurrect: find traces of a branch name and resurrect itThomas Rast, Feb 1, 2009
  44. Junio C HamanoFeb 2, 2009
  45. contrib git-resurrect: find traces of a branch name and resurrect itThomas Rast, Feb 4, 2009
  46. Junio C HamanoFeb 5, 2009

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.