# [PATCH] checkout: implement "-" shortcut name for last branch

46 messages from 2009-01-15 to 2009-02-05. Participants: Thomas Rast, Johannes Schindelin, Johannes Sixt, Johan Herland, Junio C Hamano, Santi Béjar, David Kastrup, Boyd Stephen Smith Jr..
Thread: https://gitlist.dev/t/17182

## Thomas Rast, 2009-01-15 00:06

Subject: [PATCH] checkout: implement "-" shortcut name for last branch
Message-ID: <1231977976-8739-1-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1231977976-8739-1-git-send-email-trast%40student.ethz.ch

```
Let git-checkout save the old branch as a symref in LAST_HEAD, and
make 'git checkout -' switch back to LAST_HEAD, like 'cd -' does in
the shell.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

I really wished I had this earlier today.  I'm just not sure if it's a
good idea, or even possible, to reserve the '-'.  I can't seem to
check out a branch '-foo' with git-checkout, but it's easy to create
one with 'git branch -- -foo'.  git-check-ref-format(1) doesn't forbid
it either, although the actual 'git check-ref-format -foo' exits with
status 1.

 Documentation/git-checkout.txt         |    3 +++
 Documentation/gitrepository-layout.txt |    4 ++++
 builtin-checkout.c                     |   26 +++++++++++++++++++++++++-
 3 files changed, 32 insertions(+), 1 deletions(-)

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 9cd5151..1397745 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -133,6 +133,9 @@ the conflicted merge in the specified paths.
 +
 When this parameter names a non-branch (but still a valid commit object),
 your HEAD becomes 'detached'.
++
+You may also specify "`-`", which denotes the last branch you were on
+before the current HEAD.
 
 
 Detached HEAD
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 1befca9..f506c98 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -123,6 +123,10 @@ is often called 'detached HEAD', and almost all commands work
 identically as normal.  See linkgit:git-checkout[1] for
 details.
 
+LAST_HEAD::
+	A symref that holds the value of HEAD before the last
+	branch switch.
+
 branches::
 	A slightly deprecated way to store shorthands to be used
 	to specify URL to 'git-fetch', 'git-pull' and 'git-push'
diff --git a/builtin-checkout.c b/builtin-checkout.c
index b5dd9c0..356ad6c 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -480,6 +480,15 @@ static void report_tracking(struct branch_info *new)
 	strbuf_release(&sb);
 }
 
+static void save_old_branch(struct branch_info *old, char *msg)
+{
+	if (old->path) {
+		create_symref("LAST_HEAD", old->path, msg);
+	} else
+		update_ref(msg, "LAST_HEAD", old->commit->object.sha1, NULL,
+			   REF_NODEREF, DIE_ON_ERR);
+}
+
 static void update_refs_for_switch(struct checkout_opts *opts,
 				   struct branch_info *old,
 				   struct branch_info *new)
@@ -505,12 +514,15 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 			if (old->path && !strcmp(new->path, old->path))
 				fprintf(stderr, "Already on \"%s\"\n",
 					new->name);
-			else
+			else {
 				fprintf(stderr, "Switched to%s branch \"%s\"\n",
 					opts->new_branch ? " a new" : "",
 					new->name);
+				save_old_branch(old, msg.buf);
+			}
 		}
 	} else if (strcmp(new->name, "HEAD")) {
+		save_old_branch(old, msg.buf);
 		update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
 			   REF_NODEREF, DIE_ON_ERR);
 		if (!opts->quiet) {
@@ -533,6 +545,8 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
 	int flag;
 	memset(&old, 0, sizeof(old));
 	old.path = resolve_ref("HEAD", rev, 0, &flag);
+	if (old.path)
+		old.path = strdup(old.path);
 	old.commit = lookup_commit_reference_gently(rev, 1);
 	if (!(flag & REF_ISSYMREF))
 		old.path = NULL;
@@ -604,6 +618,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_END(),
 	};
 	int has_dash_dash;
+	int flag;
 
 	memset(&opts, 0, sizeof(opts));
 	memset(&new, 0, sizeof(new));
@@ -671,6 +686,15 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		arg = argv[0];
 		has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
 
+		if (!strcmp(arg, "-")) {
+			arg = resolve_ref("LAST_HEAD", rev, 0, &flag);
+			if (!arg)
+				die("No last branch saved.");
+			if(!prefixcmp(arg, "refs/heads/"))
+				arg += 11;
+			arg = strdup(arg);
+		}
+
 		if (get_sha1(arg, rev)) {
 			if (has_dash_dash)          /* case (1) */
 				die("invalid reference: %s", arg);
-- 
1.6.1.282.gae4091.dirty

```

## Thomas Rast, 2009-01-15 00:12

Subject: [PATCH v2] checkout: implement "-" shortcut name for last branch
Message-ID: <1231978322-21228-1-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1231978322-21228-1-git-send-email-trast%40student.ethz.ch
In-Reply-To: <1231977976-8739-1-git-send-email-trast@student.ethz.ch>

```
Let git-checkout save the old branch as a symref in LAST_HEAD, and
make 'git checkout -' switch back to LAST_HEAD, like 'cd -' does in
the shell.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Bah, sorry.  I managed to keep it uncommitted AGAIN.

But this fixed version passes tests.  All of them.  Really!  ;-)


 Documentation/git-checkout.txt         |    3 ++
 Documentation/gitrepository-layout.txt |    4 ++
 builtin-checkout.c                     |   27 ++++++++++++++++-
 t/t2012-checkout-last.sh               |   50 ++++++++++++++++++++++++++++++++
 4 files changed, 83 insertions(+), 1 deletions(-)
 create mode 100755 t/t2012-checkout-last.sh

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 9cd5151..1397745 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -133,6 +133,9 @@ the conflicted merge in the specified paths.
 +
 When this parameter names a non-branch (but still a valid commit object),
 your HEAD becomes 'detached'.
++
+You may also specify "`-`", which denotes the last branch you were on
+before the current HEAD.
 
 
 Detached HEAD
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 1befca9..f506c98 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -123,6 +123,10 @@ is often called 'detached HEAD', and almost all commands work
 identically as normal.  See linkgit:git-checkout[1] for
 details.
 
+LAST_HEAD::
+	A symref that holds the value of HEAD before the last
+	branch switch.
+
 branches::
 	A slightly deprecated way to store shorthands to be used
 	to specify URL to 'git-fetch', 'git-pull' and 'git-push'
diff --git a/builtin-checkout.c b/builtin-checkout.c
index b5dd9c0..da74831 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -480,6 +480,16 @@ static void report_tracking(struct branch_info *new)
 	strbuf_release(&sb);
 }
 
+static void save_old_branch(struct branch_info *old, char *msg)
+{
+	if (old->path) {
+		create_symref("LAST_HEAD", old->path, msg);
+	} else if (old->commit) {
+		update_ref(msg, "LAST_HEAD", old->commit->object.sha1, NULL,
+			   REF_NODEREF, DIE_ON_ERR);
+	}
+}
+
 static void update_refs_for_switch(struct checkout_opts *opts,
 				   struct branch_info *old,
 				   struct branch_info *new)
@@ -505,12 +515,15 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 			if (old->path && !strcmp(new->path, old->path))
 				fprintf(stderr, "Already on \"%s\"\n",
 					new->name);
-			else
+			else {
 				fprintf(stderr, "Switched to%s branch \"%s\"\n",
 					opts->new_branch ? " a new" : "",
 					new->name);
+				save_old_branch(old, msg.buf);
+			}
 		}
 	} else if (strcmp(new->name, "HEAD")) {
+		save_old_branch(old, msg.buf);
 		update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
 			   REF_NODEREF, DIE_ON_ERR);
 		if (!opts->quiet) {
@@ -533,6 +546,8 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
 	int flag;
 	memset(&old, 0, sizeof(old));
 	old.path = resolve_ref("HEAD", rev, 0, &flag);
+	if (old.path)
+		old.path = strdup(old.path);
 	old.commit = lookup_commit_reference_gently(rev, 1);
 	if (!(flag & REF_ISSYMREF))
 		old.path = NULL;
@@ -604,6 +619,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_END(),
 	};
 	int has_dash_dash;
+	int flag;
 
 	memset(&opts, 0, sizeof(opts));
 	memset(&new, 0, sizeof(new));
@@ -671,6 +687,15 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		arg = argv[0];
 		has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
 
+		if (!strcmp(arg, "-")) {
+			arg = resolve_ref("LAST_HEAD", rev, 0, &flag);
+			if (!arg)
+				die("No last branch saved.");
+			if(!prefixcmp(arg, "refs/heads/"))
+				arg += 11;
+			arg = strdup(arg);
+		}
+
 		if (get_sha1(arg, rev)) {
 			if (has_dash_dash)          /* case (1) */
 				die("invalid reference: %s", arg);
diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
new file mode 100755
index 0000000..320f6eb
--- /dev/null
+++ b/t/t2012-checkout-last.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='checkout can switch to last branch'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo hello >world &&
+	git add world &&
+	git commit -m initial &&
+	git branch other &&
+	echo "hello again" >>world &&
+	git add world &&
+	git commit -m second
+'
+
+test_expect_success '"checkout -" does not work initially' '
+	test_must_fail git checkout -
+'
+
+test_expect_success 'first branch switch' '
+	git checkout other
+'
+
+test_expect_success '"checkout -" switches back' '
+	git checkout - &&
+	test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master"
+'
+
+test_expect_success '"checkout -" switches forth' '
+	git checkout - &&
+	test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success 'detach HEAD' '
+	git checkout $(git rev-parse HEAD)
+'
+
+test_expect_success '"checkout -" attaches again' '
+	git checkout - &&
+	test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success '"checkout -" detaches again' '
+	git checkout - &&
+	test "z$(git rev-parse HEAD)" = "z$(git rev-parse other)" &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_done
-- 
1.6.1.282.gae4091.dirty

```

## Johannes Schindelin, 2009-01-15 00:45

Subject: Re: [PATCH] checkout: implement "-" shortcut name for last branch
Message-ID: <alpine.DEB.1.00.0901150141570.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901150141570.3586%40pacific.mpi-cbg.de
In-Reply-To: <1231977976-8739-1-git-send-email-trast@student.ethz.ch>

```
Hi,

On Thu, 15 Jan 2009, Thomas Rast wrote:

> Let git-checkout save the old branch as a symref in LAST_HEAD, and
> make 'git checkout -' switch back to LAST_HEAD, like 'cd -' does in
> the shell.

Actually, what you want is in the reflog, no?  So... parsing 
.git/logs/HEAD for the latest occurrence of "checkout: moving from " and 
then using everything up until the next space should give you the branch 
name, right?

It could be a SHA-1, though, if the last branch switch was from a detached 
HEAD, though.

Ciao,
Dscho

```

## Johannes Sixt, 2009-01-15 07:27

Subject: Re: [PATCH v2] checkout: implement "-" shortcut name for last branch
Message-ID: <496EE559.3060901@viscovery.net>
URL: https://gitlist.dev/e/496EE559.3060901%40viscovery.net
In-Reply-To: <1231978322-21228-1-git-send-email-trast@student.ethz.ch>

```
Thomas Rast schrieb:
> Let git-checkout save the old branch as a symref in LAST_HEAD, and
> make 'git checkout -' switch back to LAST_HEAD, like 'cd -' does in
> the shell.

/me likes this feature.

git rebase (-i or not) calls checkout behind the scenes if the
two-argument form is used:

   git rebase [-i] master topic

and 'topic' is not the current branch. You may want to add a test that
ensures that rebase sets LAST_HEAD in this case.

You must make sure that commits referenced by LAST_HEAD are not
garbage-collected. (I don't know if this happens anyway for symrefs in .git.)

-- Hannes

```

## Johannes Schindelin, 2009-01-15 13:15

