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

3 messages, 2 authors, 2016-06-15 · open the first message on its own page

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

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:46:02

Thomas Rast [off-list ref] writes:
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 <redacted>
---

Fixed the -h to upper-case in the short options summaries, and removed
a stray 'q' in the default assignment of new_name.
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).
+. git-sh-setup
+cd_to_toplevel
Why?
+search_reflog () {
+        sed -n 's~^\([^ ]*\) .*\tcheckout: moving from '"$1"' .*~\1~p' \
+                < .git/logs/HEAD
+}
Once you used ". git-sh-setup", use "$GIT_DIR/logs/HEAD".  That way, you
can work in a bare repository (and you do not have to cd_to_toplevel,
either, I think).

Oh, don't forget to skip this step if the reflog does not exist.
+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?
+search_merges () {
+	git rev-list --pretty=tformat:"%h %p:%s" --all |
+	grep "Merge branch.*'$branch'.*into" |
"git merge tr/topic~4" can say "Merge branch 'tr/topic' (early part)".
Also merge into 'master' won't have "into ...".
+	while read sha rest; do
+		parents="$(echo "$rest" | cut -d: -f1)"
+		case "$parents" in
+		    *' '*' '*)
+			warn "$branch took part in octopus merge $sha"
+			warn "check manually!"
+			;;
+		    *' '*)
+			echo "$parents" | cut -d' ' -f2
+			;;
+		esac
+	done
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"

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

From: Thomas Rast <hidden>
Date: 2016-06-15 22:46:04

Junio C Hamano wrote:
Thomas Rast [off-list ref] writes:
quoted
Add a tool 'git-resurrect.sh <branch>' that tries to find traces of
Thanks for your review.  I've been busy and thus out of the loop all
week, but I'll try and make an improved version Soon(tm).

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

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

From: Thomas Rast <hidden>
Date: 2016-06-15 22:46:04

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 <redacted>
---

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...
quoted
+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.
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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help