Subject: Re: [PATCH v2] checkout: implement "-" shortcut name for last branch
Message-ID: <alpine.DEB.1.00.0901151413250.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901151413250.3586%40pacific.mpi-cbg.de
In-Reply-To: <496EE559.3060901@viscovery.net>

```
Hi,

On Thu, 15 Jan 2009, Johannes Sixt wrote:

> Thomas Rast schrieb:
> > Let git-checkout save the old branch as a symref in LAST_HEAD, and
> > make 'git checkout -' switch back to LAST_HEAD, like 'cd -' does in
> > the shell.
> 
> /me likes this feature.
> 
> git rebase (-i or not) calls checkout behind the scenes if the
> two-argument form is used:
> 
>    git rebase [-i] master topic
> 
> and 'topic' is not the current branch. You may want to add a test that
> ensures that rebase sets LAST_HEAD in this case.
> 
> You must make sure that commits referenced by LAST_HEAD are not
> garbage-collected. (I don't know if this happens anyway for symrefs in .git.)

Note: if you used reflogs for that feature, the garbage collection could 
not have killed the commit.  However, it is quite possible that the 
branch was deleted.

Ciao,
Dscho

```

## Thomas Rast, 2009-01-15 13:59

Subject: Re: [PATCH v2] checkout: implement "-" shortcut name for last branch
Message-ID: <200901151500.01876.trast@student.ethz.ch>
URL: https://gitlist.dev/e/200901151500.01876.trast%40student.ethz.ch
In-Reply-To: <alpine.DEB.1.00.0901151413250.3586@pacific.mpi-cbg.de>

```
Johannes Schindelin wrote:
> On Thu, 15 Jan 2009, Johannes Sixt wrote:
> > You must make sure that commits referenced by LAST_HEAD are not
> > garbage-collected. (I don't know if this happens anyway for symrefs in .git.)
> 
> Note: if you used reflogs for that feature, the garbage collection could 
> not have killed the commit.  However, it is quite possible that the 
> branch was deleted.

Suddenly I'm not so sure about either behaviour any more.

Consider:

  $ git commit -m initial
  [master (root-commit)]: created 812c476: "initial"
   1 files changed, 1 insertions(+), 0 deletions(-)
   create mode 100644 foo
  $ git checkout $(git rev-parse HEAD)
  Note: moving to "812c476ca23e25efa7e4d7081153ba657a127d95" which isn't a local branch
  If you want to create a new branch from this checkout, you may do so
  (now or later) by using -b with the checkout command again. Example:
    git checkout -b <new_branch_name>
  HEAD is now at 812c476... initial
  $ git branch -D master
  Deleted branch master (812c476).
  $ git for-each-ref
  $ git reflog expire --expire=now --all
  $ git prune --expire now
  $ git show
  fatal: bad object HEAD
  $ git show 812c476
  fatal: ambiguous argument '812c476': unknown revision or path not in the working tree.
  Use '--' to separate paths from revisions

Oops.

Some quick RTFS shows that it indeed "only" cares about refs and
reflogs.

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


```

## Thomas Rast, 2009-01-15 14:01

Subject: Re: [PATCH] checkout: implement "-" shortcut name for last branch
Message-ID: <200901151501.26394.trast@student.ethz.ch>
URL: https://gitlist.dev/e/200901151501.26394.trast%40student.ethz.ch
In-Reply-To: <alpine.DEB.1.00.0901150141570.3586@pacific.mpi-cbg.de>

```
Johannes Schindelin wrote:
> On Thu, 15 Jan 2009, Thomas Rast wrote:
> 
> > Let git-checkout save the old branch as a symref in LAST_HEAD, and
> > make 'git checkout -' switch back to LAST_HEAD, like 'cd -' does in
> > the shell.
> 
> Actually, what you want is in the reflog, no?  So... parsing 
> .git/logs/HEAD for the latest occurrence of "checkout: moving from " and 
> then using everything up until the next space should give you the branch 
> name, right?

It just feels wrong to grab that information from there; it's a
free-form comment field for user consumption.  And it wasn't even that
hard to implement a LAST_HEAD.

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


```

## Johannes Schindelin, 2009-01-15 14:09

Subject: Re: [PATCH v2] checkout: implement "-" shortcut name for last branch
Message-ID: <alpine.DEB.1.00.0901151508540.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901151508540.3586%40pacific.mpi-cbg.de
In-Reply-To: <200901151500.01876.trast@student.ethz.ch>

```
Hi,

On Thu, 15 Jan 2009, Thomas Rast wrote:

> Johannes Schindelin wrote:
> > On Thu, 15 Jan 2009, Johannes Sixt wrote:
> > > You must make sure that commits referenced by LAST_HEAD are not
> > > garbage-collected. (I don't know if this happens anyway for symrefs in .git.)
> > 
> > Note: if you used reflogs for that feature, the garbage collection could 
> > not have killed the commit.  However, it is quite possible that the 
> > branch was deleted.
> 
> Suddenly I'm not so sure about either behaviour any more.
> 
> Consider:
> 
>   $ git commit -m initial
>   [master (root-commit)]: created 812c476: "initial"
>    1 files changed, 1 insertions(+), 0 deletions(-)
>    create mode 100644 foo
>   $ git checkout $(git rev-parse HEAD)
>   Note: moving to "812c476ca23e25efa7e4d7081153ba657a127d95" which isn't a local branch
>   If you want to create a new branch from this checkout, you may do so
>   (now or later) by using -b with the checkout command again. Example:
>     git checkout -b <new_branch_name>
>   HEAD is now at 812c476... initial
>   $ git branch -D master
>   Deleted branch master (812c476).
>   $ git for-each-ref
>   $ git reflog expire --expire=now --all
>   $ git prune --expire now
>   $ git show
>   fatal: bad object HEAD
>   $ git show 812c476
>   fatal: ambiguous argument '812c476': unknown revision or path not in the working tree.
>   Use '--' to separate paths from revisions
> 
> Oops.
> 
> Some quick RTFS shows that it indeed "only" cares about refs and
> reflogs.

Maybe something like this would help (completely untested, though the 
idea should be clear)?

-- snipsnap --
[PATCH] pack-objects --all: include HEAD, which could be detached

Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
---
 builtin-pack-objects.c |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index cb51916..da55671 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -2219,6 +2219,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
 						 rp_ac_alloc * sizeof(*rp_av));
 			}
 			rp_av[rp_ac++] = arg;
+			if (!strcmp("--all", arg)) {
+				ALLOC_GROW(rp_av, rp_ac + 1, rp_ac_alloc);
+				rp_av[rp_ac++] = "HEAD";
+			}
 			continue;
 		}
 		if (!strcmp("--thin", arg)) {

```

## Johannes Schindelin, 2009-01-15 14:14

Subject: Re: [PATCH] checkout: implement "-" shortcut name for last branch
Message-ID: <alpine.DEB.1.00.0901151510340.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901151510340.3586%40pacific.mpi-cbg.de
In-Reply-To: <200901151501.26394.trast@student.ethz.ch>

```
Hi,

On Thu, 15 Jan 2009, Thomas Rast wrote:

> Johannes Schindelin wrote:
> > On Thu, 15 Jan 2009, Thomas Rast wrote:
> > 
> > > Let git-checkout save the old branch as a symref in LAST_HEAD, and
> > > make 'git checkout -' switch back to LAST_HEAD, like 'cd -' does in
> > > the shell.
> > 
> > Actually, what you want is in the reflog, no?  So... parsing 
> > .git/logs/HEAD for the latest occurrence of "checkout: moving from " and 
> > then using everything up until the next space should give you the branch 
> > name, right?
> 
> It just feels wrong to grab that information from there; it's a
> free-form comment field for user consumption.  And it wasn't even that
> hard to implement a LAST_HEAD.

There are a number of issues why I would like to avoid introducing 
LAST_HEAD:

- it does not work when you are using different Git versions on the same 
  repository,

- it does not work when you switched recently,

- you are storing redundant information,

- yes, the field is meant for user consumption, but no, it is not 
  free-form,

- AFAICT your version could never be convinced to resurrect deleted 
  branches, without resorting to reflogs anyway.

- the reflog method reflects pretty much exactly how people work around 
  the lack of "checkout -" currently, so why not just use the same proven 
  approach?

Ciao,
Dscho

```

## Johannes Schindelin, 2009-01-15 14:17

Subject: Re: [PATCH v2] checkout: implement "-" shortcut name for last branch
Message-ID: <alpine.DEB.1.00.0901151517190.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901151517190.3586%40pacific.mpi-cbg.de
In-Reply-To: <alpine.DEB.1.00.0901151508540.3586@pacific.mpi-cbg.de>

```
Hi,

On Thu, 15 Jan 2009, Johannes Schindelin wrote:

> [PATCH] pack-objects --all: include HEAD, which could be detached

In hind sight, it would probably be better to add this to revision.c.

Ciao,
Dscho

```

## Johan Herland, 2009-01-15 16:32

Subject: Re: [PATCH v2] checkout: implement "-" shortcut name for last branch
Message-ID: <200901151732.57023.johan@herland.net>
URL: https://gitlist.dev/e/200901151732.57023.johan%40herland.net
In-Reply-To: <alpine.DEB.1.00.0901151413250.3586@pacific.mpi-cbg.de>

```
On Thursday 15 January 2009, Johannes Schindelin wrote:
> Hi,
>
> On Thu, 15 Jan 2009, Johannes Sixt wrote:
> > Thomas Rast schrieb:
> > > Let git-checkout save the old branch as a symref in LAST_HEAD,
> > > and make 'git checkout -' switch back to LAST_HEAD, like 'cd -'
> > > does in the shell.
> >
> > /me likes this feature.
> >
> > git rebase (-i or not) calls checkout behind the scenes if the
> > two-argument form is used:
> >
> >    git rebase [-i] master topic
> >
> > and 'topic' is not the current branch. You may want to add a test
> > that ensures that rebase sets LAST_HEAD in this case.
> >
> > You must make sure that commits referenced by LAST_HEAD are not
> > garbage-collected. (I don't know if this happens anyway for symrefs
> > in .git.)
>
> Note: if you used reflogs for that feature, the garbage collection
> could not have killed the commit.  However, it is quite possible that
> the branch was deleted.

I also like this feature, but as this is only a _convenience_ feature, I 
would prefer if it didn't keep the previous branch/commit alive (if 
otherwise unreachable). In any case, this new feature will _have_ to 
handle the case where there simply is no previous branch/commit (e.g. 
after a git clone or git init).

I suggest that "git checkout -" looks at the reflog, and if there is no 
previous entry in the reflog, or that entry is unreachable, then fail 
in the same manner as "git checkout garbage"


Have fun! :)

...Johan

-- 
Johan Herland, <johan@herland.net>
www.herland.net

```

## Johannes Schindelin, 2009-01-15 16:50

Subject: Re: [PATCH v2] checkout: implement "-" shortcut name for last branch
Message-ID: <alpine.DEB.1.00.0901151749350.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901151749350.3586%40pacific.mpi-cbg.de
In-Reply-To: <200901151732.57023.johan@herland.net>

```
Hi,

On Thu, 15 Jan 2009, Johan Herland wrote:

> On Thursday 15 January 2009, Johannes Schindelin wrote:
>
> > On Thu, 15 Jan 2009, Johannes Sixt wrote:
> > > Thomas Rast schrieb:
> > > > Let git-checkout save the old branch as a symref in LAST_HEAD,
> > > > and make 'git checkout -' switch back to LAST_HEAD, like 'cd -'
> > > > does in the shell.
> > >
> > > /me likes this feature.
> > >
> > > git rebase (-i or not) calls checkout behind the scenes if the
> > > two-argument form is used:
> > >
> > >    git rebase [-i] master topic
> > >
> > > and 'topic' is not the current branch. You may want to add a test
> > > that ensures that rebase sets LAST_HEAD in this case.
> > >
> > > You must make sure that commits referenced by LAST_HEAD are not
> > > garbage-collected. (I don't know if this happens anyway for symrefs
> > > in .git.)
> >
> > Note: if you used reflogs for that feature, the garbage collection
> > could not have killed the commit.  However, it is quite possible that
> > the branch was deleted.
> 
> I also like this feature, but as this is only a _convenience_ feature, I 
> would prefer if it didn't keep the previous branch/commit alive (if 
> otherwise unreachable).

You misread me: if the information is in HEAD's reflog, the _is_ 
reachable.  From HEAD's reflog.  And therefore, the objects will not be 
gc'ed (yet).

> In any case, this new feature will _have_ to handle the case where there 
> simply is no previous branch/commit (e.g. after a git clone or git 
> init).
>
> I suggest that "git checkout -" looks at the reflog, and if there is no 
> previous entry in the reflog, or that entry is unreachable, then fail 
> in the same manner as "git checkout garbage"

Exactly my thinking.

Ciao,
Dscho

```

## Thomas Rast, 2009-01-15 17:05

Subject: Re: [PATCH] checkout: implement "-" shortcut name for last branch
Message-ID: <200901151805.44747.trast@student.ethz.ch>
URL: https://gitlist.dev/e/200901151805.44747.trast%40student.ethz.ch
In-Reply-To: <alpine.DEB.1.00.0901151510340.3586@pacific.mpi-cbg.de>

```
Johannes Schindelin wrote:
> There are a number of issues why I would like to avoid introducing 
> LAST_HEAD:
> 
> - it does not work when you are using different Git versions on the same 
>   repository,
> 
> - it does not work when you switched recently,

If you switch once, you'll be able to use the feature one checkout
later than if it was reflog-based.

If you switch a lot, the feature won't be in your git half the time
anyway.

> - you are storing redundant information,

AFAIK it's the first instance of this data in a non-free-form field.
There's also the precedent of ORIG_HEAD.

> - yes, the field is meant for user consumption, but no, it is not 
>   free-form,

It's a field of almost arbitrary character data, filled by 70% of the
update-ref calls I can find in git.git in a "<tool>: <comment>" format
and by the rest with things such as "initial pull" or
"refs/remotes/git-svn: updating HEAD".  (The latter is so informative
that it probably deserves a fix.)  How is that not free-form?

> - AFAICT your version could never be convinced to resurrect deleted 
>   branches, without resorting to reflogs anyway.

Neither can any other use of git-checkout without the user manually
recovering some valid revspec referring to the old branch tip from the
reflog.  I wanted to be able to abbreviate the previous branch's name,
and it does just that.

> - the reflog method reflects pretty much exactly how people work around 
>   the lack of "checkout -" currently, so why not just use the same proven 
>   approach?

So you can make me fight an uphill battle against your idea how it
should be done.


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









```

## Johannes Schindelin, 2009-01-15 18:34

Subject: Re: [PATCH] checkout: implement "-" shortcut name for last branch
Message-ID: <alpine.DEB.1.00.0901151922360.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901151922360.3586%40pacific.mpi-cbg.de
In-Reply-To: <200901151805.44747.trast@student.ethz.ch>

```
Hi,

On Thu, 15 Jan 2009, Thomas Rast wrote:

> Johannes Schindelin wrote:
> > There are a number of issues why I would like to avoid introducing 
> > LAST_HEAD:
> > 
> > - it does not work when you are using different Git versions on the same 
> >   repository,
> > 
> > - it does not work when you switched recently,
> 
> If you switch once, you'll be able to use the feature one checkout
> later than if it was reflog-based.
> 
> If you switch a lot, the feature won't be in your git half the time
> anyway.

But once it is, you could also have something like "git checkout -{5}" 
meaning the 5th last branch you were on.

No, I am not married to that syntax

> > - you are storing redundant information,
> 
> AFAIK it's the first instance of this data in a non-free-form field.
> There's also the precedent of ORIG_HEAD.

See below.

> > - yes, the field is meant for user consumption, but no, it is not 
> >   free-form,
> 
> It's a field of almost arbitrary character data, filled by 70% of the
> update-ref calls I can find in git.git in a "<tool>: <comment>" format
> and by the rest with things such as "initial pull" or
> "refs/remotes/git-svn: updating HEAD".  (The latter is so informative
> that it probably deserves a fix.)  How is that not free-form?

That is not free-form, as the "<tool>:" is a hard convention all obey 
(and therefore, git checkout - only relies on _checkout_ not changing the 
format), and checkout is sufficiently plumbing that we will not change it 
all that lightly, certainly not when "git checkout -" depends on it.

So I think that those free-form concerns are totally unfounded.

Oh, and before you say that people could mess with GIT_REFLOG_ACTION, git 
checkout is no longer a script, and creates the message itself.  So we 
have full control over it.

They could edit the logs directly, but that applies to virtually the whole 
repository, and can safely be ignored as a lemming behavior.

> > - AFAICT your version could never be convinced to resurrect deleted 
> >   branches, without resorting to reflogs anyway.
> 
> Neither can any other use of git-checkout without the user manually
> recovering some valid revspec referring to the old branch tip from the
> reflog.

To the contrary.  The reflog has this information together with the 
message "moved from ...".

> > - the reflog method reflects pretty much exactly how people work around 
> >   the lack of "checkout -" currently, so why not just use the same proven 
> >   approach?
> 
> So you can make me fight an uphill battle against your idea how it
> should be done.

If you can convince me that there are benefits from introducing yet 
another file in $GIT_DIR and duplicating information that is in the 
reflogs already, then no, it's not an uphill battle.

I mean, I _like_ the feature.  Otherwise I would not spend so much time 
suggesting what I think would be a method more in line with what we have 
already.

Ciao,
Dscho

```

## Junio C Hamano, 2009-01-15 20:11

Subject: Re: [PATCH v2] checkout: implement "-" shortcut name for last branch
Message-ID: <7vocy8s51o.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vocy8s51o.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <1231978322-21228-1-git-send-email-trast@student.ethz.ch>

```
Thomas Rast <trast@student.ethz.ch> writes:

> Let git-checkout save the old branch as a symref in LAST_HEAD, and
> make 'git checkout -' switch back to LAST_HEAD, like 'cd -' does in
> the shell.

I do not like this for two reasons.

I will not dispute that you would need to have "checkout" and other branch
switching operations to record where you were in order to be able to refer
to "where I was".  And as Dscho and others point out, there already is an
existing mechanism that does exactly that, so it _might_ be easier to work
with an extra LAST_HEAD, it is not absolutely necessary.

I do not see a reason to limit the new notation "where I was" only to "git
checkout".  Wouldn't it be handy if you can use the notation as the other
branch to merge from, or the commit to rebase on?

"cd -" is a very good analogy why your "-" shortcut is a short-sighted
convenience feature that is too narrow and not well designed.  "cd -" can
go back, but you cannot say "ls -" to list the contents of the previous
directory.

So if this topic were "Introduce LAST_HEAD to always keep track of the
branch I was on before the current branch", and were advertised as "You
can use this throughout git to say things like 'git checkout LAST_HEAD',
'git merge LAST_HEAD', and 'git rebase LAST_HEAD'", I think it might have
made a bit more sense.  You could _additionally_ say "because switching to
LAST_HEAD happens very often, there is another short cut 'checkout -' but
that is exactly the same as 'checkout LAST_HEAD'".

Another reason is the one level limitation.  If we do not use LAST_HEAD,
and instead used HEAD reflog, to get to this information, there is no
reason we cannot to give an equally easy access to the second from the
last branch the user was on.

So I think it is just the matter of coming up with a clever syntax that
works on reflogs to name the nth last branch we were on and teach that
syntax to both get_sha1() and resolve_ref().

With the attached illustration patch,

     $ git checkout junk
     $ git chekcout master
     $ git checkout @{-1}

will take you back to junk branch.  It probably would serve as a starting
point, if anybody is interested.

NOTE!

 * It will report "Switched to branch "junk", not "junk (@{-1})" or
   anything that hints the user used this new syntax.  switch_branches()
   may need to be given more information to distinguish the name the end
   user spelled to specify the branch (e.g. "@{-1}") and the actual name
   of the branch (e.g. "junk"), and use the former together with the
   latter when reporting to the end user and use the latter only to record
   what happened to the reflog.  But this is a very minor point.

 * The reflog parser only parses "checkout" and not rebase action.  It
   also does not notice "git checkout HEAD^" is not switching to a real
   branch.

 * The code read the reflog twice, first to count how many branch
   switching there are and then to locate the N-th entry we are interested
   in, because I was lazy.  We may want an API to enumerate reflog entries
   in reverse.

 * interpret_nth_last_branch() is not hooked to get_sha1() codepath in
   this patch, so this is still only applicable to "git checkout".  But it
   should be trivial to do so.

 builtin-checkout.c |   10 +++++-
 cache.h            |    1 +
 sha1_name.c        |   78 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+), 2 deletions(-)

diff --git c/builtin-checkout.c w/builtin-checkout.c
index b5dd9c0..a3b69d6 100644
--- c/builtin-checkout.c
+++ w/builtin-checkout.c
@@ -361,8 +361,14 @@ struct branch_info {
 static void setup_branch_path(struct branch_info *branch)
 {
 	struct strbuf buf = STRBUF_INIT;
-	strbuf_addstr(&buf, "refs/heads/");
-	strbuf_addstr(&buf, branch->name);
+
+	if (!interpret_nth_last_branch(branch->name, &buf)) {
+		branch->name = xstrdup(buf.buf);
+		strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
+	} else {
+		strbuf_addstr(&buf, "refs/heads/");
+		strbuf_addstr(&buf, branch->name);
+	}
 	branch->path = strbuf_detach(&buf, NULL);
 }
 
diff --git c/cache.h w/cache.h
index 8e1af26..0dd9168 100644
--- c/cache.h
+++ w/cache.h
@@ -663,6 +663,7 @@ extern int read_ref(const char *filename, unsigned char *sha1);
 extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
 extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
 extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
+extern int interpret_nth_last_branch(const char *str, struct strbuf *);
 
 extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
 extern const char *ref_rev_parse_rules[];
diff --git c/sha1_name.c w/sha1_name.c
index 159c2ab..6377264 100644
--- c/sha1_name.c
+++ w/sha1_name.c
@@ -674,6 +674,84 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
 	return retval;
 }
 
+struct grab_nth_branch_switch_cbdata {
+	int counting;
+	int nth;
+	struct strbuf *buf;
+};
+
+static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
+				  const char *email, unsigned long timestamp, int tz,
+				  const char *message, void *cb_data)
+{
+	struct grab_nth_branch_switch_cbdata *cb = cb_data;
+	const char *match = NULL;
+
+	if (!prefixcmp(message, "checkout: moving to "))
+		match = message + strlen("checkout: moving to ");
+	else if (!prefixcmp(message, "checkout: moving from ")) {
+		const char *cp = message + strlen("checkout: moving from ");
+		if ((cp = strstr(cp, " to ")) != NULL) {
+			match = cp + 4;
+		}
+	}
+
+	if (!match)
+		return 0;
+
+	if (cb->counting) {
+		cb->nth++;
+		return 0;
+	}
+
+	if (--cb->nth <= 0) {
+		size_t len = strlen(match);
+		while (match[len-1] == '\n')
+			len--;
+		strbuf_reset(cb->buf);
+		strbuf_add(cb->buf, match, len);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * This reads "@{-N}" syntax, finds the name of the Nth previous
+ * branch we were on, and places the name of the branch in the given
+ * buf and returns 0 if successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ */
+int interpret_nth_last_branch(const char *name, struct strbuf *buf)
+{
+	int nth, i;
+	struct grab_nth_branch_switch_cbdata cb;
+
+	if (name[0] != '@' || name[1] != '{' || name[2] != '-')
+		return -1;
+	for (i = 3, nth = 0; name[i] && name[i] != '}'; i++) {
+		char ch = name[i];
+		if ('0' <= ch && ch <= '9')
+			nth = nth * 10 + ch - '0';
+		else
+			return -1;
+	}
+	if (nth < 0 || 10 <= nth)
+		return -1;
+
+	cb.counting = 1;
+	cb.nth = 0;
+	cb.buf = buf;
+	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+
+	cb.counting = 0;
+	cb.nth -= nth;
+	cb.buf = buf;
+	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+	return 0;
+}
+
 /*
  * This is like "get_sha1_basic()", except it allows "sha1 expressions",
  * notably "xyz^" for "parent of xyz"

```

## Junio C Hamano, 2009-01-15 20:12

Subject: Re: [PATCH v2] checkout: implement "-" shortcut name for last branch
Message-ID: <7vhc40s50t.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vhc40s50t.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <alpine.DEB.1.00.0901151517190.3586@pacific.mpi-cbg.de>

```
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> On Thu, 15 Jan 2009, Johannes Schindelin wrote:
>
>> [PATCH] pack-objects --all: include HEAD, which could be detached
>
> In hind sight, it would probably be better to add this to revision.c.

If you mean that "git log --all" should also include a possibly detached
HEAD in its traversal, and a patch that implements such a fix would
automatically fix "repack -a" without the patch you are responding to, I
think I agree 100%.

```

## Johannes Schindelin, 2009-01-15 20:35

Subject: Re: [PATCH v2] checkout: implement "-" shortcut name for last branch
Message-ID: <alpine.DEB.1.00.0901152132390.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901152132390.3586%40pacific.mpi-cbg.de
In-Reply-To: <7vhc40s50t.fsf@gitster.siamese.dyndns.org>

```
Hi,

On Thu, 15 Jan 2009, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > On Thu, 15 Jan 2009, Johannes Schindelin wrote:
> >
> >> [PATCH] pack-objects --all: include HEAD, which could be detached
> >
> > In hind sight, it would probably be better to add this to revision.c.
> 
> If you mean that "git log --all" should also include a possibly detached
> HEAD in its traversal, and a patch that implements such a fix would
> automatically fix "repack -a" without the patch you are responding to, I
> think I agree 100%.

Yes, indeed.

Something like

-- snip --
diff --git a/revision.c b/revision.c
index db60f06..b065184 100644
--- a/revision.c
+++ b/revision.c
@@ -1263,6 +1263,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
 
 			if (!strcmp(arg, "--all")) {
 				handle_refs(revs, flags, for_each_ref);
+				handle_refs(revs, flags, head_ref);
 				continue;
 			}
 			if (!strcmp(arg, "--branches")) {
-- snap --

but that was just a quick guess, and if nobody beats me to it, I'll turn 
it into a proper patch later.

Ciao,
Dscho

```

## Junio C Hamano, 2009-01-15 20:50

Subject: Re: [PATCH v2] checkout: implement "-" shortcut name for last branch
Message-ID: <7v8wpcs38c.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7v8wpcs38c.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <7vocy8s51o.fsf@gitster.siamese.dyndns.org>

```
Junio C Hamano <gitster@pobox.com> writes:

> So I think it is just the matter of coming up with a clever syntax that
> works on reflogs to name the nth last branch we were on and teach that
> syntax to both get_sha1() and resolve_ref().
>
> With the attached illustration patch,
>
>      $ git checkout junk
>      $ git chekcout master
>      $ git checkout @{-1}
>
> will take you back to junk branch.  It probably would serve as a starting
> point, if anybody is interested.
>
> NOTE!
> ...
>  * interpret_nth_last_branch() is not hooked to get_sha1() codepath in
>    this patch, so this is still only applicable to "git checkout".  But it
>    should be trivial to do so.
> ...
> +/*
> + * This reads "@{-N}" syntax, finds the name of the Nth previous
> + * branch we were on, and places the name of the branch in the given
> + * buf and returns 0 if successful.
> + *
> + * If the input is not of the accepted format, it returns a negative
> + * number to signal an error.
> + */
> +int interpret_nth_last_branch(const char *name, struct strbuf *buf)

A few more things to note.

 * interpret_nth_last_branch() probably should return how many bytes it
   consumed, instead of returning 0 in the successful case.  This is to
   allow things like "git merge @{-1}~2" to be easily parsed, either by
   "git merge" itself into "git merge junk~2", which would result in
   "Merge branch junk (early part)", or by get_sha1() which would result
   in "Merge commit deadbeefacebeads".

 * I mentioned resolve_ref() may need to be told about this syntax but I
   do not think it is necessary.  If a command that can take an arbitrary
   refname or committish in the most general case does something special
   when the end user input is a branch name ("git checkout" is a prime
   example for this, but "git merge" also has this property, illustrated
   by the previous "Merge branch junk" example), these commands has to do
   their own special case logic before the user input hits get_sha1() or
   resolve_ref() anyway (setup_branch_path() in builtin-checkout.c is a
   good example of this), and such special case logic can and probably
   should use interpret_nth_last_branch() directly.

```

## Thomas Rast, 2009-01-16 09:08

Subject: Re: [PATCH] checkout: implement "-" shortcut name for last branch
Message-ID: <200901161008.16234.trast@student.ethz.ch>
URL: https://gitlist.dev/e/200901161008.16234.trast%40student.ethz.ch
In-Reply-To: <alpine.DEB.1.00.0901151510340.3586@pacific.mpi-cbg.de>

```
Johannes Schindelin wrote:
> - AFAICT your version could never be convinced to resurrect deleted 
>   branches, without resorting to reflogs anyway.

Speaking of resurrection, there are other possible sources that a
branch tip could be gleaned from.  How about the script below?  The
advantage is that it can even be used to recover Junio's topic
branches by looking at the merges in 'pu'.

(I'll answer the rest later.)

--- 8< ---
#!/bin/sh

. git-sh-setup

USAGE="<branch>"

test "$#" = 1 || usage

branch="$1"
candidates=

search_reflog () {
	next=
	git reflog show HEAD |
	while read sha ref msg; do
		if test -n "$next"; then
			next=
			echo ${sha%...}
		fi
		if echo "$msg" | grep -q "^checkout: moving from $branch "; then
			next=t
		fi
		if echo "$msg" | grep -q "^merge $branch:"; then
			git rev-list --parents -1 ${sha%...} \
				| cut -d' ' -f3
		fi
	done
}

search_merges () {
	git rev-list --pretty=tformat:"%h %p:%s" --all |
	grep "Merge branch.*'$branch'.*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
}

search_merge_targets () {
	git rev-list --pretty=tformat:"%h %s" --all |
	grep "Merge branch '[^']*' into $branch$" |
	cut -d' ' -f1
}

candidates="$(search_reflog | sort -u)"
if test -z "$candidates"; then
	echo "** Searching merges... **"
	candidates="$( (search_merges;search_merge_targets) | sort -u)"
fi

echo "** Candidates **"
for cmt in $candidates; do
	git --no-pager log --pretty=oneline --abbrev-commit -1 $cmt
done

newest=$(git rev-list -1 $candidates)

if ! git rev-parse --verify --quiet $branch >/dev/null; then
	printf "** Restoring $branch to "
	git --no-pager log -1 --pretty=tformat:"%h %s" $newest
	git branch $branch $newest
else
	printf "Most recent among them: "
	git --no-pager log -1 --pretty=tformat:"%h %s" $newest
	echo "** $branch already exists, doing nothing"
fi


```

## Johannes Schindelin, 2009-01-16 11:18

Subject: Re: [PATCH] checkout: implement "-" shortcut name for last branch
Message-ID: <alpine.DEB.1.00.0901161213370.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901161213370.3586%40pacific.mpi-cbg.de
In-Reply-To: <200901161008.16234.trast@student.ethz.ch>

```
Hi,

On Fri, 16 Jan 2009, Thomas Rast wrote:

> search_reflog () {
> 	next=
> 	git reflog show HEAD |
> 	while read sha ref msg; do
> 		if test -n "$next"; then
> 			next=
> 			echo ${sha%...}
> 		fi
> 		if echo "$msg" | grep -q "^checkout: moving from $branch "; then
> 			next=t
> 		fi
> 		if echo "$msg" | grep -q "^merge $branch:"; then
> 			git rev-list --parents -1 ${sha%...} \
> 				| cut -d' ' -f3
> 		fi
> 	done
> }

How about this instead:

search_reflog () {
	sed -n 's/\([^ ]*\) .*\tcheckout: moving from $branch .*/\1/p' \
		< .git/logs/HEAD
}

Of course, this leaves out the merges...  but I'd make that a command line 
option anyway: would you like to resurrect a branch that you recently were 
on, or one that you recently merged, or one that was merged by someone 
else?

Ciao,
Dscho

```

## Johannes Schindelin, 2009-01-16 12:31

Subject: Re: [PATCH v2] checkout: implement "-" shortcut name for last branch
Message-ID: <alpine.DEB.1.00.0901161329490.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901161329490.3586%40pacific.mpi-cbg.de
In-Reply-To: <7vocy8s51o.fsf@gitster.siamese.dyndns.org>

```
Hi,

On Thu, 15 Jan 2009, Junio C Hamano wrote:

> I do not see a reason to limit the new notation "where I was" only to 
> "git checkout".  Wouldn't it be handy if you can use the notation as the 
> other branch to merge from, or the commit to rebase on?
> 
> [...]
> 
> Another reason is the one level limitation.  If we do not use LAST_HEAD,
> and instead used HEAD reflog, to get to this information, there is no
> reason we cannot to give an equally easy access to the second from the
> last branch the user was on.
> 
> So I think it is just the matter of coming up with a clever syntax that
> works on reflogs to name the nth last branch we were on and teach that
> syntax to both get_sha1() and resolve_ref().
> 
> With the attached illustration patch,
> 
>      $ git checkout junk
>      $ git chekcout master
>      $ git checkout @{-1}
> 
> will take you back to junk branch.  It probably would serve as a starting
> point, if anybody is interested.

I like it.  Additionaly, we could teach "checkout" that "-" is 
equivalent to "@{-1}", as checkout cannot possibly take stdin, so 
it would not hurt.  Thomas?

Ciao,
Dscho

```

## Johannes Schindelin, 2009-01-16 12:52

Subject: [PATCH] revision walker: include a detached HEAD in --all
Message-ID: <alpine.DEB.1.00.0901161351460.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901161351460.3586%40pacific.mpi-cbg.de
In-Reply-To: <7vhc40s50t.fsf@gitster.siamese.dyndns.org>

```

When HEAD is detached, --all should list it, too, logically, as a
detached HEAD is by definition a temporary, unnamed branch.

It is especially necessary to list it when garbage collecting, as
the detached HEAD would be trashed.

Noticed by Thomas Rast.

Note that this affects creating bundles with --all; I contend that it
is a good change to add the HEAD, so that cloning from such a bundle
will give you a current branch.  However, I had to fix t5701 as it
assumed that --all does not imply HEAD.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---

	On Thu, 15 Jan 2009, Junio C Hamano wrote:

	> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
	> 
	> > On Thu, 15 Jan 2009, Johannes Schindelin wrote:
	> >
	> >> [PATCH] pack-objects --all: include HEAD, which could be 
	> >> detached
	> >
	> > In hind sight, it would probably be better to add this to 
	> > revision.c.
	> 
	> If you mean that "git log --all" should also include a possibly 
	> detached HEAD in its traversal, and a patch that implements such a fix 
	> would automatically fix "repack -a" without the patch you are 
	> responding to, I think I agree 100%.

	Here it is.  (Sorry for the delay, it was due to some 
	well-deserved inebriation.)

 revision.c              |    1 +
 t/t5701-clone-local.sh  |    4 ++--
 t/t6014-rev-list-all.sh |   38 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 41 insertions(+), 2 deletions(-)
 create mode 100755 t/t6014-rev-list-all.sh

diff --git a/revision.c b/revision.c
index db60f06..b065184 100644
--- a/revision.c
+++ b/revision.c
@@ -1263,6 +1263,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
 
 			if (!strcmp(arg, "--all")) {
 				handle_refs(revs, flags, for_each_ref);
+				handle_refs(revs, flags, head_ref);
 				continue;
 			}
 			if (!strcmp(arg, "--branches")) {
diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh
index 8dfaaa4..14413f8 100755
--- a/t/t5701-clone-local.sh
+++ b/t/t5701-clone-local.sh
@@ -11,8 +11,8 @@ test_expect_success 'preparing origin repository' '
 	git clone --bare . x &&
 	test "$(GIT_CONFIG=a.git/config git config --bool core.bare)" = true &&
 	test "$(GIT_CONFIG=x/config git config --bool core.bare)" = true
-	git bundle create b1.bundle --all HEAD &&
-	git bundle create b2.bundle --all &&
+	git bundle create b1.bundle master HEAD &&
+	git bundle create b2.bundle master &&
 	mkdir dir &&
 	cp b1.bundle dir/b3
 	cp b1.bundle b4
diff --git a/t/t6014-rev-list-all.sh b/t/t6014-rev-list-all.sh
new file mode 100755
index 0000000..991ab4a
--- /dev/null
+++ b/t/t6014-rev-list-all.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+test_description='--all includes detached HEADs'
+
+. ./test-lib.sh
+
+
+commit () {
+	test_tick &&
+	echo $1 > foo &&
+	git add foo &&
+	git commit -m "$1"
+}
+
+test_expect_success 'setup' '
+
+	commit one &&
+	commit two &&
+	git checkout HEAD^ &&
+	commit detached
+
+'
+
+test_expect_success 'rev-list --all lists detached HEAD' '
+
+	test 3 = $(git rev-list --all | wc -l)
+
+'
+
+test_expect_success 'repack does not lose detached HEAD' '
+
+	git gc &&
+	git prune --expire=now &&
+	git show HEAD
+
+'
+
+test_done
-- 
1.6.1.299.gfdbb

```

## Santi Béjar, 2009-01-16 13:12

Subject: Re: [PATCH] revision walker: include a detached HEAD in --all
Message-ID: <adf1fd3d0901160512i2de8f473gd471cc1dcb72afa4@mail.gmail.com>
URL: https://gitlist.dev/e/adf1fd3d0901160512i2de8f473gd471cc1dcb72afa4%40mail.gmail.com
In-Reply-To: <alpine.DEB.1.00.0901161351460.3586@pacific.mpi-cbg.de>

```
2009/1/16 Johannes Schindelin <Johannes.Schindelin@gmx.de>:
>
> When HEAD is detached, --all should list it, too, logically, as a
> detached HEAD is by definition a temporary, unnamed branch.
>
> It is especially necessary to list it when garbage collecting, as
> the detached HEAD would be trashed.
>
> Noticed by Thomas Rast.
>
> Note that this affects creating bundles with --all; I contend that it
> is a good change to add the HEAD, so that cloning from such a bundle
> will give you a current branch.  However, I had to fix t5701 as it
> assumed that --all does not imply HEAD.

>From the description I understand that it only affects when the HEAD
is detached, but in t5701 the HEAD is not detached so nothing should
be fixed.

For gc for sure it is a good thing, but I'm not convinced of the
others, as a detached HEAD is a very special thing (temporary and
unnamed branch).

Santi

```

## Johannes Schindelin, 2009-01-16 13:17

Subject: Re: [PATCH] revision walker: include a detached HEAD in --all
Message-ID: <alpine.DEB.1.00.0901161415230.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901161415230.3586%40pacific.mpi-cbg.de
In-Reply-To: <adf1fd3d0901160512i2de8f473gd471cc1dcb72afa4@mail.gmail.com>

```
Hi,

On Fri, 16 Jan 2009, Santi Béjar wrote:

> 2009/1/16 Johannes Schindelin <Johannes.Schindelin@gmx.de>:
> >
> > Note that this affects creating bundles with --all; I contend that it 
> > is a good change to add the HEAD, so that cloning from such a bundle 
> > will give you a current branch.  However, I had to fix t5701 as it 
> > assumed that --all does not imply HEAD.
> 
> From the description I understand that it only affects when the HEAD is 
> detached, but in t5701 the HEAD is not detached so nothing should be 
> fixed.

The error in t5701 was that it _wanted_ to test a bundle without a HEAD, 
but it actually created it with --all.  That was implying that --all does 
not mean HEAD, and I disagree with that.

> For gc for sure it is a good thing, but I'm not convinced of the others, 
> as a detached HEAD is a very special thing (temporary and unnamed 
> branch).

So?  What does "--all" mean?  All branches or what? :-)

Seriously, I think that --all should imply HEAD at all times, as the only 
time when it makes a difference is when you have that unnamed _branch_ 
that is a detached HEAD.

Maybe I would be more amenable to your criticism if you could come up with 
a scenario where implying HEAD with --all is _wrong_.

Ciao,
Dscho

```

## David Kastrup, 2009-01-16 13:22

Subject: Re: [PATCH] revision walker: include a detached HEAD in --all
Message-ID: <868wpbmlmb.fsf@lola.quinscape.zz>
URL: https://gitlist.dev/e/868wpbmlmb.fsf%40lola.quinscape.zz
In-Reply-To: <alpine.DEB.1.00.0901161415230.3586@pacific.mpi-cbg.de>

```
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> On Fri, 16 Jan 2009, Santi Béjar wrote:
>
>> 2009/1/16 Johannes Schindelin <Johannes.Schindelin@gmx.de>:
>> >
>> > Note that this affects creating bundles with --all; I contend that it 
>> > is a good change to add the HEAD, so that cloning from such a bundle 
>> > will give you a current branch.  However, I had to fix t5701 as it 
>> > assumed that --all does not imply HEAD.
>> 
>> From the description I understand that it only affects when the HEAD is 
>> detached, but in t5701 the HEAD is not detached so nothing should be 
>> fixed.
>
> The error in t5701 was that it _wanted_ to test a bundle without a HEAD, 
> but it actually created it with --all.  That was implying that --all does 
> not mean HEAD, and I disagree with that.

I don't think that a detached HEAD should be a special case, since you
can have other detached symbolic references no longer on a branch.  None
of those should be garbage-collected either, I think.

-- 
David Kastrup

```

## Santi Béjar, 2009-01-16 13:46

Subject: Re: [PATCH] revision walker: include a detached HEAD in --all
Message-ID: <adf1fd3d0901160546o50db0594h7377774fed9fef99@mail.gmail.com>
URL: https://gitlist.dev/e/adf1fd3d0901160546o50db0594h7377774fed9fef99%40mail.gmail.com
In-Reply-To: <alpine.DEB.1.00.0901161415230.3586@pacific.mpi-cbg.de>

```
2009/1/16 Johannes Schindelin <Johannes.Schindelin@gmx.de>:
> Hi,
>
> On Fri, 16 Jan 2009, Santi Béjar wrote:
>
>> 2009/1/16 Johannes Schindelin <Johannes.Schindelin@gmx.de>:
>> >
>> > Note that this affects creating bundles with --all; I contend that it
>> > is a good change to add the HEAD, so that cloning from such a bundle
>> > will give you a current branch.  However, I had to fix t5701 as it
>> > assumed that --all does not imply HEAD.
>>
>> From the description I understand that it only affects when the HEAD is
>> detached, but in t5701 the HEAD is not detached so nothing should be
>> fixed.
>
> The error in t5701 was that it _wanted_ to test a bundle without a HEAD,
> but it actually created it with --all.  That was implying that --all does
> not mean HEAD

Yes, that is the current behaviour.

> , and I disagree with that.

I know you disagree, but in the commit log you said:

---
[PATCH] revision walker: include a detached HEAD in --all

When HEAD is detached, --all should list it, too, logically, as a
detached HEAD is by definition a temporary, unnamed branch.
---

so nothing talks about changing the behaviour when the HEAD is not detached.

But the problem with t5701 is another thing. If you run this:

git init
: >file
git add .
git commit -m1
git bundle create b1.bundle --all HEAD
git ls-remote b1.bundle
git rev-parse --all HEAD

you will see that the same rev-parse parameters in "git bundle"
produce tree lines while with "git rev-parse" only two are produced.


>
>> For gc for sure it is a good thing, but I'm not convinced of the others,
>> as a detached HEAD is a very special thing (temporary and unnamed
>> branch).
>
> So?  What does "--all" mean?  All branches or what? :-)
>
> Seriously, I think that --all should imply HEAD at all times, as the only
> time when it makes a difference is when you have that unnamed _branch_
> that is a detached HEAD.
>
> Maybe I would be more amenable to your criticism if you could come up with
> a scenario where implying HEAD with --all is _wrong_.

I don't think it is plainly wrong. I think both makes sense, but I
think it is not a good idea to change the behaviour now as some
scripts may rely on it.

Santi

```

## Santi Béjar, 2009-01-16 13:50

Subject: Re: [PATCH] revision walker: include a detached HEAD in --all
Message-ID: <adf1fd3d0901160550m200e9478t5755ebdc176f09a2@mail.gmail.com>
URL: https://gitlist.dev/e/adf1fd3d0901160550m200e9478t5755ebdc176f09a2%40mail.gmail.com
In-Reply-To: <adf1fd3d0901160546o50db0594h7377774fed9fef99@mail.gmail.com>

```
2009/1/16 Santi Béjar <santi@agolina.net>:
> 2009/1/16 Johannes Schindelin <Johannes.Schindelin@gmx.de>:
>> Hi,
>>
>> On Fri, 16 Jan 2009, Santi Béjar wrote:
>>
>>> 2009/1/16 Johannes Schindelin <Johannes.Schindelin@gmx.de>:
>>> >
>>> > Note that this affects creating bundles with --all; I contend that it
>>> > is a good change to add the HEAD, so that cloning from such a bundle
>>> > will give you a current branch.  However, I had to fix t5701 as it
>>> > assumed that --all does not imply HEAD.
>>>
>>> From the description I understand that it only affects when the HEAD is
>>> detached, but in t5701 the HEAD is not detached so nothing should be
>>> fixed.
>>
>> The error in t5701 was that it _wanted_ to test a bundle without a HEAD,
>> but it actually created it with --all.  That was implying that --all does
>> not mean HEAD
>
> Yes, that is the current behaviour.
>
>> , and I disagree with that.
>
> I know you disagree, but in the commit log you said:
>
> ---
> [PATCH] revision walker: include a detached HEAD in --all
>
> When HEAD is detached, --all should list it, too, logically, as a
> detached HEAD is by definition a temporary, unnamed branch.
> ---
>
> so nothing talks about changing the behaviour when the HEAD is not detached.
>
> But the problem with t5701 is another thing. If you run this:

>
> git init
> : >file
> git add .
> git commit -m1
> git bundle create b1.bundle --all HEAD
> git ls-remote b1.bundle
> git rev-parse --all HEAD
>
> you will see that the same rev-parse parameters in "git bundle"
> produce tree lines while with "git rev-parse" only two are produced.
>

Sorry, there are two problems with t5701, the one of the changing
behaviour of the --all flag and this one.

Santi

```

## Thomas Rast, 2009-01-18 01:38

Subject: [TOY PATCH] git-resurrect: find traces of a branch name and resurrect it
Message-ID: <1232242703-19086-1-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232242703-19086-1-git-send-email-trast%40student.ethz.ch
In-Reply-To: <alpine.DEB.1.00.0901161213370.3586@pacific.mpi-cbg.de>

```
Add a tool 'git resurrect <branch>...' that tries to find traces of
each <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>
---

So here's a slightly more polished version so gmane can keep it
forever.  Thanks for the sed trick!  I was too lazy to add more
options, but at least there's a "fast" and a "complete" mode.



 Makefile         |    1 +
 git-resurrect.sh |  109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 110 insertions(+), 0 deletions(-)
 create mode 100755 git-resurrect.sh

diff --git a/Makefile b/Makefile
index 2b873fa..87cb539 100644
--- a/Makefile
+++ b/Makefile
@@ -260,6 +260,7 @@ SCRIPT_SH += git-merge-resolve.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-parse-remote.sh
 SCRIPT_SH += git-pull.sh
+SCRIPT_SH += git-resurrect.sh
 SCRIPT_SH += git-quiltimport.sh
 SCRIPT_SH += git-rebase--interactive.sh
 SCRIPT_SH += git-rebase.sh
diff --git a/git-resurrect.sh b/git-resurrect.sh
new file mode 100755
index 0000000..6d5a0c7
--- /dev/null
+++ b/git-resurrect.sh
@@ -0,0 +1,109 @@
+#!/bin/sh
+
+USAGE="git resurrect [-m | --merges] [-n | --dry-run] <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 and merge messages.  With --merges, the history of all refs
+is scanned for merge commit subjects, which is rather slow but allows
+you to resurrect other people's topic branches."
+
+. git-sh-setup
+cd_to_toplevel
+
+OPTIONS_SPEC="\
+git resurrect [-m | --merges] [-n | --dry-run] <name>...
+--
+m,merges             also scan merges (slow)
+n,dry-run            don't recreate the branch"
+
+test "$#" = 0 && usage
+
+eval "$(echo "$OPTIONS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
+
+search_reflog () {
+        sed -n 's~^\([^ ]*\) .*\tcheckout: moving from '"$1"' .*~\1~p' \
+                < .git/logs/HEAD
+}
+
+search_reflog_merges () {
+        sed -n 's~^[^ ]* \([^ ]*\) .*\tmerge '"$1"':~\1~p' \
+                < .git/logs/HEAD
+}
+
+search_merges () {
+	git rev-list --pretty=tformat:"%h %p:%s" --all |
+	grep "Merge branch.*'$branch'.*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
+}
+
+search_merge_targets () {
+	git rev-list --pretty=tformat:"%h %s" --all |
+	grep "Merge branch '[^']*' into $branch$" |
+	cut -d' ' -f1
+}
+
+dry_run=
+scan_merges=
+
+while test "$#" != 0; do
+	case "$1" in
+	    -n|--dry-run)
+		dry_run=t
+		;;
+	    -m|--merges)
+		scan_merges=t
+		;;
+	    --)
+		shift
+		break
+		;;
+	    *)
+		usage
+		;;
+	esac
+	shift
+done
+
+for branch in "$@"; do
+	candidates="$(search_reflog $1; search_reflog_merges $1)"
+	if test ! -z "$scan_merges"; then
+		candidates="$candidates $(search_merges $1; search_merge_targets $1)"
+	fi
+
+	candidates="$(git rev-parse $candidates | sort -u)"
+
+	if test -z "$candidates"; then
+		echo "** No candidates for $branch found **"
+		test -z "$scan_merges" && echo "(maybe try again with -m)"
+	else
+		echo "** Candidates for $branch **"
+		for cmt in $candidates; do
+			git --no-pager log --pretty=oneline --abbrev-commit -1 $cmt
+		done
+
+		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 $branch >/dev/null; then
+			printf "** Restoring $branch to "
+			git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+			git branch $branch $newest
+		else
+			printf "Most recent: "
+			git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+			echo "** $branch already exists, doing nothing"
+		fi
+	fi
+done
-- 
1.6.1.320.gd5dca.dirty

```

## Junio C Hamano, 2009-01-18 06:01

Subject: Re: [PATCH] revision walker: include a detached HEAD in --all
Message-ID: <7v8wp917c3.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7v8wp917c3.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <alpine.DEB.1.00.0901161351460.3586@pacific.mpi-cbg.de>

```
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> When HEAD is detached, --all should list it, too, logically, as a
> detached HEAD is by definition a temporary, unnamed branch.
>
> It is especially necessary to list it when garbage collecting, as
> the detached HEAD would be trashed.
>
> Noticed by Thomas Rast.
>
> Note that this affects creating bundles with --all; I contend that it
> is a good change to add the HEAD, so that cloning from such a bundle
> will give you a current branch.  However, I had to fix t5701 as it
> assumed that --all does not imply HEAD.

Sorry, but I do not understand.

> diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh
> index 8dfaaa4..14413f8 100755
> --- a/t/t5701-clone-local.sh
> +++ b/t/t5701-clone-local.sh
> @@ -11,8 +11,8 @@ test_expect_success 'preparing origin repository' '
>  	git clone --bare . x &&
>  	test "$(GIT_CONFIG=a.git/config git config --bool core.bare)" = true &&
>  	test "$(GIT_CONFIG=x/config git config --bool core.bare)" = true
> -	git bundle create b1.bundle --all HEAD &&
> -	git bundle create b2.bundle --all &&
> +	git bundle create b1.bundle master HEAD &&
> +	git bundle create b2.bundle master &&

Because --all did not imply HEAD, "--all HEAD" used to be the way to say
"everything and HEAD".  Now --all does imply HEAD, but it should still be
a valid way to say "everything, by the way, do not forget HEAD".

Does the first one need to be changed to "master HEAD"?  If "--all HEAD"
makes the rest of the test unhappy because HEAD is listed twice, perhaps
that is an independent bug that needs to be fixed?

For that matter, what does "git bundle create x HEAD HEAD" do?  Does it
list HEAD twice?

```

## Junio C Hamano, 2009-01-18 06:36

Subject: Re: [PATCH] revision walker: include a detached HEAD in --all
Message-ID: <7v3afh15pi.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7v3afh15pi.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <7v8wp917c3.fsf@gitster.siamese.dyndns.org>

```
Junio C Hamano <gitster@pobox.com> writes:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> ...
>> Note that this affects creating bundles with --all; I contend that it
>> is a good change to add the HEAD, so that cloning from such a bundle
>> will give you a current branch.  However, I had to fix t5701 as it
>> assumed that --all does not imply HEAD.
>
> Sorry, but I do not understand.
>
>> diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh
>> index 8dfaaa4..14413f8 100755
>> --- a/t/t5701-clone-local.sh
>> +++ b/t/t5701-clone-local.sh
>> @@ -11,8 +11,8 @@ test_expect_success 'preparing origin repository' '
>>  	git clone --bare . x &&
>>  	test "$(GIT_CONFIG=a.git/config git config --bool core.bare)" = true &&
>>  	test "$(GIT_CONFIG=x/config git config --bool core.bare)" = true
>> -	git bundle create b1.bundle --all HEAD &&
>> -	git bundle create b2.bundle --all &&
>> +	git bundle create b1.bundle master HEAD &&
>> +	git bundle create b2.bundle master &&
>
> Because --all did not imply HEAD, "--all HEAD" used to be the way to say
> "everything and HEAD".  Now --all does imply HEAD, but it should still be
> a valid way to say "everything, by the way, do not forget HEAD".
>
> Does the first one need to be changed to "master HEAD"?  If "--all HEAD"
> makes the rest of the test unhappy because HEAD is listed twice, perhaps
> that is an independent bug that needs to be fixed?
>
> For that matter, what does "git bundle create x HEAD HEAD" do?  Does it
> list HEAD twice?

With a patch like this, I think b1.bundle can be created with "--all HEAD"
as before.

Of course, to advertise that --all now includes HEAD and it is a _good_
thing, we may want to even say "git bundle create b1.bundle --all" in the
above test sequence.

Creation of b2.bundle should say "master" explicitly as in your patch,
because the point of that bundle is to test a use of such HEAD-less bundle
in the later parts of the script.

-- >8 --
Subject: [PATCH] bundle: allow the same ref to be given more than once

"git bundle create x master master" used to create a bundle that lists
the same branch (master) twice.  Cloning from such a bundle resulted in
a needless warning "warning: Duplicated ref: refs/remotes/origin/master".

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 bundle.c |    2 ++
 object.c |   19 +++++++++++++++++++
 object.h |    1 +
 3 files changed, 22 insertions(+), 0 deletions(-)

diff --git a/bundle.c b/bundle.c
index daecd8e..b20f210 100644
--- a/bundle.c
+++ b/bundle.c
@@ -240,6 +240,8 @@ int create_bundle(struct bundle_header *header, const char *path,
 		return error("unrecognized argument: %s'", argv[i]);
 	}
 
+	object_array_remove_duplicates(&revs.pending);
+
 	for (i = 0; i < revs.pending.nr; i++) {
 		struct object_array_entry *e = revs.pending.objects + i;
 		unsigned char sha1[20];
diff --git a/object.c b/object.c
index 50b6528..7e6a92c 100644
--- a/object.c
+++ b/object.c
@@ -268,3 +268,22 @@ void add_object_array_with_mode(struct object *obj, const char *name, struct obj
 	objects[nr].mode = mode;
 	array->nr = ++nr;
 }
+
+void object_array_remove_duplicates(struct object_array *array)
+{
+	int ref, src, dst;
+	struct object_array_entry *objects = array->objects;
+
+	for (ref = 0; ref < array->nr - 1; ref++) {
+		for (src = ref + 1, dst = src;
+		     src < array->nr;
+		     src++) {
+			if (!strcmp(objects[ref].name, objects[src].name))
+				continue;
+			if (src != dst)
+				objects[dst] = objects[src];
+			dst++;
+		}
+		array->nr = dst;
+	}
+}
diff --git a/object.h b/object.h
index 036bd66..3193916 100644
--- a/object.h
+++ b/object.h
@@ -71,5 +71,6 @@ int object_list_contains(struct object_list *list, struct object *obj);
 /* Object array handling .. */
 void add_object_array(struct object *obj, const char *name, struct object_array *array);
 void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode);
+void object_array_remove_duplicates(struct object_array *);
 
 #endif /* OBJECT_H */
-- 
1.6.1.208.g58df

```

## Johannes Schindelin, 2009-01-18 13:42

Subject: Re: [PATCH] revision walker: include a detached HEAD in --all
Message-ID: <alpine.DEB.1.00.0901181440550.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901181440550.3586%40pacific.mpi-cbg.de
In-Reply-To: <7v8wp917c3.fsf@gitster.siamese.dyndns.org>

```
Hi,

On Sat, 17 Jan 2009, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > When HEAD is detached, --all should list it, too, logically, as a
> > detached HEAD is by definition a temporary, unnamed branch.
> >
> > It is especially necessary to list it when garbage collecting, as
> > the detached HEAD would be trashed.
> >
> > Noticed by Thomas Rast.
> >
> > Note that this affects creating bundles with --all; I contend that it
> > is a good change to add the HEAD, so that cloning from such a bundle
> > will give you a current branch.  However, I had to fix t5701 as it
> > assumed that --all does not imply HEAD.
> 
> Sorry, but I do not understand.
> 
> > diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh
> > index 8dfaaa4..14413f8 100755
> > --- a/t/t5701-clone-local.sh
> > +++ b/t/t5701-clone-local.sh
> > @@ -11,8 +11,8 @@ test_expect_success 'preparing origin repository' '
> >  	git clone --bare . x &&
> >  	test "$(GIT_CONFIG=a.git/config git config --bool core.bare)" = true &&
> >  	test "$(GIT_CONFIG=x/config git config --bool core.bare)" = true
> > -	git bundle create b1.bundle --all HEAD &&
> > -	git bundle create b2.bundle --all &&
> > +	git bundle create b1.bundle master HEAD &&
> > +	git bundle create b2.bundle master &&
> 
> Because --all did not imply HEAD, "--all HEAD" used to be the way to say
> "everything and HEAD".  Now --all does imply HEAD, but it should still be
> a valid way to say "everything, by the way, do not forget HEAD".
> 
> Does the first one need to be changed to "master HEAD"?  If "--all HEAD"
> makes the rest of the test unhappy because HEAD is listed twice, perhaps
> that is an independent bug that needs to be fixed?

I changed it away from --all because I am a fan of being explicit.  We 
want a bundle here that has master and HEAD in it.  This being a test 
case, being lazy is so wrong here.  You should describe what you actually 
want, not use a set of parameters that just happens to work (by chance as 
we saw).

Ciao,
Dscho

```

## Johannes Schindelin, 2009-01-18 14:06

Subject: Re: [PATCH] revision walker: include a detached HEAD in --all
Message-ID: <alpine.DEB.1.00.0901181442370.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901181442370.3586%40pacific.mpi-cbg.de
In-Reply-To: <7v3afh15pi.fsf@gitster.siamese.dyndns.org>

```
Hi,

On Sat, 17 Jan 2009, Junio C Hamano wrote:

> Subject: [PATCH] bundle: allow the same ref to be given more than once
> 
> "git bundle create x master master" used to create a bundle that lists
> the same branch (master) twice.  Cloning from such a bundle resulted in
> a needless warning "warning: Duplicated ref: refs/remotes/origin/master".
> 
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  bundle.c |    2 ++
>  object.c |   19 +++++++++++++++++++
>  object.h |    1 +
>  3 files changed, 22 insertions(+), 0 deletions(-)

Yes, that would be good.  You have my ACK on that if you want.

Another thing I am thinking about on and off:

You cannot really use bundles as a replacement for regular transports 
(e.g. when you administrator does not let you ssh or git:// out, and you 
do not have an HTTP server available [*1*]).

Suppose you have two branches, 'master' and 'side'.  Now you make changes 
to 'master' and send the complete repository as a bundle to your friend.  
Now you delete the branch 'side', and send the next bundle (created with 
--all implying HEAD, and --since=$(stat -c %Y <first-bundle>)).

Then your friend has no idea if 'side' was deleted or untouched.

So I think we'd need some option for "create bundle" to list all specified 
refs, and if they have not really changed, their SHA-1s as prerequisites, 
too.  Maybe "--full-bundle", or just "--full"?

Another problem: suppose you have a branch, called 'private', that you 
excluded from your bundles.  Now you happened to make changes to it, and 
by mistake, the branch gets included in the incremental bundle.  No 
problem for you, as your friend lacks the prerequisites to reconstruct it.

But unfortunately, your friend cannot even pull 'master' from it, because 
of our overzealous verification process which refuses all fetches when 
some prerequisites are missing locally, even if they are not even needed.

This problem is much harder to solve, I think, and maybe we just want to 
leave it: as bundles are just fed into index-pack --fix-thin, which has no 
idea what objects can be skipped.  Maybe there is no clean solution to 
that to begin with.

Ciao,
Dscho

[*1*] When people are stuck behind such a stupidly restrictive firewall, 
often people come with "helpful" suggestions to use a VPN, or to publish 
your private repository, or get an external machine to HTTP proxy their 
connections.  I find it outright mean to waste the time of people who 
already have a big problem.

However, I believe that a mail based bundle exchange should be a 
relatively easy way out for those situations, once it works.

```

## Johannes Schindelin, 2009-01-18 16:19

Subject: Re: [TOY PATCH] git-resurrect: find traces of a branch name and resurrect it
Message-ID: <alpine.DEB.1.00.0901181718370.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901181718370.3586%40pacific.mpi-cbg.de
In-Reply-To: <1232242703-19086-1-git-send-email-trast@student.ethz.ch>

```
Hi,

On Sun, 18 Jan 2009, Thomas Rast wrote:

>  Makefile         |    1 +
>  git-resurrect.sh |  109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++

Maybe have it in contrib/ instead?

Ciao,
Dscho

```

## Thomas Rast, 2009-01-20 09:01

Subject: Re: [TOY PATCH] git-resurrect: find traces of a branch name and resurrect it
Message-ID: <200901201001.54979.trast@student.ethz.ch>
URL: https://gitlist.dev/e/200901201001.54979.trast%40student.ethz.ch
In-Reply-To: <alpine.DEB.1.00.0901181718370.3586@pacific.mpi-cbg.de>

```
[Sorry for missing this message, it seems KMail4 does have some rather
annoying filtering bugs...]

Johannes Schindelin wrote:
> On Sun, 18 Jan 2009, Thomas Rast wrote:
> 
> >  Makefile         |    1 +
> >  git-resurrect.sh |  109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 
> Maybe have it in contrib/ instead?

It was really intended as a toy patch, but if people find it useful
(Boyd?) I can add the rest of the options so that all searches can be
chosen independently, and shape it as a "real" contrib patch.

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

```

## Boyd Stephen Smith Jr., 2009-01-20 16:57

Subject: Re: [TOY PATCH] git-resurrect: find traces of a branch name and resurrect it
Message-ID: <200901201057.18127.bss@iguanasuicide.net>
URL: https://gitlist.dev/e/200901201057.18127.bss%40iguanasuicide.net
In-Reply-To: <200901201001.54979.trast@student.ethz.ch>

```
On Tuesday 2009 January 20 03:01:50 Thomas Rast wrote:
>It was really intended as a toy patch, but if people find it useful
>(Boyd?) I can add the rest of the options so that all searches can be
>chosen independently, and shape it as a "real" contrib patch.

I'll test it out later today and get back to you.

[OT]
I actually prefer Stephen; My father is Boyd.
-- 
Boyd Stephen Smith Jr.                     ,= ,-_-. =. 
bss@iguanasuicide.net                     ((_/)o o(\_))
ICQ: 514984 YM/AIM: DaTwinkDaddy           `-'(. .)`-' 
http://iguanasuicide.net/                      \_/     

```

## Boyd Stephen Smith Jr., 2009-01-20 20:50

Subject: Re: [TOY PATCH] git-resurrect: find traces of a branch name and resurrect it
Message-ID: <200901201450.53450.bss@iguanasuicide.net>
URL: https://gitlist.dev/e/200901201450.53450.bss%40iguanasuicide.net
In-Reply-To: <200901201057.18127.bss@iguanasuicide.net>

```
On Tuesday 2009 January 20 10:57:17 Boyd Stephen Smith Jr. wrote:
>On Tuesday 2009 January 20 03:01:50 Thomas Rast wrote:
>>It was really intended as a toy patch, but if people find it useful
>>(Boyd?) I can add the rest of the options so that all searches can be
>>chosen independently, and shape it as a "real" contrib patch.
>
>I'll test it out later today and get back to you.

In my particular case, it wasn't useful without the -m option, but I 
understand why it is not the default.

I think it could be quite nice; "undelete"-type commands are generally 
well-received by users and when run against reflogs alone, that's what the 
command is.

It's useful enough to me that I'd love to see it mainlined.
-- 
Boyd Stephen Smith Jr.                     ,= ,-_-. =. 
bss@iguanasuicide.net                     ((_/)o o(\_))
ICQ: 514984 YM/AIM: DaTwinkDaddy           `-'(. .)`-' 
http://iguanasuicide.net/                      \_/     

```

## Thomas Rast, 2009-01-23 20:03

Subject: [PATCH] contrib git-resurrect: find traces of a branch name and resurrect it
Message-ID: <1232740985-4551-1-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232740985-4551-1-git-send-email-trast%40student.ethz.ch
In-Reply-To: <200901201450.53450.bss@iguanasuicide.net>

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

Boyd Stephen Smith Jr. wrote:
> I think it could be quite nice; "undelete"-type commands are generally 
> well-received by users and when run against reflogs alone, that's what the 
> command is.
> 
> It's useful enough to me that I'd love to see it mainlined.

So here's a version for contrib with more options and some other
tweaks.

I removed the ability to "batch resurrect" with several <name>
arguments since that would have conflicted with -b <newname>, but
otherwise the features are the same.

> In my particular case, it wasn't useful without the -m option, but I 
> understand why it is not the default.

Aside from the obvious speed reasons, I don't really want to teach
people that commits "know" the branch they were on.  It is a pure
coincidence if you can resurrect a topic branch from merge messages;
an equivalent merge could have gone through as a fast-forward, and
you'd never know.



 contrib/git-resurrect.sh |  140 ++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 140 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..29bf723
--- /dev/null
+++ b/contrib/git-resurrect.sh
@@ -0,0 +1,140 @@
+#!/bin/sh
+
+USAGE="[-h] [-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 [-h] [-r] [-m] [-t] [-n] [-b <newname>] <name>
+--
+b,branch=            save branch as <newname> instead of <name>
+H,try-hard           same as -r -m -t
+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
+cd_to_toplevel
+
+search_reflog () {
+        sed -n 's~^\([^ ]*\) .*\tcheckout: moving from '"$1"' .*~\1~p' \
+                < .git/logs/HEAD
+}
+
+search_reflog_merges () {
+        sed -n 's~^[^ ]* \([^ ]*\) .*\tmerge '"$1"':~\1~p' \
+                < .git/logs/HEAD
+}
+
+search_merges () {
+	git rev-list --pretty=tformat:"%h %p:%s" --all |
+	grep "Merge branch.*'$branch'.*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
+}
+
+search_merge_targets () {
+	git rev-list --pretty=tformat:"%h %s" --all |
+	grep "Merge branch '[^']*' into $branch$" |
+	cut -d' ' -f1
+}
+
+dry_run=
+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
+		;;
+	    -m|--merges)
+		scan_merges=t
+		;;
+	    -r|--reflog_merges)
+		scan_reflog_merges=t
+		;;
+	    -t|--merge-targets)
+		scan_merge_targets=t
+		;;
+	    -H|--try-hard)
+		scan_reflog_merges=t
+		scan_merges=t
+		scan_merge_targets=t
+		;;
+	    --)
+		shift
+		break
+		;;
+	    *)
+		usage
+		;;
+	esac
+	shift
+done
+
+test "$#" = 1 || usage
+
+branch="$1"
+test -z "$new_name" && new_name="$branch"q
+
+candidates="$(search_reflog $1)"
+if test ! -z "$scan_reflog_merges"; then
+	candidates="$candidates $(search_reflog_merges $1)"
+fi
+if test ! -z "$scan_merges"; then
+	candidates="$candidates $(search_merges $1)"
+fi
+if test ! -z "$scan_merge_targets"; then
+	candidates="$candidates $(search_merge_targets $1)"
+fi
+
+candidates="$(git rev-parse $candidates | sort -u)"
+
+if test -z "$candidates"; then
+	hint=
+	test "z$scan_merges$scan_reflog_merges$scan_merge_targets" != "zttt" \
+		&& hint="(maybe try again with -H)"
+	die "no candidates for $branch found" $hint
+fi
+
+echo "** Candidates for $branch **"
+for cmt in $candidates; do
+	git --no-pager log --pretty=oneline --abbrev-commit -1 $cmt
+done
+
+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.447.gbdf1d

```

## Boyd Stephen Smith Jr., 2009-01-23 21:00

Subject: Re: [PATCH] contrib git-resurrect: find traces of a branch name and resurrect it
Message-ID: <200901231500.23182.bss@iguanasuicide.net>
URL: https://gitlist.dev/e/200901231500.23182.bss%40iguanasuicide.net
In-Reply-To: <1232740985-4551-1-git-send-email-trast@student.ethz.ch>

```
On Friday 2009 January 23 14:03:05 Thomas Rast wrote:
>Boyd Stephen Smith Jr. wrote:
>> I think it could be quite nice; "undelete"-type commands are generally
>> well-received by users and when run against reflogs alone, that's what the
>> command is.
>>
>> It's useful enough to me that I'd love to see it mainlined.
>
>So here's a version for contrib with more options and some other
>tweaks.

I wanted/needed the ability to ignore reflogs entirely.  Use went something 
like this:
1. resurrect branch from origin/pu
2. add patches, mail to list
3. # wait 24 hours
4. pull, see from logs that branch was modified, but not just my changes (or 
without all of my changes).
5. delete local branch
6. Try to resurrect branch from origin/pu, get local version I just deleted.
7. delete reflog for that branch
8. Try to resurrect branch from origin/pu, get local version I merged into 
master at some point.
9. Add new option.

So, I added a couple of options locally: --only-merges, so it would only look 
at the first line of commit logs, ignoring my local reflogs entirely; 
and --revisions, to specify arguments to pass to rev-list so it wouldn't even 
see my local merges (I passed 'origin/pu origin/next').

Yeah, my usage might be abusage, but it worked for me. :)

Would you object to a patch adding a --reflog option and allowing each of the 
scan options to be negated?

>I removed the ability to "batch resurrect" with several <name>
>arguments since that would have conflicted with -b <newname>, but
>otherwise the features are the same.

In my local version, which I was going to try and clean up over the weekend, I 
was going to support both, by borrowing refspec syntax from fetch/push.  
Specifically.  Resurrecting 'js/notes' as 'pu/js/notes' would look like:
git-resurrect -H js/notes:pu/js/notes

Would you object to a patch that dropped -b in favor of the refspec syntax?

>> In my particular case, it wasn't useful without the -m option, but I
>> understand why it is not the default.
>
>Aside from the obvious speed reasons, I don't really want to teach
>people that commits "know" the branch they were on.  It is a pure
>coincidence if you can resurrect a topic branch from merge messages;
>an equivalent merge could have gone through as a fast-forward, and
>you'd never know.

Yeah, agreed.  I made this more clear in my local version by changing the 
documentation from "scan for merges" to "scan first line of commit messages 
for possible merges".  It's more wordy, but it make it clear that it is 
dependent on the message, and it's not tracked outside of that.

I also tend to merge topic branches with --no-ff so that I do get the merge 
message, so it has a better chance of working against my repository.  (I also 
enjoy octopus merging when possible so the history indicates the patch sets 
are separable, but maybe I'm just a little "touched" and haven't been bitten 
by by an octopus yet.[1])

Not directly related to any issue you bring up:

There seems to be some needless redundancy between USAGE and OPTIONS_SPEC.

Would you object to a patch that used $USAGE inside OPTIONS_SPEC?
-- 
Boyd Stephen Smith Jr.                     ,= ,-_-. =. 
bss@iguanasuicide.net                     ((_/)o o(\_))
ICQ: 514984 YM/AIM: DaTwinkDaddy           `-'(. .)`-' 
http://iguanasuicide.net/                      \_/     

[1] I hear they are even more feral than penguins.

```

## Thomas Rast, 2009-01-26 11:54

Subject: Re: [PATCH] contrib git-resurrect: find traces of a branch name and resurrect it
Message-ID: <200901261254.39360.trast@student.ethz.ch>
URL: https://gitlist.dev/e/200901261254.39360.trast%40student.ethz.ch
In-Reply-To: <200901231500.23182.bss@iguanasuicide.net>

```
Hi Stephen,

Sorry for the long delay.  I'm going to roll a v2 with two small
fixes.  After that it's all yours ;-)

Boyd Stephen Smith Jr. wrote:
[...]
> 6. Try to resurrect branch from origin/pu, get local version I just deleted.
> 7. delete reflog for that branch
> 8. Try to resurrect branch from origin/pu, get local version I merged into 
> master at some point.
> 9. Add new option.
> 
> So, I added a couple of options locally: --only-merges, so it would only look 
> at the first line of commit logs, ignoring my local reflogs entirely; 
> and --revisions, to specify arguments to pass to rev-list so it wouldn't even 
> see my local merges (I passed 'origin/pu origin/next').
> 
> Yeah, my usage might be abusage, but it worked for me. :)

I'm fine with adding such an option, but I still wonder what was wrong
with the original scheme of asking 'git rev-list -1' for the newest
commit.  I thought rev-list always listed by date, so that command
should always pick the newest candidate commit from all candidates
selected.  Do you have an example where that breaks?  Or did you just
have a use-case in which you wanted something other than the newest
candidate?

> In my local version, which I was going to try and clean up over the weekend, I 
> was going to support both, by borrowing refspec syntax from fetch/push.  
> Specifically.  Resurrecting 'js/notes' as 'pu/js/notes' would look like:
> git-resurrect -H js/notes:pu/js/notes
> 
> Would you object to a patch that dropped -b in favor of the refspec syntax?

No, that would be fine by me.

> There seems to be some needless redundancy between USAGE and OPTIONS_SPEC.
> 
> Would you object to a patch that used $USAGE inside OPTIONS_SPEC?

Also a good idea.

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

```

## Thomas Rast, 2009-01-26 12:40

Subject: [PATCH v2] contrib git-resurrect: find traces of a branch name and resurrect it
Message-ID: <1232973657-31444-1-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232973657-31444-1-git-send-email-trast%40student.ethz.ch
In-Reply-To: <200901261254.39360.trast@student.ethz.ch>

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

Fixed the -h to upper-case in the short options summaries, and removed
a stray 'q' in the default assignment of new_name.


 contrib/git-resurrect.sh |  140 ++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 140 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..3c1c946
--- /dev/null
+++ b/contrib/git-resurrect.sh
@@ -0,0 +1,140 @@
+#!/bin/sh
+
+USAGE="[-H] [-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 [-H] [-r] [-m] [-t] [-n] [-b <newname>] <name>
+--
+b,branch=            save branch as <newname> instead of <name>
+H,try-hard           same as -r -m -t
+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
+cd_to_toplevel
+
+search_reflog () {
+        sed -n 's~^\([^ ]*\) .*\tcheckout: moving from '"$1"' .*~\1~p' \
+                < .git/logs/HEAD
+}
+
+search_reflog_merges () {
+        sed -n 's~^[^ ]* \([^ ]*\) .*\tmerge '"$1"':~\1~p' \
+                < .git/logs/HEAD
+}
+
+search_merges () {
+	git rev-list --pretty=tformat:"%h %p:%s" --all |
+	grep "Merge branch.*'$branch'.*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
+}
+
+search_merge_targets () {
+	git rev-list --pretty=tformat:"%h %s" --all |
+	grep "Merge branch '[^']*' into $branch$" |
+	cut -d' ' -f1
+}
+
+dry_run=
+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
+		;;
+	    -m|--merges)
+		scan_merges=t
+		;;
+	    -r|--reflog_merges)
+		scan_reflog_merges=t
+		;;
+	    -t|--merge-targets)
+		scan_merge_targets=t
+		;;
+	    -H|--try-hard)
+		scan_reflog_merges=t
+		scan_merges=t
+		scan_merge_targets=t
+		;;
+	    --)
+		shift
+		break
+		;;
+	    *)
+		usage
+		;;
+	esac
+	shift
+done
+
+test "$#" = 1 || usage
+
+branch="$1"
+test -z "$new_name" && new_name="$branch"
+
+candidates="$(search_reflog $1)"
+if test ! -z "$scan_reflog_merges"; then
+	candidates="$candidates $(search_reflog_merges $1)"
+fi
+if test ! -z "$scan_merges"; then
+	candidates="$candidates $(search_merges $1)"
+fi
+if test ! -z "$scan_merge_targets"; then
+	candidates="$candidates $(search_merge_targets $1)"
+fi
+
+candidates="$(git rev-parse $candidates | sort -u)"
+
+if test -z "$candidates"; then
+	hint=
+	test "z$scan_merges$scan_reflog_merges$scan_merge_targets" != "zttt" \
+		&& hint="(maybe try again with -H)"
+	die "no candidates for $branch found" $hint
+fi
+
+echo "** Candidates for $branch **"
+for cmt in $candidates; do
+	git --no-pager log --pretty=oneline --abbrev-commit -1 $cmt
+done
+
+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.469.g6f3d5

```

## Junio C Hamano, 2009-01-27 06:31

Subject: Re: [PATCH v2] contrib git-resurrect: find traces of a branch name and resurrect it
Message-ID: <7vwschz2dc.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vwschz2dc.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <1232973657-31444-1-git-send-email-trast@student.ethz.ch>

```
Thomas Rast <trast@student.ethz.ch> 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 <trast@student.ethz.ch>
> ---
>
> 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"

```

## Thomas Rast, 2009-01-30 22:52

Subject: Re: [PATCH v2] contrib git-resurrect: find traces of a branch name and resurrect it
Message-ID: <200901302352.45247.trast@student.ethz.ch>
URL: https://gitlist.dev/e/200901302352.45247.trast%40student.ethz.ch
In-Reply-To: <7vwschz2dc.fsf@gitster.siamese.dyndns.org>

```
Junio C Hamano wrote:
> Thomas Rast <trast@student.ethz.ch> writes:
> 
> > 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

```

## Thomas Rast, 2009-02-01 21:34

Subject: [PATCH v3] contrib git-resurrect: find traces of a branch name and resurrect it
Message-ID: <1233524085-25342-1-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1233524085-25342-1-git-send-email-trast%40student.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...

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

```

## Junio C Hamano, 2009-02-02 02:31

Subject: Re: [PATCH v3] contrib git-resurrect: find traces of a branch name and resurrect it
Message-ID: <7vljsppo14.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vljsppo14.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <1233524085-25342-1-git-send-email-trast@student.ethz.ch>

```
Thomas Rast <trast@student.ethz.ch> writes:

>> 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).

Do you mean something like this?

	sed -n -e "/^$_x40 $_x40 \($_x40\) Merge .*/ {
		s//\1/p
                q
        }"

```

## Thomas Rast, 2009-02-04 10:04

Subject: [PATCH v4] contrib git-resurrect: find traces of a branch name and resurrect it
Message-ID: <1233741858-13099-1-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1233741858-13099-1-git-send-email-trast%40student.ethz.ch
In-Reply-To: <7vljsppo14.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:
> Do you mean something like this?
> 
> 	sed -n -e "/^$_x40 $_x40 \($_x40\) Merge .*/ {
> 		s//\1/p
>                 q
>         }"

Yep, precisely.  Thanks!  This indeed gives it a nice speed boost if
all you want is a topic "resurrection" from pu.



 contrib/git-resurrect.sh |  180 ++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 180 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..c364dda
--- /dev/null
+++ b/contrib/git-resurrect.sh
@@ -0,0 +1,180 @@
+#!/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
+k,keep-going         full rev-list scan (instead of first match)
+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 "/^$_x40 \($_x40\) Merge .*/ {s//\1/p;$early_exit}"
+}
+
+search_merge_targets () {
+	git rev-list --all --grep="Merge branch '[^']*' into $branch\$" \
+		--pretty=tformat:"%H %s" --all |
+	sed -ne "/^\($_x40\) Merge .*/ {s//\1/p;$early_exit} "
+}
+
+dry_run=
+early_exit=q
+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=
+		;;
+	    -k|--keep-going)
+		early_exit=
+		;;
+	    --no-keep-going)
+		early_exit=q
+		;;
+	    -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.530.gdaa1c

```

## Junio C Hamano, 2009-02-05 08:38

Subject: Re: [PATCH v4] contrib git-resurrect: find traces of a branch name and resurrect it
Message-ID: <7vtz79ffd6.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vtz79ffd6.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <1233741858-13099-1-git-send-email-trast@student.ethz.ch>

```
Thomas Rast <trast@student.ethz.ch> writes:

> Junio C Hamano wrote:
>> Do you mean something like this?
>> 
>> 	sed -n -e "/^$_x40 $_x40 \($_x40\) Merge .*/ {
>> 		s//\1/p
>>                 q
>>         }"
>
> Yep, precisely.  Thanks!  This indeed gives it a nice speed boost if
> all you want is a topic "resurrection" from pu.
> ...
> +search_merges () {
> +        git rev-list --all --grep="Merge branch '$1'" \
> +                --pretty=tformat:"%P %s" |
> +        sed -ne "/^$_x40 \($_x40\) Merge .*/ {s//\1/p;$early_exit}"
> +}

Will apply, but just to let you know, I wrote my example on separate lines
(and with separate -n and -e options for that matter) for a reason.  I
recall some implementation of sed (perhaps older BSDs, but don't quote me
on that) did not understanding semicolon with close brace on the same
line.  It may not be a problem in practice these days, but I do not have
access to many different platforms to check as I used to.

```
