# [PATCH/RFC v3 5/6] sha1_name: implement @{-N} syntax for N-th last checked out

56 messages from 2009-01-17 to 2009-01-25. Participants: Thomas Rast, Johannes Schindelin, Junio C Hamano.
Thread: https://gitlist.dev/t/17221

## Thomas Rast, 2009-01-17 03:30

Subject: [PATCH/RFC v3 0/6] N-th last checked out branch
Message-ID: <1232163011-20088-1-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232163011-20088-1-git-send-email-trast%40student.ethz.ch
In-Reply-To: <7v8wpcs38c.fsf@gitster.siamese.dyndns.org>

```
Junio C Hamano <gitster@pobox.com> writes:
> "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.

True.

> Another reason is the one level limitation.

It shares that limitation with my mind ;-)

But well, since you all seem to want a more general solution, here's a
draft.

I started poking around in refs.c for the backwards search, but the
two reflog functions had a lot of functionality implemented very
differently.  So 1-4 are just cleanup and adding that backwards
iterator.  Note that 3/6 changes for_each_reflog_ent() to die() when
it finds a format error, unlike the previous behaviour.  No tests
seemed to care, but maybe someone out there relies on having broken
reflogs? ;-)

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

So this might be settled.

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

I don't handle this, but I'm not sure how much of a problem it is.  It
can correctly detach and re-attach even in cases such as HEAD^.

Ok, now that you mention it, it will probably put you on the parent of
whatever you happened to be on, instead of the earlier value of HEAD^.

>      $ git checkout @{-1}

One problem with the syntax is that the assumption that there can only
be a single @{} construct is quite entrenched in sha1_name.c.  So if
we want to support @{-1}@{1}, that'll need some extra work.

@{-1}~2 and similar work fine though.


Other things of note:

I changed the semantics to really only look at checkouts that "did
something".  If you keep saying 'git checkout master' over and over,
those will not count towards the N.

I changed the parser to read the 'checkout: moving from $old' instead
of the 'to $new'.  The above semantics introduced too many extra
conditions for my taste if we wanted to support the pre-1.5.3 reflog
comment format.  (You'd have to check if the $to was changed from the
last [newer] entry, but then there's another border case when you do
'git init; git checkout -b side; git checkout -' because you never
moved to master.)

The @{date} syntax will be marginally slower after the refactoring
because the old parser carefully avoided parsing numbers where it
could.  (I guess it could actually be done as a bisection for an extra
order of magnitude.)

It's far too early here to be sending mail.

Interactive rebase needs a "move hunk" feature.


Thomas Rast (6):
  reflog: refactor parsing and checking
  reflog: refactor log open+mmap
  reflog: make for_each_reflog_ent use mmap
  reflog: add backwards iterator
  sha1_name: implement @{-N} syntax for N-th last checked out
  checkout: implement '@{-N}' and '-' special abbreviations

 Documentation/git-checkout.txt  |    4 +
 Documentation/git-rev-parse.txt |    3 +
 builtin-checkout.c              |   15 ++-
 cache.h                         |    1 +
 refs.c                          |  285 +++++++++++++++++++++++----------------
 refs.h                          |    1 +
 sha1_name.c                     |   79 +++++++++++-
 t/t1505-rev-parse-last.sh       |   71 ++++++++++
 t/t2012-checkout-last.sh        |   50 +++++++
 9 files changed, 387 insertions(+), 122 deletions(-)
 create mode 100755 t/t1505-rev-parse-last.sh
 create mode 100755 t/t2012-checkout-last.sh

```

## Thomas Rast, 2009-01-17 03:30

Subject: [PATCH/RFC v3 1/6] reflog: refactor parsing and checking
Message-ID: <1232163011-20088-2-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232163011-20088-2-git-send-email-trast%40student.ethz.ch
In-Reply-To: <1232163011-20088-1-git-send-email-trast@student.ethz.ch>

```
read_ref_at() and for_each_reflog_ent() both had parsing and error
checking routines.  Refactor into a separate function that fully
parses a single entry.  Note that this switches for_each_reflog_ent()
from silently ignoring errors to die().

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 refs.c |  197 ++++++++++++++++++++++++++++++++--------------------------------
 1 files changed, 98 insertions(+), 99 deletions(-)

diff --git a/refs.c b/refs.c
index 33ced65..4571fac 100644
--- a/refs.c
+++ b/refs.c
@@ -1337,24 +1337,68 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
 	return 0;
 }
 
-static char *ref_msg(const char *line, const char *endp)
+/*
+ * Check and parse a single reflog line.  Assumes that there is only
+ * one newline in the range buf[0]..buf[len-1] (but does check that it
+ * is at buf[len-1]).
+ */
+static void parse_reflog_line(const char *buf, int len,
+			      unsigned char *osha1, unsigned char *nsha1,
+			      char **email,
+			      unsigned long *timestamp, int *tz,
+			      char **message,
+			      const char *logname)
 {
-	const char *ep;
-	line += 82;
-	ep = memchr(line, '\n', endp - line);
-	if (!ep)
-		ep = endp;
-	return xmemdupz(line, ep - line);
+	static char *retbuf = NULL;
+	static int retbufsz = 0;
+	char *tzstr, *email_end;
+
+	if (len < 83 || buf[len-1] != '\n')
+		die("Log %s is corrupt (entry too short or unterminated).", logname);
+
+	if (get_sha1_hex(buf, osha1) || buf[40] != ' ')
+		die("Log %s is corrupt (malformed old sha1).", logname);
+
+	if (get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ')
+		die("Log %s is corrupt (malformed new sha1).", logname);
+
+	ALLOC_GROW(retbuf, len-82+1, retbufsz);
+	memcpy(retbuf, buf+82, len-82);
+	retbuf[len-82] = '\0';
+
+	email_end = strchr(retbuf, '>');
+	if (!email_end || email_end[1] != ' ')
+		die("Log %s is corrupt (malformed email field).", logname);
+
+	*email = retbuf;
+	email_end[1] = '\0';
+
+	*timestamp = strtoul(email_end + 2, &tzstr, 10);
+	if (!(*timestamp) || !tzstr || tzstr[0] != ' ' ||
+	    (tzstr[1] != '+' && tzstr[1] != '-') ||
+	    !isdigit(tzstr[2]) || !isdigit(tzstr[3]) ||
+	    !isdigit(tzstr[4]) || !isdigit(tzstr[5]))
+		die("Log %s is corrupt (malformed timezone).", logname);
+	if (!(tzstr[6] == '\t' || tzstr[6] == '\n'))
+		die("Log %s is corrupt (bad message field separator).", logname);
+	*tz = strtoul(tzstr, NULL, 10);
+
+	if (tzstr[6] == '\t')
+		*message = tzstr+7;
+	else
+		*message = tzstr+6;
 }
 
 int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
 {
-	const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
-	char *tz_c;
+	const char *logfile, *logdata, *logend, *rec, *start;
+	char *email, *message;
 	int logfd, tz, reccnt = 0;
 	struct stat st;
 	unsigned long date;
-	unsigned char logged_sha1[20];
+	unsigned char new_sha1[20];
+	unsigned char old_sha1[20];
+	unsigned char next_sha1[20];
 	void *log_mapped;
 	size_t mapsz;
 
@@ -1370,86 +1414,55 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
 	logdata = log_mapped;
 	close(logfd);
 
-	lastrec = NULL;
 	rec = logend = logdata + st.st_size;
+	if (logdata < rec && *(rec-1) == '\n')
+		rec--;
 	while (logdata < rec) {
-		reccnt++;
-		if (logdata < rec && *(rec-1) == '\n')
-			rec--;
-		lastgt = NULL;
-		while (logdata < rec && *(rec-1) != '\n') {
-			rec--;
-			if (*rec == '>')
-				lastgt = rec;
-		}
-		if (!lastgt)
-			die("Log %s is corrupt.", logfile);
-		date = strtoul(lastgt + 1, &tz_c, 10);
+		start = memrchr(logdata, '\n', rec-logdata);
+		if (start)
+			start++;
+		else
+			start = logdata;
+		parse_reflog_line(start, rec-start+1,
+				  old_sha1, new_sha1,
+				  &email, &date, &tz, &message,
+				  logfile);
+
+		if (cutoff_time)
+			*cutoff_time = date;
+		if (cutoff_tz)
+			*cutoff_tz = tz;
+		if (cutoff_cnt)
+			*cutoff_cnt = reccnt;
+		if (msg)
+			*msg = message;
+
 		if (date <= at_time || cnt == 0) {
-			tz = strtoul(tz_c, NULL, 10);
-			if (msg)
-				*msg = ref_msg(rec, logend);
-			if (cutoff_time)
-				*cutoff_time = date;
-			if (cutoff_tz)
-				*cutoff_tz = tz;
-			if (cutoff_cnt)
-				*cutoff_cnt = reccnt - 1;
-			if (lastrec) {
-				if (get_sha1_hex(lastrec, logged_sha1))
-					die("Log %s is corrupt.", logfile);
-				if (get_sha1_hex(rec + 41, sha1))
-					die("Log %s is corrupt.", logfile);
-				if (hashcmp(logged_sha1, sha1)) {
-					fprintf(stderr,
-						"warning: Log %s has gap after %s.\n",
-						logfile, show_date(date, tz, DATE_RFC2822));
-				}
-			}
-			else if (date == at_time) {
-				if (get_sha1_hex(rec + 41, sha1))
-					die("Log %s is corrupt.", logfile);
-			}
-			else {
-				if (get_sha1_hex(rec + 41, logged_sha1))
-					die("Log %s is corrupt.", logfile);
-				if (hashcmp(logged_sha1, sha1)) {
-					fprintf(stderr,
-						"warning: Log %s unexpectedly ended on %s.\n",
-						logfile, show_date(date, tz, DATE_RFC2822));
-				}
-			}
+			if (reccnt && hashcmp(new_sha1, next_sha1))
+				fprintf(stderr,
+					"warning: Log %s has gap after %s.\n",
+					logfile, show_date(date, tz, DATE_RFC2822));
+			if (!reccnt && date < at_time && hashcmp(new_sha1, next_sha1))
+				fprintf(stderr,
+					"warning: Log %s unexpectedly ended on %s.\n",
+					logfile, show_date(date, tz, DATE_RFC2822));
+				/* leave caller's sha1 untouched */
+			else
+				hashcpy(sha1, new_sha1);
 			munmap(log_mapped, mapsz);
 			return 0;
 		}
-		lastrec = rec;
+
+		hashcpy(next_sha1, old_sha1);
+		rec = start-1;
 		if (cnt > 0)
 			cnt--;
+		reccnt++;
 	}
 
-	rec = logdata;
-	while (rec < logend && *rec != '>' && *rec != '\n')
-		rec++;
-	if (rec == logend || *rec == '\n')
-		die("Log %s is corrupt.", logfile);
-	date = strtoul(rec + 1, &tz_c, 10);
-	tz = strtoul(tz_c, NULL, 10);
-	if (get_sha1_hex(logdata, sha1))
-		die("Log %s is corrupt.", logfile);
-	if (is_null_sha1(sha1)) {
-		if (get_sha1_hex(logdata + 41, sha1))
-			die("Log %s is corrupt.", logfile);
-	}
-	if (msg)
-		*msg = ref_msg(logdata, logend);
-	munmap(log_mapped, mapsz);
+	hashcpy(sha1, new_sha1);
 
-	if (cutoff_time)
-		*cutoff_time = date;
-	if (cutoff_tz)
-		*cutoff_tz = tz;
-	if (cutoff_cnt)
-		*cutoff_cnt = reccnt;
+	munmap(log_mapped, mapsz);
 	return 1;
 }
 
@@ -1466,30 +1479,16 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
 		return -1;
 	while (fgets(buf, sizeof(buf), logfp)) {
 		unsigned char osha1[20], nsha1[20];
-		char *email_end, *message;
+		char *email, *message;
 		unsigned long timestamp;
 		int len, tz;
 
-		/* old SP new SP name <email> SP time TAB msg LF */
 		len = strlen(buf);
-		if (len < 83 || buf[len-1] != '\n' ||
-		    get_sha1_hex(buf, osha1) || buf[40] != ' ' ||
-		    get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ' ||
-		    !(email_end = strchr(buf + 82, '>')) ||
-		    email_end[1] != ' ' ||
-		    !(timestamp = strtoul(email_end + 2, &message, 10)) ||
-		    !message || message[0] != ' ' ||
-		    (message[1] != '+' && message[1] != '-') ||
-		    !isdigit(message[2]) || !isdigit(message[3]) ||
-		    !isdigit(message[4]) || !isdigit(message[5]))
-			continue; /* corrupt? */
-		email_end[1] = '\0';
-		tz = strtol(message + 1, NULL, 10);
-		if (message[6] != '\t')
-			message += 6;
-		else
-			message += 7;
-		ret = fn(osha1, nsha1, buf+82, timestamp, tz, message, cb_data);
+		parse_reflog_line(buf, len,
+				  osha1, nsha1,
+				  &email, &timestamp, &tz, &message,
+				  logfile);
+		ret = fn(osha1, nsha1, email, timestamp, tz, message, cb_data);
 		if (ret)
 			break;
 	}
-- 
1.6.1.315.g92577

```

## Thomas Rast, 2009-01-17 03:30

Subject: [PATCH/RFC v3 2/6] reflog: refactor log open+mmap
Message-ID: <1232163011-20088-3-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232163011-20088-3-git-send-email-trast%40student.ethz.ch
In-Reply-To: <1232163011-20088-2-git-send-email-trast@student.ethz.ch>

```
Move the open+mmap code from read_ref_at() to a separate function for
the next patch.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 refs.c |   54 ++++++++++++++++++++++++++++++++----------------------
 1 files changed, 32 insertions(+), 22 deletions(-)

diff --git a/refs.c b/refs.c
index 4571fac..0a57896 100644
--- a/refs.c
+++ b/refs.c
@@ -1389,12 +1389,31 @@ static void parse_reflog_line(const char *buf, int len,
 		*message = tzstr+6;
 }
 
+static char *open_reflog(const char *ref, size_t *mapsz, const char **logfile)
+{
+	struct stat st;
+	int logfd;
+	char *map;
+
+	*logfile = git_path("logs/%s", ref);
+	logfd = open(*logfile, O_RDONLY, 0);
+	if (logfd < 0)
+		return NULL;
+	if (fstat(logfd, &st))
+		return NULL;
+
+	*mapsz = xsize_t(st.st_size);
+	map = xmmap(NULL, *mapsz, PROT_READ, MAP_PRIVATE, logfd, 0);
+	close(logfd);
+	return map;
+}
+
+
 int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
 {
-	const char *logfile, *logdata, *logend, *rec, *start;
+	const char *logfile, *logdata, *logend, *rec, *end;
 	char *email, *message;
-	int logfd, tz, reccnt = 0;
-	struct stat st;
+	int tz, reccnt = 0;
 	unsigned long date;
 	unsigned char new_sha1[20];
 	unsigned char old_sha1[20];
@@ -1402,28 +1421,20 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
 	void *log_mapped;
 	size_t mapsz;
 
-	logfile = git_path("logs/%s", ref);
-	logfd = open(logfile, O_RDONLY, 0);
-	if (logfd < 0)
+	logdata = log_mapped = open_reflog(ref, &mapsz, &logfile);
+	if (!logdata)
 		die("Unable to read log %s: %s", logfile, strerror(errno));
-	fstat(logfd, &st);
-	if (!st.st_size)
+	if (!mapsz)
 		die("Log %s is empty.", logfile);
-	mapsz = xsize_t(st.st_size);
-	log_mapped = xmmap(NULL, mapsz, PROT_READ, MAP_PRIVATE, logfd, 0);
-	logdata = log_mapped;
-	close(logfd);
 
-	rec = logend = logdata + st.st_size;
-	if (logdata < rec && *(rec-1) == '\n')
-		rec--;
+	rec = logend = logdata + mapsz;
 	while (logdata < rec) {
-		start = memrchr(logdata, '\n', rec-logdata);
-		if (start)
-			start++;
-		else
-			start = logdata;
-		parse_reflog_line(start, rec-start+1,
+		if (logdata < rec && rec[-1] == '\n')
+			rec--;
+		end = rec;
+		while (logdata < rec && rec[-1] != '\n')
+			rec--;
+		parse_reflog_line(rec, end-rec+1,
 				  old_sha1, new_sha1,
 				  &email, &date, &tz, &message,
 				  logfile);
@@ -1454,7 +1465,6 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
 		}
 
 		hashcpy(next_sha1, old_sha1);
-		rec = start-1;
 		if (cnt > 0)
 			cnt--;
 		reccnt++;
-- 
1.6.1.315.g92577

```

## Thomas Rast, 2009-01-17 03:30

Subject: [PATCH/RFC v3 3/6] reflog: make for_each_reflog_ent use mmap
Message-ID: <1232163011-20088-4-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232163011-20088-4-git-send-email-trast%40student.ethz.ch
In-Reply-To: <1232163011-20088-3-git-send-email-trast@student.ethz.ch>

```
Now that all building blocks have been refactored, we can implement
for_each_reflog_ent in terms of them, so that it uses mmap.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 refs.c |   25 ++++++++++++++++---------
 1 files changed, 16 insertions(+), 9 deletions(-)

diff --git a/refs.c b/refs.c
index 0a57896..3848aa0 100644
--- a/refs.c
+++ b/refs.c
@@ -1478,22 +1478,28 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
 
 int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
 {
-	const char *logfile;
-	FILE *logfp;
-	char buf[1024];
+	const char *log_mapped, *buf, *log_end, *logfile;
+	size_t mapsz;
 	int ret = 0;
 
-	logfile = git_path("logs/%s", ref);
-	logfp = fopen(logfile, "r");
-	if (!logfp)
+	buf = log_mapped = open_reflog(ref, &mapsz, &logfile);
+	if (!log_mapped)
 		return -1;
-	while (fgets(buf, sizeof(buf), logfp)) {
+	log_end = buf + mapsz;
+
+	while (buf < log_end) {
 		unsigned char osha1[20], nsha1[20];
 		char *email, *message;
+		const char *next;
 		unsigned long timestamp;
 		int len, tz;
 
-		len = strlen(buf);
+		next = memchr(buf, '\n', log_end - buf);
+		if (next)
+			next++;
+		else
+			next = log_end;
+		len = next - buf;
 		parse_reflog_line(buf, len,
 				  osha1, nsha1,
 				  &email, &timestamp, &tz, &message,
@@ -1501,8 +1507,9 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
 		ret = fn(osha1, nsha1, email, timestamp, tz, message, cb_data);
 		if (ret)
 			break;
+		buf = next;
 	}
-	fclose(logfp);
+	munmap((void*) log_mapped, mapsz);
 	return ret;
 }
 
-- 
1.6.1.315.g92577

```

## Thomas Rast, 2009-01-17 03:30

Subject: [PATCH/RFC v3 4/6] reflog: add backwards iterator
Message-ID: <1232163011-20088-5-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232163011-20088-5-git-send-email-trast%40student.ethz.ch
In-Reply-To: <1232163011-20088-4-git-send-email-trast@student.ethz.ch>

```
Add for_each_reflog_ent_backward() which does the same as
for_each_reflog_ent(), except it traverses the reflog in backwards
(newest to oldest) order.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 refs.c |   35 +++++++++++++++++++++++++++++++++++
 refs.h |    1 +
 2 files changed, 36 insertions(+), 0 deletions(-)

diff --git a/refs.c b/refs.c
index 3848aa0..cc78f63 100644
--- a/refs.c
+++ b/refs.c
@@ -1476,6 +1476,41 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
 	return 1;
 }
 
+int for_each_reflog_ent_backward(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+{
+	const char *log_mapped, *buf, *log_end, *logfile;
+	size_t mapsz;
+	int ret = 0;
+
+	log_mapped = open_reflog(ref, &mapsz, &logfile);
+	if (!log_mapped)
+		return -1;
+	buf = log_end = log_mapped + mapsz;
+
+	while (buf > log_mapped) {
+		unsigned char osha1[20], nsha1[20];
+		char *email, *message;
+		const char *end;
+		unsigned long timestamp;
+		int tz;
+
+		while (buf > log_mapped && buf[-1] == '\n')
+			buf--;
+		end = buf;
+		while (buf > log_mapped && buf[-1] != '\n')
+			buf--;
+		parse_reflog_line(buf, end-buf+1,
+				  osha1, nsha1,
+				  &email, &timestamp, &tz, &message,
+				  logfile);
+		ret = fn(osha1, nsha1, email, timestamp, tz, message, cb_data);
+		if (ret)
+			break;
+	}
+	munmap((void*) log_mapped, mapsz);
+	return ret;
+}
+
 int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
 {
 	const char *log_mapped, *buf, *log_end, *logfile;
diff --git a/refs.h b/refs.h
index 06ad260..723bddc 100644
--- a/refs.h
+++ b/refs.h
@@ -60,6 +60,7 @@ struct ref_lock {
 /* iterate over reflog entries */
 typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
 int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
+int for_each_reflog_ent_backward(const char *ref, each_reflog_ent_fn fn, void *cb_data);
 
 /*
  * Calls the specified function for each reflog file until it returns nonzero,
-- 
1.6.1.315.g92577

```

## Thomas Rast, 2009-01-17 03:30

Subject: [PATCH/RFC v3 5/6] sha1_name: implement @{-N} syntax for N-th last checked out
Message-ID: <1232163011-20088-6-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232163011-20088-6-git-send-email-trast%40student.ethz.ch
In-Reply-To: <1232163011-20088-5-git-send-email-trast@student.ethz.ch>

```
Implements a new syntax @{-N} that parses the reflog for the N-th last
interesting 'checkout: moving from $branch to $new' entry and
substitutes $branch.  Here, "interesting" is defined as $branch !=
$new.  We then substitute the real branch name for the parse.

For example:

  git checkout foo
  git checkout bar
  git checkout master
  git checkout master   # did not move, so doesn't count
  git rev-parse @{-1}   # same as bar
  git rev-parse @{-2}   # same as foo
  git rev-parse @{-2}~3 # same as foo~3

Thanks to Junio for much of the code.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-rev-parse.txt |    3 +
 cache.h                         |    1 +
 sha1_name.c                     |   79 +++++++++++++++++++++++++++++++++++++-
 t/t1505-rev-parse-last.sh       |   71 +++++++++++++++++++++++++++++++++++
 4 files changed, 151 insertions(+), 3 deletions(-)
 create mode 100755 t/t1505-rev-parse-last.sh

diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 2921da3..3ccef2f 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -212,6 +212,9 @@ when you run 'git-merge'.
   reflog of the current branch. For example, if you are on the
   branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
 
+* The special construct '@\{-<n>\}' means the <n>th branch checked out
+  before the current one.
+
 * A suffix '{caret}' to a revision parameter means the first parent of
   that commit object.  '{caret}<n>' means the <n>th parent (i.e.
   'rev{caret}'
diff --git a/cache.h b/cache.h
index 8e1af26..0dd9168 100644
--- a/cache.h
+++ b/cache.h
@@ -663,6 +663,7 @@ static inline unsigned int hexval(unsigned char c)
 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 a/sha1_name.c b/sha1_name.c
index 159c2ab..b2dd302 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -297,6 +297,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
 	return logs_found;
 }
 
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 {
 	static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
@@ -307,7 +309,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 	if (len == 40 && !get_sha1_hex(str, sha1))
 		return 0;
 
-	/* basic@{time or number} format to query ref-log */
+	/* basic@{time or number or -number} format to query ref-log */
 	reflog_len = at = 0;
 	if (str[len-1] == '}') {
 		for (at = 0; at < len - 1; at++) {
@@ -324,6 +326,14 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 		return -1;
 
 	if (!len && reflog_len) {
+		struct strbuf buf = STRBUF_INIT;
+		int ret;
+		/* try the @{-N} syntax for n-th checkout */
+		ret = interpret_nth_last_branch(str+at, &buf);
+		if (ret > 0) {
+			/* substitute this branch name and restart */
+			return get_sha1_1(buf.buf, buf.len, sha1);
+		}
 		/* allow "@{...}" to mean the current branch reflog */
 		refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
 	} else if (reflog_len)
@@ -379,8 +389,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 	return 0;
 }
 
-static int get_sha1_1(const char *name, int len, unsigned char *sha1);
-
 static int get_parent(const char *name, int len,
 		      unsigned char *result, int idx)
 {
@@ -674,6 +682,71 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
 	return retval;
 }
 
+struct grab_nth_branch_switch_cbdata {
+	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, *target = NULL;
+	size_t len;
+
+	if (!prefixcmp(message, "checkout: moving from ")) {
+		match = message + strlen("checkout: moving from ");
+		if ((target = strstr(match, " to ")) != NULL)
+			target += 4;
+	}
+
+	if (!match)
+		return 0;
+
+	len = target - match - 4;
+	if (target[len] == '\n' && !strncmp(match, target, len))
+		return 0;
+
+	if (--cb->nth <= 0) {
+		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;
+	struct grab_nth_branch_switch_cbdata cb;
+	const char *brace;
+	char *num_end;
+
+	if (name[0] != '@' || name[1] != '{' || name[2] != '-')
+		return -1;
+	brace = strchr(name, '}');
+	if (!brace)
+		return -1;
+	nth = strtol(name+3, &num_end, 10);
+	if (num_end != brace)
+		return -1;
+
+	cb.nth = nth;
+	cb.buf = buf;
+	for_each_reflog_ent_backward("HEAD", grab_nth_branch_switch, &cb);
+
+	return brace-name+1;
+}
+
 /*
  * This is like "get_sha1_basic()", except it allows "sha1 expressions",
  * notably "xyz^" for "parent of xyz"
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
new file mode 100755
index 0000000..1e49dd2
--- /dev/null
+++ b/t/t1505-rev-parse-last.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+test_description='test @{-N} syntax'
+
+. ./test-lib.sh
+
+
+make_commit () {
+	echo "$1" > "$1" &&
+	git add "$1" &&
+	git commit -m "$1"
+}
+
+
+test_expect_success 'setup' '
+
+	make_commit 1 &&
+	git branch side &&
+	make_commit 2 &&
+	make_commit 3 &&
+	git checkout side &&
+	make_commit 4 &&
+	git merge master &&
+	git checkout master
+
+'
+
+# 1 -- 2 -- 3 master
+#  \         \
+#   \         \
+#    --- 4 --- 5 side
+#
+# and 'side' should be the last branch
+
+git log --graph --all --pretty=oneline --decorate
+
+test_rev_equivalent () {
+
+	git rev-parse "$1" > expect &&
+	git rev-parse "$2" > output &&
+	test_cmp expect output
+
+}
+
+test_expect_success '@{-1} works' '
+	test_rev_equivalent side @{-1}
+'
+
+test_expect_success '@{-1}~2 works' '
+	test_rev_equivalent side~2 @{-1}~2
+'
+
+test_expect_success '@{-1}^2 works' '
+	test_rev_equivalent side^2 @{-1}^2
+'
+
+test_expect_failure '@{-1}@{1} works' '
+	test_rev_equivalent side@{1} @{-1}@{1}
+'
+
+test_expect_success '@{-2} works' '
+	test_rev_equivalent master @{-2}
+'
+
+test_expect_success '@{-3} fails' '
+	test_must_fail git rev-parse @{-3}
+'
+
+test_done
+
+
-- 
1.6.1.315.g92577

```

## Thomas Rast, 2009-01-17 03:30

Subject: [PATCH/RFC v3 6/6] checkout: implement '@{-N}' and '-' special abbreviations
Message-ID: <1232163011-20088-7-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232163011-20088-7-git-send-email-trast%40student.ethz.ch
In-Reply-To: <1232163011-20088-6-git-send-email-trast@student.ethz.ch>

```
Checks if the branch to be checked out is either '@{-N}' or the
special shorthand '-' for '@{-1}' (i.e. the last checked out branch).
If so, we take it to mean the branch name, not the corresponding SHA,
so that we check out an attached HEAD on that branch.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-checkout.txt |    4 +++
 builtin-checkout.c             |   15 ++++++++++-
 t/t2012-checkout-last.sh       |   50 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 67 insertions(+), 2 deletions(-)
 create mode 100755 t/t2012-checkout-last.sh

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 9cd5151..3bccffa 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -133,6 +133,10 @@ the conflicted merge in the specified paths.
 +
 When this parameter names a non-branch (but still a valid commit object),
 your HEAD becomes 'detached'.
++
+As a special case, the "`@\{-N\}`" syntax for the N-th last branch
+checks out the branch (instead of detaching).  You may also specify
+"`-`" which is synonymous with "`@\{-1\}`".
 
 
 Detached HEAD
diff --git a/builtin-checkout.c b/builtin-checkout.c
index b5dd9c0..b0a101b 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -361,8 +361,16 @@ 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);
+	int ret;
+
+	if ((ret = interpret_nth_last_branch(branch->name, &buf))
+	    && ret == strlen(branch->name)) {
+		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);
 }
 
@@ -671,6 +679,9 @@ 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 = "@{-1}";
+
 		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.315.g92577

```

## Johannes Schindelin, 2009-01-17 05:35

Subject: Re: [PATCH/RFC v3 1/6] reflog: refactor parsing and checking
Message-ID: <alpine.DEB.1.00.0901170632210.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901170632210.3586%40pacific.mpi-cbg.de
In-Reply-To: <1232163011-20088-2-git-send-email-trast@student.ethz.ch>

```
Hi,

On Sat, 17 Jan 2009, Thomas Rast wrote:

> Note that this switches for_each_reflog_ent() from silently ignoring 
> errors to die().

I am not convinced this is necessary, and neither that it is good.

You could return int from parse_reflog_line() just like the other 
non-dying functions.

And keep in mind that reflogs could be corrupted for all kinds of reasons; 
with your patch, reflog handling would fail gracelessly, even if a 
lot could still be salvaged (by ignoring the error with a warning).

Ciao,
Dscho

```

## Johannes Schindelin, 2009-01-17 05:40

Subject: Re: [PATCH/RFC v3 2/6] reflog: refactor log open+mmap
Message-ID: <alpine.DEB.1.00.0901170636280.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901170636280.3586%40pacific.mpi-cbg.de
In-Reply-To: <1232163011-20088-3-git-send-email-trast@student.ethz.ch>

```
Hi,

On Sat, 17 Jan 2009, Thomas Rast wrote:

> +static char *open_reflog(const char *ref, size_t *mapsz, const char **logfile)
> +{
> +	struct stat st;
> +	int logfd;
> +	char *map;
> +
> +	*logfile = git_path("logs/%s", ref);

That is dangerous.  git_path() returns a pointer to a static buffer.  
Before your patch, logfile was a local variable, and one could be 
relatively sure that git_path() was not called during the lifetime.  Now 
the lifetime of logfile is no longer as clear-cut, and it is much easier 
to overlook that git_path() must not be called while logfile holds a 
reference to its static buffer.

Ciao,
Dscho

```

## Johannes Schindelin, 2009-01-17 05:52

Subject: Re: [PATCH/RFC v3 0/6] N-th last checked out branch
Message-ID: <alpine.DEB.1.00.0901170646560.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901170646560.3586%40pacific.mpi-cbg.de
In-Reply-To: <1232163011-20088-1-git-send-email-trast@student.ethz.ch>

```
Hi,

On Sat, 17 Jan 2009, Thomas Rast wrote:

>  Documentation/git-checkout.txt  |    4 +
>  Documentation/git-rev-parse.txt |    3 +
>  builtin-checkout.c              |   15 ++-
>  cache.h                         |    1 +
>  refs.c                          |  285 +++++++++++++++++++++++----------------
>  refs.h                          |    1 +
>  sha1_name.c                     |   79 +++++++++++-
>  t/t1505-rev-parse-last.sh       |   71 ++++++++++
>  t/t2012-checkout-last.sh        |   50 +++++++
>  9 files changed, 387 insertions(+), 122 deletions(-)
>  create mode 100755 t/t1505-rev-parse-last.sh
>  create mode 100755 t/t2012-checkout-last.sh

Let's quickly compare that to what Junio sent:

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

Granted, the documentation and tests are nice, but why that big change in 
refs.c?  After all, the more you change in one go, the more you can break.

Let's take one step back.  The two things that Junio's patch left to be 
desired (in addition to documentation and tests) are

- reflogs are traversed twice.  While this is not a showstopper, it is 
  easily improved by building a string_list and then picking the one entry 
  we're interested in, and

- the '-' handling you seem to want.

I really have to ask: why did you not work on top of Junio's patch, just 
adding docs, tests, and checkout -?  And then -- maybe -- the 
string_list...

Although I have to admit that I am not _that_ convinced the string_list is 
worth it: reflogs are not evaluated all the time, so it is definitely not 
performance critical.

Ciao,
Dscho

```

## Thomas Rast, 2009-01-17 13:38

Subject: Re: [PATCH/RFC v3 0/6] N-th last checked out branch
Message-ID: <200901171438.22504.trast@student.ethz.ch>
URL: https://gitlist.dev/e/200901171438.22504.trast%40student.ethz.ch
In-Reply-To: <alpine.DEB.1.00.0901170646560.3586@pacific.mpi-cbg.de>

```
Johannes Schindelin wrote:
> On Sat, 17 Jan 2009, Thomas Rast wrote:
> >  9 files changed, 387 insertions(+), 122 deletions(-)
> 
> Let's quickly compare that to what Junio sent:
> 
>  builtin-checkout.c |   10 +++++-
>  cache.h            |    1 +
>  sha1_name.c        |   78 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 87 insertions(+), 2 deletions(-)
[...]
> - the '-' handling you seem to want.

Let's be clear that the handling *I* wanted is this big:

 builtin-checkout.c                     |   27 ++++++++++++++++-

In any case, I'll follow up with a version that still traverses the
logs twice and thus doesn't need the first two.

> I really have to ask: why did you not work on top of Junio's patch, just 
> adding docs, tests, and checkout -?  And then -- maybe -- the 
> string_list...
> 
> Although I have to admit that I am not _that_ convinced the string_list is 
> worth it: reflogs are not evaluated all the time, so it is definitely not 
> performance critical.

I take it you have some idea where and how string_list fits into this
topic?

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


```

## Thomas Rast, 2009-01-17 13:40

Subject: [PATCH/RFC v3bis 1/2] sha1_name: implement @{-N} syntax for N-th last checked out
Message-ID: <1232199621-31072-1-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232199621-31072-1-git-send-email-trast%40student.ethz.ch
In-Reply-To: <200901171438.22504.trast@student.ethz.ch>

```
Implements a new syntax @{-N} that parses the reflog for the N-th last
interesting 'checkout: moving from $branch to $new' entry and
substitutes $branch.  Here, "interesting" is defined as $branch !=
$new.  We then substitute the real branch name for the parse.

For example:

  git checkout foo
  git checkout bar
  git checkout master
  git checkout master   # did not move, so doesn't count
  git rev-parse @{-1}   # same as bar
  git rev-parse @{-2}   # same as foo
  git rev-parse @{-2}~3 # same as foo~3

Thanks to Junio for much of the code.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-rev-parse.txt |    3 +
 cache.h                         |    1 +
 sha1_name.c                     |   99 +++++++++++++++++++++++++++++++++++++-
 t/t1505-rev-parse-last.sh       |   71 ++++++++++++++++++++++++++++
 4 files changed, 171 insertions(+), 3 deletions(-)
 create mode 100755 t/t1505-rev-parse-last.sh

diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 2921da3..3ccef2f 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -212,6 +212,9 @@ when you run 'git-merge'.
   reflog of the current branch. For example, if you are on the
   branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
 
+* The special construct '@\{-<n>\}' means the <n>th branch checked out
+  before the current one.
+
 * A suffix '{caret}' to a revision parameter means the first parent of
   that commit object.  '{caret}<n>' means the <n>th parent (i.e.
   'rev{caret}'
diff --git a/cache.h b/cache.h
index 8e1af26..0dd9168 100644
--- a/cache.h
+++ b/cache.h
@@ -663,6 +663,7 @@ static inline unsigned int hexval(unsigned char c)
 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 a/sha1_name.c b/sha1_name.c
index 159c2ab..9e1538e 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -297,6 +297,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
 	return logs_found;
 }
 
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 {
 	static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
@@ -307,7 +309,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 	if (len == 40 && !get_sha1_hex(str, sha1))
 		return 0;
 
-	/* basic@{time or number} format to query ref-log */
+	/* basic@{time or number or -number} format to query ref-log */
 	reflog_len = at = 0;
 	if (str[len-1] == '}') {
 		for (at = 0; at < len - 1; at++) {
@@ -324,6 +326,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 		return -1;
 
 	if (!len && reflog_len) {
+		struct strbuf buf = STRBUF_INIT;
+		int ret;
+		/* try the @{-N} syntax for n-th checkout */
+		ret = interpret_nth_last_branch(str+at, &buf);
+		if (ret > 0) {
+			/* substitute this branch name and restart */
+			return get_sha1_1(buf.buf, buf.len, sha1);
+		} else if (ret == 0) {
+			return -1;
+		}
 		/* allow "@{...}" to mean the current branch reflog */
 		refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
 	} else if (reflog_len)
@@ -379,8 +391,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 	return 0;
 }
 
-static int get_sha1_1(const char *name, int len, unsigned char *sha1);
-
 static int get_parent(const char *name, int len,
 		      unsigned char *result, int idx)
 {
@@ -674,6 +684,89 @@ 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, *target = NULL;
+	size_t len;
+
+	if (!prefixcmp(message, "checkout: moving from ")) {
+		match = message + strlen("checkout: moving from ");
+		if ((target = strstr(match, " to ")) != NULL)
+			target += 4;
+	}
+
+	if (!match)
+		return 0;
+
+	len = target - match - 4;
+	if (target[len] == '\n' && !strncmp(match, target, len))
+		return 0;
+
+	if (cb->counting) {
+		cb->nth++;
+		return 0;
+	}
+
+	if (cb->nth-- <= 0) {
+		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 the number of characters parsed if successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ *
+ * If the input was ok but there are not N branch switches in the
+ * reflog, it returns 0.
+ */
+int interpret_nth_last_branch(const char *name, struct strbuf *buf)
+{
+	int nth;
+	struct grab_nth_branch_switch_cbdata cb;
+	const char *brace;
+	char *num_end;
+
+	if (name[0] != '@' || name[1] != '{' || name[2] != '-')
+		return -1;
+	brace = strchr(name, '}');
+	if (!brace)
+		return -1;
+	nth = strtol(name+3, &num_end, 10);
+	if (num_end != brace)
+		return -1;
+
+	cb.counting = 1;
+	cb.nth = 0;
+	cb.buf = buf;
+	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+
+	if (cb.nth < nth)
+		return 0;
+
+	cb.counting = 0;
+	cb.nth -= nth;
+	cb.buf = buf;
+	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+
+	return brace-name+1;
+}
+
 /*
  * This is like "get_sha1_basic()", except it allows "sha1 expressions",
  * notably "xyz^" for "parent of xyz"
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
new file mode 100755
index 0000000..1e49dd2
--- /dev/null
+++ b/t/t1505-rev-parse-last.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+test_description='test @{-N} syntax'
+
+. ./test-lib.sh
+
+
+make_commit () {
+	echo "$1" > "$1" &&
+	git add "$1" &&
+	git commit -m "$1"
+}
+
+
+test_expect_success 'setup' '
+
+	make_commit 1 &&
+	git branch side &&
+	make_commit 2 &&
+	make_commit 3 &&
+	git checkout side &&
+	make_commit 4 &&
+	git merge master &&
+	git checkout master
+
+'
+
+# 1 -- 2 -- 3 master
+#  \         \
+#   \         \
+#    --- 4 --- 5 side
+#
+# and 'side' should be the last branch
+
+git log --graph --all --pretty=oneline --decorate
+
+test_rev_equivalent () {
+
+	git rev-parse "$1" > expect &&
+	git rev-parse "$2" > output &&
+	test_cmp expect output
+
+}
+
+test_expect_success '@{-1} works' '
+	test_rev_equivalent side @{-1}
+'
+
+test_expect_success '@{-1}~2 works' '
+	test_rev_equivalent side~2 @{-1}~2
+'
+
+test_expect_success '@{-1}^2 works' '
+	test_rev_equivalent side^2 @{-1}^2
+'
+
+test_expect_failure '@{-1}@{1} works' '
+	test_rev_equivalent side@{1} @{-1}@{1}
+'
+
+test_expect_success '@{-2} works' '
+	test_rev_equivalent master @{-2}
+'
+
+test_expect_success '@{-3} fails' '
+	test_must_fail git rev-parse @{-3}
+'
+
+test_done
+
+
-- 
1.6.1.315.g92577

```

## Thomas Rast, 2009-01-17 13:40

Subject: [PATCH/RFC v3bis 2/2] checkout: implement '@{-N}' and '-' special abbreviations
Message-ID: <1232199621-31072-2-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232199621-31072-2-git-send-email-trast%40student.ethz.ch
In-Reply-To: <1232199621-31072-1-git-send-email-trast@student.ethz.ch>

```
Checks if the branch to be checked out is either '@{-N}' or the
special shorthand '-' for '@{-1}' (i.e. the last checked out branch).
If so, we take it to mean the branch name, not the corresponding SHA,
so that we check out an attached HEAD on that branch.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-checkout.txt |    4 +++
 builtin-checkout.c             |   15 ++++++++++-
 t/t2012-checkout-last.sh       |   50 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 67 insertions(+), 2 deletions(-)
 create mode 100755 t/t2012-checkout-last.sh

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 9cd5151..3bccffa 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -133,6 +133,10 @@ the conflicted merge in the specified paths.
 +
 When this parameter names a non-branch (but still a valid commit object),
 your HEAD becomes 'detached'.
++
+As a special case, the "`@\{-N\}`" syntax for the N-th last branch
+checks out the branch (instead of detaching).  You may also specify
+"`-`" which is synonymous with "`@\{-1\}`".
 
 
 Detached HEAD
diff --git a/builtin-checkout.c b/builtin-checkout.c
index b5dd9c0..b0a101b 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -361,8 +361,16 @@ 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);
+	int ret;
+
+	if ((ret = interpret_nth_last_branch(branch->name, &buf))
+	    && ret == strlen(branch->name)) {
+		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);
 }
 
@@ -671,6 +679,9 @@ 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 = "@{-1}";
+
 		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.315.g92577

```

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

Subject: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <alpine.DEB.1.00.0901171602340.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901171602340.3586%40pacific.mpi-cbg.de
In-Reply-To: <200901171438.22504.trast@student.ethz.ch>

```

Instead of traversing them twice, we just build a list of branch switches,
pick the one we're interested in, and free the list again.

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

	On Sat, 17 Jan 2009, Thomas Rast wrote:

	> Johannes Schindelin wrote:
	> 
	> > I really have to ask: why did you not work on top of Junio's 
	> > patch, just adding docs, tests, and checkout -?  And then -- maybe -- 
	> > the string_list...

	Of course, I meant the patch as-is, with Junio as author.  But 
	hey, if he does not care...

	> > Although I have to admit that I am not _that_ convinced the 
	> > string_list is worth it: reflogs are not evaluated all the time, so 
	> > it is definitely not performance critical.
	>
	> I take it you have some idea where and how string_list fits into 
	> this topic?

	Indeed.

	This patch was generated using --collapse-non-alnums for readability.
	Did not really help much.

 sha1_name.c |   72 ++++++++++++++++++++++++----------------------------------
 1 files changed, 30 insertions(+), 42 deletions(-)

diff --git a/sha1_name.c b/sha1_name.c
index 2bbc5f1..306d04b 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -6,6 +6,7 @@
 #include "tree-walk.h"
 #include "refs.h"
 #include "cache-tree.h"
+#include "string-list.h"
 
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
@@ -691,43 +692,31 @@ 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,
+static int add_one_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;
+	struct string_list *list = cb_data;
 	const char *match = NULL, *target = NULL;
 	size_t len;
-
-	if (!prefixcmp(message, "checkout: moving from ")) {
-		match = message + strlen("checkout: moving from ");
-		if ((target = strstr(match, " to ")) != NULL)
-			target += 4;
-	}
-
-	if (!match)
+
+	if (prefixcmp(message, "checkout: moving from "))
 		return 0;
 
-	len = target - match - 4;
-	if (target[len] == '\n' && !strncmp(match, target, len))
-		return 0;
-
-	if (cb->counting) {
-		cb->nth++;
-		return 0;
-	}
-
-	if (cb->nth-- <= 0) {
-		strbuf_reset(cb->buf);
-		strbuf_add(cb->buf, match, len);
-		return 1;
-	}
+	match = message + strlen("checkout: moving from ");
+
+	/* Is it "moving" from a branch to itself?  Then ignore it. */
+	if ((target = strstr(match, " to ")) != NULL) {
+		target += 4;
+		len = target - match - 4;
+		if (target[len] == '\n' && !strncmp(match, target, len))
+			return 0;
+	}
+	else
+		len = strchrnul(match, ' ') - match;
+
+	string_list_append(xstrndup(match, len), list);
+
 	return 0;
 }
 
@@ -745,7 +734,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 int interpret_nth_last_branch(const char *name, struct strbuf *buf)
 {
 	int nth;
-	struct grab_nth_branch_switch_cbdata cb;
+	struct string_list branch_list = { NULL, 0, 0, 0 };
 	const char *brace;
 	char *num_end;
 
@@ -758,18 +747,17 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
 	if (num_end != brace)
 		return -1;
 
-	cb.counting = 1;
-	cb.nth = 0;
-	cb.buf = buf;
-	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
-
-	if (cb.nth < nth)
+	for_each_reflog_ent("HEAD", add_one_branch_switch, &branch_list);
+
+	if (branch_list.nr < nth)
 		return 0;
-
-	cb.counting = 0;
-	cb.nth -= nth;
-	cb.buf = buf;
-	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+
+	strbuf_reset(buf);
+	strbuf_addstr(buf, branch_list.items[branch_list.nr - nth].string);
+
+	/* force free()ing the items */
+	branch_list.strdup_strings = 1;
+	string_list_clear(&branch_list, 0);
 
 	return brace-name+1;
 }
-- 
1.6.1.325.g062d4

```

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

Subject: [PATCH/RFC v4 0/5] N-th last checked out branch
Message-ID: <1232208597-29249-1-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232208597-29249-1-git-send-email-trast%40student.ethz.ch
In-Reply-To: <alpine.DEB.1.00.0901171602340.3586@pacific.mpi-cbg.de>

```
Johannes Schindelin wrote:
> 
> Instead of traversing them twice, we just build a list of branch switches,
> pick the one we're interested in, and free the list again.
> 
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
> 
> On Sat, 17 Jan 2009, Thomas Rast wrote:
> 
> > Johannes Schindelin wrote:
> > 
> > > I really have to ask: why did you not work on top of Junio's 
> > > patch, just adding docs, tests, and checkout -?  And then -- maybe -- 
> > > the string_list...
> 
> Of course, I meant the patch as-is, with Junio as author.  But 
> hey, if he does not care...

*shrug*

For one thing there was no commit message.

I made up a message, split the changes I made into smaller patches,
and added a fixed up version of your patch on top, since it had some
context that is not in any version I have.


Johannes Schindelin (1):
  interpret_nth_last_branch(): avoid traversing the reflogs twice

Junio C Hamano (1):
  checkout: implement "@{-N}" shortcut name for N-th last branch

Thomas Rast (3):
  sha1_name: tweak @{-N} lookup
  sha1_name: support @{-N} syntax in get_sha1()
  checkout: implement "-" abbreviation, add docs and tests

 Documentation/git-checkout.txt  |    4 ++
 Documentation/git-rev-parse.txt |    3 +
 builtin-checkout.c              |   15 ++++++-
 cache.h                         |    1 +
 sha1_name.c                     |   88 +++++++++++++++++++++++++++++++++++++-
 t/t1505-rev-parse-last.sh       |   71 +++++++++++++++++++++++++++++++
 t/t2012-checkout-last.sh        |   50 ++++++++++++++++++++++
 7 files changed, 227 insertions(+), 5 deletions(-)
 create mode 100755 t/t1505-rev-parse-last.sh
 create mode 100755 t/t2012-checkout-last.sh

```

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

Subject: [PATCH/RFC v4 1/5] checkout: implement "@{-N}" shortcut name for N-th last branch
Message-ID: <1232208597-29249-2-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232208597-29249-2-git-send-email-trast%40student.ethz.ch
In-Reply-To: <1232208597-29249-1-git-send-email-trast@student.ethz.ch>

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

Implement a shortcut @{-N} for the N-th last branch checked out, that
works by parsing the reflog for the message added by previous
git-checkout invocations.  We expand the @{-N} to the branch name, so
that you end up on an attached HEAD on that branch.
---
 builtin-checkout.c |   10 +++++-
 cache.h            |    1 +
 sha1_name.c        |   78 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+), 2 deletions(-)

diff --git a/builtin-checkout.c b/builtin-checkout.c
index b5dd9c0..a3b69d6 100644
--- a/builtin-checkout.c
+++ b/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 a/cache.h b/cache.h
index 8e1af26..0dd9168 100644
--- a/cache.h
+++ b/cache.h
@@ -663,6 +663,7 @@ static inline unsigned int hexval(unsigned char c)
 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 a/sha1_name.c b/sha1_name.c
index 159c2ab..6377264 100644
--- a/sha1_name.c
+++ b/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"
-- 
1.6.1.315.g92577

```

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

Subject: [PATCH/RFC v4 2/5] sha1_name: tweak @{-N} lookup
Message-ID: <1232208597-29249-3-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232208597-29249-3-git-send-email-trast%40student.ethz.ch
In-Reply-To: <1232208597-29249-2-git-send-email-trast@student.ethz.ch>

```
Have the lookup only look at "interesting" checkouts, meaning those
that tell you "Already on ..." don't count even though they also cause
a reflog entry.

Let interpret_nth_last_branch() return the number of characters
parsed, so that git-checkout can verify that the branch spec was
@{-N}, not @{-1}^2 or something like that.  (The latter will be added
later.)

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 builtin-checkout.c |    4 ++-
 sha1_name.c        |   53 ++++++++++++++++++++++++++++-----------------------
 2 files changed, 32 insertions(+), 25 deletions(-)

diff --git a/builtin-checkout.c b/builtin-checkout.c
index a3b69d6..dc1de06 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -361,8 +361,10 @@ struct branch_info {
 static void setup_branch_path(struct branch_info *branch)
 {
 	struct strbuf buf = STRBUF_INIT;
+	int ret;
 
-	if (!interpret_nth_last_branch(branch->name, &buf)) {
+	if ((ret = interpret_nth_last_branch(branch->name, &buf))
+	    && ret == strlen(branch->name)) {
 		branch->name = xstrdup(buf.buf);
 		strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
 	} else {
diff --git a/sha1_name.c b/sha1_name.c
index 6377264..34e39db 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -685,29 +685,28 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 				  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;
-		}
+	const char *match = NULL, *target = NULL;
+	size_t len;
+
+	if (!prefixcmp(message, "checkout: moving from ")) {
+		match = message + strlen("checkout: moving from ");
+		if ((target = strstr(match, " to ")) != NULL)
+			target += 4;
 	}
 
 	if (!match)
 		return 0;
 
+	len = target - match - 4;
+	if (target[len] == '\n' && !strncmp(match, target, len))
+		return 0;
+
 	if (cb->counting) {
 		cb->nth++;
 		return 0;
 	}
 
-	if (--cb->nth <= 0) {
-		size_t len = strlen(match);
-		while (match[len-1] == '\n')
-			len--;
+	if (cb->nth-- <= 0) {
 		strbuf_reset(cb->buf);
 		strbuf_add(cb->buf, match, len);
 		return 1;
@@ -718,26 +717,28 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 /*
  * 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.
+ * buf and returns the number of characters parsed if successful.
  *
  * If the input is not of the accepted format, it returns a negative
  * number to signal an error.
+ *
+ * If the input was ok but there are not N branch switches in the
+ * reflog, it returns 0.
  */
 int interpret_nth_last_branch(const char *name, struct strbuf *buf)
 {
-	int nth, i;
+	int nth;
 	struct grab_nth_branch_switch_cbdata cb;
+	const char *brace;
+	char *num_end;
 
 	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)
+	brace = strchr(name, '}');
+	if (!brace)
+		return -1;
+	nth = strtol(name+3, &num_end, 10);
+	if (num_end != brace)
 		return -1;
 
 	cb.counting = 1;
@@ -745,11 +746,15 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
 	cb.buf = buf;
 	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
 
+	if (cb.nth < nth)
+		return 0;
+
 	cb.counting = 0;
 	cb.nth -= nth;
 	cb.buf = buf;
 	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
-	return 0;
+
+	return brace-name+1;
 }
 
 /*
-- 
1.6.1.315.g92577

```

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

Subject: [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1()
Message-ID: <1232208597-29249-4-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232208597-29249-4-git-send-email-trast%40student.ethz.ch
In-Reply-To: <1232208597-29249-3-git-send-email-trast@student.ethz.ch>

```
Let get_sha1() parse the @{-N} syntax, with docs and tests.

Note that while @{-1}^2, @{-2}~5 and such are supported, @{-1}@{1} is
currently not allowed.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-rev-parse.txt |    3 ++
 sha1_name.c                     |   16 +++++++--
 t/t1505-rev-parse-last.sh       |   71 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+), 3 deletions(-)
 create mode 100755 t/t1505-rev-parse-last.sh

diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 2921da3..3ccef2f 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -212,6 +212,9 @@ when you run 'git-merge'.
   reflog of the current branch. For example, if you are on the
   branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
 
+* The special construct '@\{-<n>\}' means the <n>th branch checked out
+  before the current one.
+
 * A suffix '{caret}' to a revision parameter means the first parent of
   that commit object.  '{caret}<n>' means the <n>th parent (i.e.
   'rev{caret}'
diff --git a/sha1_name.c b/sha1_name.c
index 34e39db..9e1538e 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -297,6 +297,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
 	return logs_found;
 }
 
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 {
 	static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
@@ -307,7 +309,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 	if (len == 40 && !get_sha1_hex(str, sha1))
 		return 0;
 
-	/* basic@{time or number} format to query ref-log */
+	/* basic@{time or number or -number} format to query ref-log */
 	reflog_len = at = 0;
 	if (str[len-1] == '}') {
 		for (at = 0; at < len - 1; at++) {
@@ -324,6 +326,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 		return -1;
 
 	if (!len && reflog_len) {
+		struct strbuf buf = STRBUF_INIT;
+		int ret;
+		/* try the @{-N} syntax for n-th checkout */
+		ret = interpret_nth_last_branch(str+at, &buf);
+		if (ret > 0) {
+			/* substitute this branch name and restart */
+			return get_sha1_1(buf.buf, buf.len, sha1);
+		} else if (ret == 0) {
+			return -1;
+		}
 		/* allow "@{...}" to mean the current branch reflog */
 		refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
 	} else if (reflog_len)
@@ -379,8 +391,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 	return 0;
 }
 
-static int get_sha1_1(const char *name, int len, unsigned char *sha1);
-
 static int get_parent(const char *name, int len,
 		      unsigned char *result, int idx)
 {
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
new file mode 100755
index 0000000..1e49dd2
--- /dev/null
+++ b/t/t1505-rev-parse-last.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+test_description='test @{-N} syntax'
+
+. ./test-lib.sh
+
+
+make_commit () {
+	echo "$1" > "$1" &&
+	git add "$1" &&
+	git commit -m "$1"
+}
+
+
+test_expect_success 'setup' '
+
+	make_commit 1 &&
+	git branch side &&
+	make_commit 2 &&
+	make_commit 3 &&
+	git checkout side &&
+	make_commit 4 &&
+	git merge master &&
+	git checkout master
+
+'
+
+# 1 -- 2 -- 3 master
+#  \         \
+#   \         \
+#    --- 4 --- 5 side
+#
+# and 'side' should be the last branch
+
+git log --graph --all --pretty=oneline --decorate
+
+test_rev_equivalent () {
+
+	git rev-parse "$1" > expect &&
+	git rev-parse "$2" > output &&
+	test_cmp expect output
+
+}
+
+test_expect_success '@{-1} works' '
+	test_rev_equivalent side @{-1}
+'
+
+test_expect_success '@{-1}~2 works' '
+	test_rev_equivalent side~2 @{-1}~2
+'
+
+test_expect_success '@{-1}^2 works' '
+	test_rev_equivalent side^2 @{-1}^2
+'
+
+test_expect_failure '@{-1}@{1} works' '
+	test_rev_equivalent side@{1} @{-1}@{1}
+'
+
+test_expect_success '@{-2} works' '
+	test_rev_equivalent master @{-2}
+'
+
+test_expect_success '@{-3} fails' '
+	test_must_fail git rev-parse @{-3}
+'
+
+test_done
+
+
-- 
1.6.1.315.g92577

```

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

Subject: [PATCH/RFC v4 4/5] checkout: implement "-" abbreviation, add docs and tests
Message-ID: <1232208597-29249-5-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232208597-29249-5-git-send-email-trast%40student.ethz.ch
In-Reply-To: <1232208597-29249-4-git-send-email-trast@student.ethz.ch>

```
Have '-' mean the same as '@{-1}', i.e., the last branch we were on.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-checkout.txt |    4 +++
 builtin-checkout.c             |    3 ++
 t/t2012-checkout-last.sh       |   50 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 57 insertions(+), 0 deletions(-)
 create mode 100755 t/t2012-checkout-last.sh

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 9cd5151..3bccffa 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -133,6 +133,10 @@ the conflicted merge in the specified paths.
 +
 When this parameter names a non-branch (but still a valid commit object),
 your HEAD becomes 'detached'.
++
+As a special case, the "`@\{-N\}`" syntax for the N-th last branch
+checks out the branch (instead of detaching).  You may also specify
+"`-`" which is synonymous with "`@\{-1\}`".
 
 
 Detached HEAD
diff --git a/builtin-checkout.c b/builtin-checkout.c
index dc1de06..b0a101b 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -679,6 +679,9 @@ 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 = "@{-1}";
+
 		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.315.g92577

```

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

Subject: [PATCH/RFC v4 5/5] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <1232208597-29249-6-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232208597-29249-6-git-send-email-trast%40student.ethz.ch
In-Reply-To: <1232208597-29249-5-git-send-email-trast@student.ethz.ch>

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

Instead of traversing them twice, we just build a list of branch switches,
pick the one we're interested in, and free the list again.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 sha1_name.c |   61 ++++++++++++++++++++++++----------------------------------
 1 files changed, 25 insertions(+), 36 deletions(-)

diff --git a/sha1_name.c b/sha1_name.c
index 9e1538e..b21a1f0 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -5,6 +5,8 @@
 #include "blob.h"
 #include "tree-walk.h"
 #include "refs.h"
+#include "cache-tree.h"
+#include "string-list.h"
 
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
@@ -684,43 +686,31 @@ 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,
+static int add_one_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;
+	struct string_list *list = cb_data;
 	const char *match = NULL, *target = NULL;
 	size_t len;
 
-	if (!prefixcmp(message, "checkout: moving from ")) {
-		match = message + strlen("checkout: moving from ");
-		if ((target = strstr(match, " to ")) != NULL)
-			target += 4;
-	}
-
-	if (!match)
+	if (prefixcmp(message, "checkout: moving from "))
 		return 0;
 
-	len = target - match - 4;
-	if (target[len] == '\n' && !strncmp(match, target, len))
-		return 0;
+	match = message + strlen("checkout: moving from ");
 
-	if (cb->counting) {
-		cb->nth++;
-		return 0;
+	/* Is it "moving" from a branch to itself?  Then ignore it. */
+	if ((target = strstr(match, " to ")) != NULL) {
+		target += 4;
+		len = target - match - 4;
+		if (target[len] == '\n' && !strncmp(match, target, len))
+			return 0;
 	}
+	else
+		len = strchrnul(match, ' ') - match;
+
+	string_list_append(xstrndup(match, len), list);
 
-	if (cb->nth-- <= 0) {
-		strbuf_reset(cb->buf);
-		strbuf_add(cb->buf, match, len);
-		return 1;
-	}
 	return 0;
 }
 
@@ -738,7 +728,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 int interpret_nth_last_branch(const char *name, struct strbuf *buf)
 {
 	int nth;
-	struct grab_nth_branch_switch_cbdata cb;
+	struct string_list branch_list = { NULL, 0, 0, 0 };
 	const char *brace;
 	char *num_end;
 
@@ -751,18 +741,17 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
 	if (num_end != brace)
 		return -1;
 
-	cb.counting = 1;
-	cb.nth = 0;
-	cb.buf = buf;
-	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+	for_each_reflog_ent("HEAD", add_one_branch_switch, &branch_list);
 
-	if (cb.nth < nth)
+	if (branch_list.nr < nth)
 		return 0;
 
-	cb.counting = 0;
-	cb.nth -= nth;
-	cb.buf = buf;
-	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+	strbuf_reset(buf);
+	strbuf_addstr(buf, branch_list.items[branch_list.nr - nth].string);
+
+	/* force free()ing the items */
+	branch_list.strdup_strings = 1;
+	string_list_clear(&branch_list, 0);
 
 	return brace-name+1;
 }
-- 
1.6.1.315.g92577

```

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

Subject: Re: [PATCH/RFC v4 0/5] N-th last checked out branch
Message-ID: <alpine.DEB.1.00.0901171747290.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901171747290.3586%40pacific.mpi-cbg.de
In-Reply-To: <1232208597-29249-1-git-send-email-trast@student.ethz.ch>

```
Hi,

On Sat, 17 Jan 2009, Thomas Rast wrote:

> I [...] added a fixed up version of your patch on top, since it had some 
> context that is not in any version I have.

Thanks.

Yeah, I know, I have too many patches in my fork, but I'm working on it... 
:-)

Ciao,
Dscho

```

## Johannes Schindelin, 2009-01-17 17:55

Subject: Re: [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1()
Message-ID: <alpine.DEB.1.00.0901171854060.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901171854060.3586%40pacific.mpi-cbg.de
In-Reply-To: <1232208597-29249-4-git-send-email-trast@student.ethz.ch>

```
Hi,

On Sat, 17 Jan 2009, Thomas Rast wrote:

> diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
> new file mode 100755
> index 0000000..1e49dd2
> --- /dev/null
> +++ b/t/t1505-rev-parse-last.sh
> @@ -0,0 +1,71 @@
> +#!/bin/sh
> +
> +test_description='test @{-N} syntax'
> +
> +. ./test-lib.sh
> +
> +
> +make_commit () {
> +	echo "$1" > "$1" &&
> +	git add "$1" &&
> +	git commit -m "$1"
> +}
> +
> +
> +test_expect_success 'setup' '
> +
> +	make_commit 1 &&
> +	git branch side &&
> +	make_commit 2 &&
> +	make_commit 3 &&
> +	git checkout side &&
> +	make_commit 4 &&
> +	git merge master &&
> +	git checkout master
> +
> +'
> +
> +# 1 -- 2 -- 3 master
> +#  \         \
> +#   \         \
> +#    --- 4 --- 5 side
> +#
> +# and 'side' should be the last branch
> +
> +git log --graph --all --pretty=oneline --decorate
> +

Maybe you want to squash this in, so that the output of "make test" is 
not cluttered by the graph?

-- snipsnap --
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
index 1e49dd2..72e8322 100755
--- a/t/t1505-rev-parse-last.sh
+++ b/t/t1505-rev-parse-last.sh
@@ -32,7 +32,11 @@ test_expect_success 'setup' '
 #
 # and 'side' should be the last branch
 
-git log --graph --all --pretty=oneline --decorate
+test_expect_success 'show a log (for debugging)' '
+
+	git log --graph --all --pretty=oneline --decorate
+
+'
 
 test_rev_equivalent () {
 
-- 
1.6.1.332.g9a59d

```

## Johannes Schindelin, 2009-01-17 18:08

Subject: [PATCH 6/5] Fix parsing of @{-1}@{1}
Message-ID: <alpine.DEB.1.00.0901171907530.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901171907530.3586%40pacific.mpi-cbg.de
In-Reply-To: <1232208597-29249-6-git-send-email-trast@student.ethz.ch>

```

To do that, Git no longer looks forward for the '@{' corresponding to the
closing '}' but backward, and dwim_ref() as well as dwim_log() learnt
about the @{-<N>} notation.

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

	The modifications of dwim_ref() and dwim_log() are probably
	more important than the issue I tried to fix...

 sha1_name.c               |   25 ++++++++++++++++++++++++-
 t/t1505-rev-parse-last.sh |    2 +-
 2 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/sha1_name.c b/sha1_name.c
index 306d04b..ee0c456 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -240,8 +240,28 @@ static int ambiguous_path(const char *path, int len)
 	return slash;
 }
 
+/*
+ * *string and *len will only be substituted, and *string returned (for
+ * later free()ing) if the string passed in is of the form @{-<n>}.
+ */
+static char *substitute_nth_last_branch(const char **string, int *len)
+{
+	struct strbuf buf = STRBUF_INIT;
+	int ret = interpret_nth_last_branch(*string, &buf);
+
+	if (ret == *len) {
+		size_t size;
+		*string = strbuf_detach(&buf, &size);
+		*len = size;
+		return (char *)*string;
+	}
+
+	return NULL;
+}
+
 int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
 {
+	char *last_branch = substitute_nth_last_branch(&str, &len);
 	const char **p, *r;
 	int refs_found = 0;
 
@@ -261,11 +281,13 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
 				break;
 		}
 	}
+	free(last_branch);
 	return refs_found;
 }
 
 int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
 {
+	char *last_branch = substitute_nth_last_branch(&str, &len);
 	const char **p;
 	int logs_found = 0;
 
@@ -296,6 +318,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
 		if (!warn_ambiguous_refs)
 			break;
 	}
+	free(last_branch);
 	return logs_found;
 }
 
@@ -314,7 +337,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 	/* basic@{time or number or -number} format to query ref-log */
 	reflog_len = at = 0;
 	if (str[len-1] == '}') {
-		for (at = 0; at < len - 1; at++) {
+		for (at = len-2; at >= 0; at--) {
 			if (str[at] == '@' && str[at+1] == '{') {
 				reflog_len = (len-1) - (at+2);
 				len = at;
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
index 72e8322..2d6b31e 100755
--- a/t/t1505-rev-parse-last.sh
+++ b/t/t1505-rev-parse-last.sh
@@ -58,7 +58,7 @@ test_expect_success '@{-1}^2 works' '
 	test_rev_equivalent side^2 @{-1}^2
 '
 
-test_expect_failure '@{-1}@{1} works' '
+test_expect_success '@{-1}@{1} works' '
 	test_rev_equivalent side@{1} @{-1}@{1}
 '
 
-- 
1.6.1.332.g9a59d

```

## Junio C Hamano, 2009-01-17 19:13

Subject: Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <7vljt97nld.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vljt97nld.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <alpine.DEB.1.00.0901171602340.3586@pacific.mpi-cbg.de>

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

> Instead of traversing them twice, we just build a list of branch switches,
> pick the one we're interested in, and free the list again.

Isn't the code keeping them all in core, or am I reading the patch wrong?

If you know that you are interested in the nth-from-the-last switch, and
if you are reading from the beginning, you would need to keep at most n 
last switches you have seen in core, wouldn't you?  

```

## Johannes Schindelin, 2009-01-17 19:29

Subject: Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <alpine.DEB.1.00.0901172028470.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901172028470.3586%40pacific.mpi-cbg.de
In-Reply-To: <7vljt97nld.fsf@gitster.siamese.dyndns.org>

```
Hi,

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

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > Instead of traversing them twice, we just build a list of branch switches,
> > pick the one we're interested in, and free the list again.
> 
> Isn't the code keeping them all in core, or am I reading the patch wrong?
> 
> If you know that you are interested in the nth-from-the-last switch, and
> if you are reading from the beginning, you would need to keep at most n 
> last switches you have seen in core, wouldn't you?  

That is correct.  But this is such a highly uncritical code path that I'd 
like to keep this simple rather than fast.

Ciao,
Dscho

```

## Junio C Hamano, 2009-01-17 19:37

Subject: Re: [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1()
Message-ID: <7v63kd7mgv.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7v63kd7mgv.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <1232208597-29249-4-git-send-email-trast@student.ethz.ch>

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

> Let get_sha1() parse the @{-N} syntax, with docs and tests.
>
> Note that while @{-1}^2, @{-2}~5 and such are supported, @{-1}@{1} is
> currently not allowed.
>
> Signed-off-by: Thomas Rast <trast@student.ethz.ch>
> ...
> @@ -324,6 +326,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
>  		return -1;
>  
>  	if (!len && reflog_len) {
> +		struct strbuf buf = STRBUF_INIT;
> +		int ret;
> +		/* try the @{-N} syntax for n-th checkout */
> +		ret = interpret_nth_last_branch(str+at, &buf);
> +		if (ret > 0) {
> +			/* substitute this branch name and restart */
> +			return get_sha1_1(buf.buf, buf.len, sha1);
> +		} else if (ret == 0) {
> +			return -1;
> +		}

What are the possible failure cases, and what do we want to tell the
end-user?

 - You asked for 3rd but there weren't that many switches yet, and ask
   "git rev-parse --verify @{-3}".

   Are we Ok with "fatal: Needed a single revision" from rev-parse?  Do we
   want to show "fatal: @{-3}: not that many branch switches yet"?

   What happens to "git checkout @{-3}" in this case?  Having checkout say
   "fatal: invalid reference: @{-3}" would be fine in this case, I think.

 - You try "git checkout @{-3}", you were on "frotz" branch back then, but
   the branch does not exist anymore.

   I think you will get "fatal: invalid reference: frotz" from checkout,
   which should be fine.

There also is a case where nth_last_branch() may find something that is
not a branch (e.g. "git checkout HEAD^"), but I am hoping we can label
that as a bug in nth_last_branch() and fix it later.

```

## Junio C Hamano, 2009-01-17 19:57

Subject: Re: [PATCH/RFC v4 4/5] checkout: implement "-" abbreviation, add docs and tests
Message-ID: <7vwsct66yv.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vwsct66yv.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <1232208597-29249-5-git-send-email-trast@student.ethz.ch>

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

> @@ -133,6 +133,10 @@ the conflicted merge in the specified paths.
>  +
>  When this parameter names a non-branch (but still a valid commit object),
>  your HEAD becomes 'detached'.
> ++
> +As a special case, the "`@\{-N\}`" syntax for the N-th last branch
> +checks out the branch (instead of detaching).  You may also specify
> +"`-`" which is synonymous with "`@\{-1\}`".

I mildly disagree with this wording.

The new syntax is supposed to be a new way to name a branch, not a random
non-branch committish that is special cased by "git checkout".  I would
further suggest that we should teach "git rev-parse --symbolic-full-name"
and "git rev-parse --symbolic" about the new syntax, so that scripts can
use the syntax to find out the same information.

The "-" thing deserves a mention here in the documentation.  That _is_ a
special case that only applies to the "git checkout" command.

> diff --git a/builtin-checkout.c b/builtin-checkout.c
> index dc1de06..b0a101b 100644
> --- a/builtin-checkout.c
> +++ b/builtin-checkout.c
> @@ -679,6 +679,9 @@ 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 = "@{-1}";
> +

This is not quite nice as it could be, but it probably is Ok.  If the
interpretation of @{-1} errors out, the user won't see an error message
that talks about "-" but instead the user will see "@{-1}".

Also it will look somewhat inconsistent to the end user who does not know
the internals for "-" claim to be a synonym for @{-1} but it really isn't.
For example, "git checkout -^0" does not work as "git checkout @{-1}^0".

To avoid such confusion, we could instead make "git checkout - <ENTER>" a
synonym for "git checkout @{-1} <ENTER>", without claiming to make "-" a
synonym for "@{-1}".  In other words, "git checkout -" can become a very
narrow, focused special case that does not allow anything else, such as
pathspecs, "--" separator, nor --force and other options.

```

## Junio C Hamano, 2009-01-17 20:02

Subject: Re: [PATCH 6/5] Fix parsing of @{-1}@{1}
Message-ID: <7vsknh66qu.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vsknh66qu.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <alpine.DEB.1.00.0901171907530.3586@pacific.mpi-cbg.de>

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

> To do that, Git no longer looks forward for the '@{' corresponding to the
> closing '}' but backward, and dwim_ref() as well as dwim_log() learnt
> about the @{-<N>} notation.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>
> 	The modifications of dwim_ref() and dwim_log() are probably
> 	more important than the issue I tried to fix...

Good, so we can say things like:

	git log -g @{-1}
        git show-branch -g @{-1}

with this?

By the way, I noticed that without these patch series we show something
when "git rev-parse --verify @{-1}" is asked for.  What is it trying to
show?

```

## Johannes Schindelin, 2009-01-17 21:22

Subject: Re: [PATCH 6/5] Fix parsing of @{-1}@{1}
Message-ID: <alpine.DEB.1.00.0901172214120.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901172214120.3586%40pacific.mpi-cbg.de
In-Reply-To: <7vsknh66qu.fsf@gitster.siamese.dyndns.org>

```
Hi,

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

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > To do that, Git no longer looks forward for the '@{' corresponding to the
> > closing '}' but backward, and dwim_ref() as well as dwim_log() learnt
> > about the @{-<N>} notation.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >
> > 	The modifications of dwim_ref() and dwim_log() are probably
> > 	more important than the issue I tried to fix...
> 
> Good, so we can say things like:
> 
> 	git log -g @{-1}
>         git show-branch -g @{-1}
> 
> with this?

I _hope_ :-)

> By the way, I noticed that without these patch series we show something 
> when "git rev-parse --verify @{-1}" is asked for.  What is it trying to 
> show?

Apparently the same as @{1.Jan}: in get_sha1_basic(), we have this code:

                /* Is it asking for N-th entry, or approxidate? */
                for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
                        char ch = str[at+2+i];
                        if ('0' <= ch && ch <= '9')
                                nth = nth * 10 + ch - '0';
                        else
                                nth = -1;
                }
                if (100000000 <= nth) {
                        at_time = nth;
                        nth = -1;
                } else if (0 <= nth)
                        at_time = 0;
                else {
                        char *tmp = xstrndup(str + at + 2, reflog_len);
                        at_time = approxidate(tmp);
                        free(tmp);
                }

So in the loop, nth is set to -1 because of a non-digit, and later, 
approxidate is called for nth == -1, which does this:

	$ ./test-date now
	now -> bad -> Thu Jan  1 01:00:00 1970
	now -> Sat Jan 17 22:21:20 2009

Ciao,
Dscho

```

## Junio C Hamano, 2009-01-18 00:43

Subject: Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <7vmydp5tqj.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vmydp5tqj.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <alpine.DEB.1.00.0901172028470.3586@pacific.mpi-cbg.de>

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

> That is correct.  But this is such a highly uncritical code path that I'd 
> like to keep this simple rather than fast.

I actually not worried about "fast" at all; it was more about unbounded
memory consumption.

```

## Junio C Hamano, 2009-01-18 00:54

Subject: Re: [PATCH/RFC v4 2/5] sha1_name: tweak @{-N} lookup
Message-ID: <7v4ozx5t8b.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7v4ozx5t8b.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <1232208597-29249-3-git-send-email-trast@student.ethz.ch>

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

> Have the lookup only look at "interesting" checkouts, meaning those
> that tell you "Already on ..." don't count even though they also cause
> a reflog entry.
>
> Let interpret_nth_last_branch() return the number of characters
> parsed, so that git-checkout can verify that the branch spec was
> @{-N}, not @{-1}^2 or something like that.  (The latter will be added
> later.)

Thanks; you seem to have handled the issues I pointed out in response to
my own weatherbaloon patch.  I think it is probably better to squash the
first two (and you take the authorship).

> diff --git a/sha1_name.c b/sha1_name.c
> index 6377264..34e39db 100644
> --- a/sha1_name.c
> +++ b/sha1_name.c
> @@ -685,29 +685,28 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
>  				  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;
> -		}
> +	const char *match = NULL, *target = NULL;
> +	size_t len;
> +
> +	if (!prefixcmp(message, "checkout: moving from ")) {
> +		match = message + strlen("checkout: moving from ");
> +		if ((target = strstr(match, " to ")) != NULL)
> +			target += 4;
>  	}

This drops support for older reflog records, but I think it would be Ok.
This "N-th" support is really meant to be for small number of N anyway.

> -	if (--cb->nth <= 0) {
> -		size_t len = strlen(match);
> -		while (match[len-1] == '\n')
> -			len--;
> +	if (cb->nth-- <= 0) {
>  		strbuf_reset(cb->buf);
>  		strbuf_add(cb->buf, match, len);
>  		return 1;

Hmm, did I have an off-by-one I did not notice? ;-)

>  int interpret_nth_last_branch(const char *name, struct strbuf *buf)
>  {
> -	int nth, i;
> +	int nth;
>  	struct grab_nth_branch_switch_cbdata cb;
> +	const char *brace;
> +	char *num_end;
>  
>  	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)

The removal of "limit to reasonably small recent N" I somewhat have
reservations on, but I think we can later re-add something based on
configuration variable if we need to.

```

## Johannes Schindelin, 2009-01-18 01:12

Subject: Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <alpine.DEB.1.00.0901180201070.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901180201070.3586%40pacific.mpi-cbg.de
In-Reply-To: <7vmydp5tqj.fsf@gitster.siamese.dyndns.org>

```
Hi,

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

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > That is correct.  But this is such a highly uncritical code path that 
> > I'd like to keep this simple rather than fast.
> 
> I actually not worried about "fast" at all; it was more about unbounded 
> memory consumption.

Let's just assume that I make a branch switch per minute, continuously, 
for 90 days (until the reflogs are expired), and let's say that all my 
branchnames have 20 characters.  Conservatively, the memory requirement 
would be 100 * 2000 * 30 = 3 megabyte.

Note: these are the memory requirements after some really unrealistically 
high activity, and the memory is free()d during parameter parsing.

A much more realistical expectation would be to switch branches maybe 20 
times a day, which would amount to something like 36 kilobyte.  And again, 
they are free()d before the action really starts.

Ciao,
Dscho

```

## Junio C Hamano, 2009-01-18 07:25

Subject: Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <7vprilyt1w.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vprilyt1w.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <alpine.DEB.1.00.0901180201070.3586@pacific.mpi-cbg.de>

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

> Note: these are the memory requirements after some really unrealistically 
> high activity, and the memory is free()d during parameter parsing.
>
> A much more realistical expectation would be to switch branches maybe 20 
> times a day, which would amount to something like 36 kilobyte.  And again, 
> they are free()d before the action really starts.

My HEAD reflog is 7MB long with 39000 entries, and among them, 13100
entries have "checkout: moving ".

I know I will never want to switch back to the 10000th from the last
branch.  I am quite sure that I would forget which branch I was on after
switching branches three or four times (hence my original hardcoded
limitation of 10 which "should be plenty").  When I know I only have to
keep track of 10 entries, having to keep track of 13100 entries, even if
it is 36kB (it would actually be 260kB in my case) feels there is
something wrong in the design.

```

## Johannes Schindelin, 2009-01-18 20:59

Subject: Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <alpine.DEB.1.00.0901182152010.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901182152010.3586%40pacific.mpi-cbg.de
In-Reply-To: <7vprilyt1w.fsf@gitster.siamese.dyndns.org>

```
Hi,

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

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > Note: these are the memory requirements after some really unrealistically 
> > high activity, and the memory is free()d during parameter parsing.
> >
> > A much more realistical expectation would be to switch branches maybe 20 
> > times a day, which would amount to something like 36 kilobyte.  And again, 
> > they are free()d before the action really starts.
> 
> My HEAD reflog is 7MB long with 39000 entries, and among them, 13100
> entries have "checkout: moving ".
> 
> I know I will never want to switch back to the 10000th from the last 
> branch.  I am quite sure that I would forget which branch I was on after 
> switching branches three or four times (hence my original hardcoded 
> limitation of 10 which "should be plenty").  When I know I only have to 
> keep track of 10 entries, having to keep track of 13100 entries, even if 
> it is 36kB (it would actually be 260kB in my case) feels there is 
> something wrong in the design.

Hrm.  So let's leave it as a two-pass thing?

Ciao,
Dscho

```

## Junio C Hamano, 2009-01-19 08:08

Subject: Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <7vprijra52.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vprijra52.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <alpine.DEB.1.00.0901182152010.3586@pacific.mpi-cbg.de>

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

> On Sat, 17 Jan 2009, Junio C Hamano wrote:
> ...
>> My HEAD reflog is 7MB long with 39000 entries, and among them, 13100
>> entries have "checkout: moving ".
>> 
>> I know I will never want to switch back to the 10000th from the last 
>> branch.  I am quite sure that I would forget which branch I was on after 
>> switching branches three or four times (hence my original hardcoded 
>> limitation of 10 which "should be plenty").  When I know I only have to 
>> keep track of 10 entries, having to keep track of 13100 entries, even if 
>> it is 36kB (it would actually be 260kB in my case) feels there is 
>> something wrong in the design.
>
> Hrm.  So let's leave it as a two-pass thing?

Well, I would rather be in favor of something like this.

-- >8 --
Subject: interpret_nth_last_branch(): avoid traversing the reflog twice

You can have quite a many reflog entries, but you typically won't recall
which branch you were on after switching branches for more than several
times.

Instead of reading the reflog twice, this reads the branch switching event
and keeps the latest 16 (which is an arbitrary limitation that should be
plenty) such entry, to switch back to the branch we were recently on.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 sha1_name.c              |   48 +++++++++++++++++++++------------------------
 t/t2012-checkout-last.sh |   44 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 66 insertions(+), 26 deletions(-)

diff --git a/sha1_name.c b/sha1_name.c
index 9e1538e..d6622f2 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -684,10 +684,11 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
 	return retval;
 }
 
+#define MAX_PREVIOUS_BRANCH 16
+
 struct grab_nth_branch_switch_cbdata {
-	int counting;
-	int nth;
-	struct strbuf *buf;
+	long cnt;
+	struct strbuf buf[MAX_PREVIOUS_BRANCH];
 };
 
 static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
@@ -697,6 +698,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 	struct grab_nth_branch_switch_cbdata *cb = cb_data;
 	const char *match = NULL, *target = NULL;
 	size_t len;
+	int nth;
 
 	if (!prefixcmp(message, "checkout: moving from ")) {
 		match = message + strlen("checkout: moving from ");
@@ -711,16 +713,9 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 	if (target[len] == '\n' && !strncmp(match, target, len))
 		return 0;
 
-	if (cb->counting) {
-		cb->nth++;
-		return 0;
-	}
-
-	if (cb->nth-- <= 0) {
-		strbuf_reset(cb->buf);
-		strbuf_add(cb->buf, match, len);
-		return 1;
-	}
+	nth = cb->cnt++ % MAX_PREVIOUS_BRANCH;
+	strbuf_reset(&cb->buf[nth]);
+	strbuf_add(&cb->buf[nth], match, len);
 	return 0;
 }
 
@@ -737,7 +732,8 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
  */
 int interpret_nth_last_branch(const char *name, struct strbuf *buf)
 {
-	int nth;
+	long nth;
+	int i;
 	struct grab_nth_branch_switch_cbdata cb;
 	const char *brace;
 	char *num_end;
@@ -750,19 +746,19 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
 	nth = strtol(name+3, &num_end, 10);
 	if (num_end != brace)
 		return -1;
-
-	cb.counting = 1;
-	cb.nth = 0;
-	cb.buf = buf;
-	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
-
-	if (cb.nth < nth)
-		return 0;
-
-	cb.counting = 0;
-	cb.nth -= nth;
-	cb.buf = buf;
+	if (nth <= 0 || MAX_PREVIOUS_BRANCH < nth)
+		return -1;
+	for (i = 0; i < MAX_PREVIOUS_BRANCH; i++)
+		strbuf_init(&cb.buf[i], 20);
+	cb.cnt = 0;
 	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+	if (cb.cnt < nth)
+		return -1;
+	i = (cb.cnt + MAX_PREVIOUS_BRANCH - nth) % MAX_PREVIOUS_BRANCH;
+	strbuf_reset(buf);
+	strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
+	for (i = 0; i < MAX_PREVIOUS_BRANCH; i++)
+		strbuf_release(&cb.buf[i]);
 
 	return brace-name+1;
 }
diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
index 320f6eb..87b30a2 100755
--- a/t/t2012-checkout-last.sh
+++ b/t/t2012-checkout-last.sh
@@ -47,4 +47,48 @@ test_expect_success '"checkout -" detaches again' '
 	test_must_fail git symbolic-ref HEAD
 '
 
+test_expect_success 'more switches' '
+	for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
+	do
+		git checkout -b branch$i
+	done
+'
+
+more_switches () {
+	for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
+	do
+		git checkout branch$i
+	done
+}
+
+test_expect_success 'switch to the last' '
+	more_switches &&
+	git checkout @{-1} &&
+	test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch2"
+'
+
+test_expect_success 'switch to second from the last' '
+	more_switches &&
+	git checkout @{-2} &&
+	test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch3"
+'
+
+test_expect_success 'switch to third from the last' '
+	more_switches &&
+	git checkout @{-3} &&
+	test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch4"
+'
+
+test_expect_success 'switch to fourth from the last' '
+	more_switches &&
+	git checkout @{-4} &&
+	test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch5"
+'
+
+test_expect_success 'switch to twelfth from the last' '
+	more_switches &&
+	git checkout @{-12} &&
+	test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13"
+'
+
 test_done
-- 
1.6.1.245.gdd9f9

```

## Junio C Hamano, 2009-01-19 08:19

Subject: Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <7vljt7r9mq.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vljt7r9mq.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <7vprijra52.fsf@gitster.siamese.dyndns.org>

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

> Well, I would rather be in favor of something like this.
>
> -- >8 --
> Subject: interpret_nth_last_branch(): avoid traversing the reflog twice
>
> You can have quite a many reflog entries, but you typically won't recall
> which branch you were on after switching branches for more than several
> times.
>
> Instead of reading the reflog twice, this reads the branch switching event
> and keeps the latest 16 (which is an arbitrary limitation that should be
> plenty) such entry, to switch back to the branch we were recently on.
>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  sha1_name.c              |   48 +++++++++++++++++++++------------------------
>  t/t2012-checkout-last.sh |   44 ++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 66 insertions(+), 26 deletions(-)
>
> diff --git a/sha1_name.c b/sha1_name.c
> index 9e1538e..d6622f2 100644
> --- a/sha1_name.c
> +++ b/sha1_name.c
> @@ -750,19 +746,19 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
>  	nth = strtol(name+3, &num_end, 10);
>  	if (num_end != brace)
>  		return -1;
> ...
> -	if (cb.nth < nth)
> -		return 0;
> ...
> +	if (cb.cnt < nth)
> +		return -1;

This should (obviously) be "return 0".

```

## Johannes Schindelin, 2009-01-19 12:33

Subject: Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <alpine.DEB.1.00.0901191331590.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901191331590.3586%40pacific.mpi-cbg.de
In-Reply-To: <7vljt7r9mq.fsf@gitster.siamese.dyndns.org>

```
Hi,

On Mon, 19 Jan 2009, Junio C Hamano wrote:

> Junio C Hamano <gitster@pobox.com> writes:
> 
> > Well, I would rather be in favor of something like this.
> >
> > -- >8 --
> > Subject: interpret_nth_last_branch(): avoid traversing the reflog twice
> >
> > You can have quite a many reflog entries, but you typically won't recall
> > which branch you were on after switching branches for more than several
> > times.
> >
> > Instead of reading the reflog twice, this reads the branch switching event
> > and keeps the latest 16 (which is an arbitrary limitation that should be
> > plenty) such entry, to switch back to the branch we were recently on.
> >
> > Signed-off-by: Junio C Hamano <gitster@pobox.com>
> > ---
> >  sha1_name.c              |   48 +++++++++++++++++++++------------------------
> >  t/t2012-checkout-last.sh |   44 ++++++++++++++++++++++++++++++++++++++++++
> >  2 files changed, 66 insertions(+), 26 deletions(-)
> >
> > diff --git a/sha1_name.c b/sha1_name.c
> > index 9e1538e..d6622f2 100644
> > --- a/sha1_name.c
> > +++ b/sha1_name.c
> > @@ -750,19 +746,19 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
> >  	nth = strtol(name+3, &num_end, 10);
> >  	if (num_end != brace)
> >  		return -1;
> > ...
> > -	if (cb.nth < nth)
> > -		return 0;
> > ...
> > +	if (cb.cnt < nth)
> > +		return -1;
> 
> This should (obviously) be "return 0".

This, together with a removal of the hard-coded limit of 16 could be 
squashed with this patch:

-- snipsnap --
diff --git a/sha1_name.c b/sha1_name.c
index 2c5461e..9e5f444 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -691,11 +691,9 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
 	return retval;
 }
 
-#define MAX_PREVIOUS_BRANCH 16
-
 struct grab_nth_branch_switch_cbdata {
-	long cnt;
-	struct strbuf buf[MAX_PREVIOUS_BRANCH];
+	long cnt, alloc;
+	struct strbuf *buf;
 };
 
 static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
@@ -720,7 +718,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 	if (target[len] == '\n' && !strncmp(match, target, len))
 		return 0;
 
-	nth = cb->cnt++ % MAX_PREVIOUS_BRANCH;
+	nth = cb->cnt++ % cb->alloc;
 	strbuf_reset(&cb->buf[nth]);
 	strbuf_add(&cb->buf[nth], match, len);
 	return 0;
@@ -753,19 +751,22 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
 	nth = strtol(name+3, &num_end, 10);
 	if (num_end != brace)
 		return -1;
-	if (nth <= 0 || MAX_PREVIOUS_BRANCH < nth)
+	if (nth <= 0)
 		return -1;
-	for (i = 0; i < MAX_PREVIOUS_BRANCH; i++)
+	cb.alloc = nth;
+	cb.buf = xmalloc(nth * sizeof(struct strbuf));
+	for (i = 0; i < nth; i++)
 		strbuf_init(&cb.buf[i], 20);
 	cb.cnt = 0;
 	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
 	if (cb.cnt < nth)
-		return -1;
-	i = (cb.cnt + MAX_PREVIOUS_BRANCH - nth) % MAX_PREVIOUS_BRANCH;
+		return 0;
+	i = cb.cnt % nth;
 	strbuf_reset(buf);
 	strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
-	for (i = 0; i < MAX_PREVIOUS_BRANCH; i++)
+	for (i = 0; i < nth; i++)
 		strbuf_release(&cb.buf[i]);
+	free(cb.buf);
 
 	return brace-name+1;
 }
-- 
1.6.1.347.g7b62749

```

## Johannes Schindelin, 2009-01-19 12:41

Subject: [PATCH] @{-<n>}: avoid crash with corrupt reflog
Message-ID: <alpine.DEB.1.00.0901191338470.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901191338470.3586%40pacific.mpi-cbg.de
In-Reply-To: <7vljt7r9mq.fsf@gitster.siamese.dyndns.org>

```

The earlier code checked if a " to " was found after "checkout: Moving
from ".  However, it then went on to access the pointer to " to ",
regardless if it was still NULL (if no " to " was found) or not.

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

	You might want to squash this into "sha1_name: tweak @{-N} 
	lookup", just as a safety belt.

 sha1_name.c |   20 ++++++++++----------
 1 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/sha1_name.c b/sha1_name.c
index 9e5f444..853bac6 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -705,18 +705,18 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 	size_t len;
 	int nth;
 
-	if (!prefixcmp(message, "checkout: moving from ")) {
-		match = message + strlen("checkout: moving from ");
-		if ((target = strstr(match, " to ")) != NULL)
-			target += 4;
-	}
-
-	if (!match)
+	if (prefixcmp(message, "checkout: moving from "))
 		return 0;
 
-	len = target - match - 4;
-	if (target[len] == '\n' && !strncmp(match, target, len))
-		return 0;
+	match = message + strlen("checkout: moving from ");
+	if ((target = strstr(match, " to ")) != NULL) {
+		len = target - match - 4;
+		target += 4;
+		if (target[len] == '\n' && !strncmp(match, target, len))
+			return 0;
+	}
+	else
+		len = strchrnul(match, ' ') - match;
 
 	nth = cb->cnt++ % cb->alloc;
 	strbuf_reset(&cb->buf[nth]);
-- 
1.6.1.347.g7b62749

```

## Johannes Schindelin, 2009-01-19 14:57

Subject: Re: [PATCH] @{-<n>}: avoid crash with corrupt reflog
Message-ID: <alpine.DEB.1.00.0901191547220.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901191547220.3586%40pacific.mpi-cbg.de
In-Reply-To: <alpine.DEB.1.00.0901191338470.3586@pacific.mpi-cbg.de>

```
Hi,

On Mon, 19 Jan 2009, Johannes Schindelin wrote:

> diff --git a/sha1_name.c b/sha1_name.c
> index 9e5f444..853bac6 100644
> --- a/sha1_name.c
> +++ b/sha1_name.c
> @@ -705,18 +705,18 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
>  	size_t len;
>  	int nth;
>  
> -	if (!prefixcmp(message, "checkout: moving from ")) {
> -		match = message + strlen("checkout: moving from ");
> -		if ((target = strstr(match, " to ")) != NULL)
> -			target += 4;
> -	}
> -
> -	if (!match)
> +	if (prefixcmp(message, "checkout: moving from "))
>  		return 0;
>  
> -	len = target - match - 4;
> -	if (target[len] == '\n' && !strncmp(match, target, len))
> -		return 0;
> +	match = message + strlen("checkout: moving from ");
> +	if ((target = strstr(match, " to ")) != NULL) {
> +		len = target - match - 4;

Aargh, the "- 4" is wrong, of course.

> +		target += 4;
> +		if (target[len] == '\n' && !strncmp(match, target, len))
> +			return 0;
> +	}
> +	else
> +		len = strchrnul(match, ' ') - match;
>  
>  	nth = cb->cnt++ % cb->alloc;
>  	strbuf_reset(&cb->buf[nth]);

Sorry for the noise,
Dscho

```

## Thomas Rast, 2009-01-20 00:11

Subject: Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <200901200111.38917.trast@student.ethz.ch>
URL: https://gitlist.dev/e/200901200111.38917.trast%40student.ethz.ch
In-Reply-To: <alpine.DEB.1.00.0901191331590.3586@pacific.mpi-cbg.de>

```
Johannes Schindelin wrote:
> > Junio C Hamano <gitster@pobox.com> writes:
> > > You can have quite a many reflog entries, but you typically won't recall
> > > which branch you were on after switching branches for more than several
> > > times.
> 
> This, together with a removal of the hard-coded limit of 16 could be 
> squashed with this patch:

You know, I'm quite puzzled as to why we had working code that could
read the reflog backwards earlier in this thread, but it got shot down
solely based on impact and line counts, and you now have to jump
through hoops to work around the lack of this exact functionality.

So how about I resurrect the part about for_each_reflog_ent() and
_backward(), without touching read_ref_at().  This would actually
avoid the worst (hard to check) part of the patch since the
refactoring of for_each_reflog_ent()'s error checking is quite trivial
and IMHO actually results in more readable code.

I'm just asking because I'm not particularly inclined to do it first
and get rejected _again_.

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

```

## Johannes Schindelin, 2009-01-20 00:23

Subject: Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <alpine.DEB.1.00.0901200121190.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901200121190.3586%40pacific.mpi-cbg.de
In-Reply-To: <200901200111.38917.trast@student.ethz.ch>

```
Hi,

On Tue, 20 Jan 2009, Thomas Rast wrote:

> Johannes Schindelin wrote:
> > > Junio C Hamano <gitster@pobox.com> writes:
> > > > You can have quite a many reflog entries, but you typically won't recall
> > > > which branch you were on after switching branches for more than several
> > > > times.
> > 
> > This, together with a removal of the hard-coded limit of 16 could be 
> > squashed with this patch:
> 
> You know, I'm quite puzzled as to why we had working code that could
> read the reflog backwards earlier in this thread, but it got shot down
> solely based on impact and line counts, and you now have to jump
> through hoops to work around the lack of this exact functionality.

Okay, I should have told you what my two main concerns with the patch 
were.

1) it introduces a lot of code, with a lot of possibility for bugs to hide 
   (and I found it not simple enough to slap my head and say "of course, 
   this is obvious" as I did with Junio's code (except the modulo thing 
   which I had to thing about for half a minute)).

2) on Windows, mmap() is really implemented as xmalloc() && fread().  So 
   all the shortcomings of what Junio said about my array approach would 
   hold true for your approach, too.

Ciao,
Dscho

```

## Thomas Rast, 2009-01-20 00:41

Subject: Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <200901200141.14129.trast@student.ethz.ch>
URL: https://gitlist.dev/e/200901200141.14129.trast%40student.ethz.ch
In-Reply-To: <alpine.DEB.1.00.0901200121190.3586@pacific.mpi-cbg.de>

```
Johannes Schindelin wrote:
> 1) it introduces a lot of code, with a lot of possibility for bugs to hide 

And I can understand in the case of read_ref_at() since there the
translation is really nontrivial (though I'd readily put the blame on
the optimized-to-death original code ;-).  But in the case of
for_each_reflog_ent(), it should be really straightforward.

> 2) on Windows, mmap() is really implemented as xmalloc() && fread().  So 
>    all the shortcomings of what Junio said about my array approach would 
>    hold true for your approach, too.

But the @{} syntax _already_ uses mmap via read_ref_at().  And both
uses of for_each_reflog_ent() I'm aware of, the existing git log -g
and the present @{-N} syntax, have to read to the end anyway because
they're mostly interested in the newer stuff.

So while the mmap() might occasionally grab a few more MB of RAM than
would actually be required with simple line-based input, Windows
has to read the whole reflog no matter what.

(Well, unless we make a for_each_reflog_ent_backward() that can jump
in somewhere near the end and start parsing lines backwards.)

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

```

## Junio C Hamano, 2009-01-20 06:21

Subject: [PATCH] interpret_nth_last_branch(): plug small memleak
Message-ID: <7vvdsamrai.fsf_-_@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vvdsamrai.fsf_-_%40gitster.siamese.dyndns.org
In-Reply-To: <alpine.DEB.1.00.0901191331590.3586@pacific.mpi-cbg.de>

```
The error return path leaked both cb.buf[] strbuf array itself, and the
strings contained in its elements.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 sha1_name.c |   10 +++++++---
 1 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/sha1_name.c b/sha1_name.c
index f54b6cb..4c0370b 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -754,7 +754,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 int interpret_nth_last_branch(const char *name, struct strbuf *buf)
 {
 	long nth;
-	int i;
+	int i, retval;
 	struct grab_nth_branch_switch_cbdata cb;
 	const char *brace;
 	char *num_end;
@@ -774,17 +774,21 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
 	for (i = 0; i < nth; i++)
 		strbuf_init(&cb.buf[i], 20);
 	cb.cnt = 0;
+	retval = 0;
 	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
 	if (cb.cnt < nth)
-		return 0;
+		goto release_return;
 	i = cb.cnt % nth;
 	strbuf_reset(buf);
 	strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
+	retval = brace-name+1;
+
+release_return:
 	for (i = 0; i < nth; i++)
 		strbuf_release(&cb.buf[i]);
 	free(cb.buf);
 
-	return brace-name+1;
+	return retval;
 }
 
 /*
-- 
1.6.1.267.g11c6e

```

## Junio C Hamano, 2009-01-20 06:22

Subject: [PATCH] Introduce for_each_recent_reflog_ent().
Message-ID: <7vpriimr93.fsf_-_@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vpriimr93.fsf_-_%40gitster.siamese.dyndns.org
In-Reply-To: <alpine.DEB.1.00.0901191331590.3586@pacific.mpi-cbg.de>

```
This can be used to scan only the last few kilobytes of a reflog, as a
cheap optimization when the data you are looking for is likely to be
found near the end of it.  The caller is expected to fall back to the
full scan if that is not the case.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 refs.c      |   17 ++++++++++++++++-
 refs.h      |    1 +
 sha1_name.c |    8 +++++++-
 3 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/refs.c b/refs.c
index 33ced65..024211d 100644
--- a/refs.c
+++ b/refs.c
@@ -1453,7 +1453,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
 	return 1;
 }
 
-int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data)
 {
 	const char *logfile;
 	FILE *logfp;
@@ -1464,6 +1464,16 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
 	logfp = fopen(logfile, "r");
 	if (!logfp)
 		return -1;
+
+	if (ofs) {
+		struct stat statbuf;
+		if (fstat(fileno(logfp), &statbuf) ||
+		    statbuf.st_size < ofs ||
+		    fseek(logfp, -ofs, SEEK_END) ||
+		    fgets(buf, sizeof(buf), logfp))
+			return -1;
+	}
+
 	while (fgets(buf, sizeof(buf), logfp)) {
 		unsigned char osha1[20], nsha1[20];
 		char *email_end, *message;
@@ -1497,6 +1507,11 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
 	return ret;
 }
 
+int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+{
+	return for_each_recent_reflog_ent(ref, fn, 0, cb_data);
+}
+
 static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
 {
 	DIR *dir = opendir(git_path("logs/%s", base));
diff --git a/refs.h b/refs.h
index 06ad260..3bb529d 100644
--- a/refs.h
+++ b/refs.h
@@ -60,6 +60,7 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned
 /* iterate over reflog entries */
 typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
 int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
+int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, void *cb_data);
 
 /*
  * Calls the specified function for each reflog file until it returns nonzero,
diff --git a/sha1_name.c b/sha1_name.c
index 4c0370b..38c9f1b 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -775,7 +775,13 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
 		strbuf_init(&cb.buf[i], 20);
 	cb.cnt = 0;
 	retval = 0;
-	for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+	for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
+	if (cb.cnt < nth) {
+		cb.cnt = 0;
+		for (i = 0; i < nth; i++)
+			strbuf_release(&cb.buf[i]);
+		for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+	}
 	if (cb.cnt < nth)
 		goto release_return;
 	i = cb.cnt % nth;
-- 
1.6.1.267.g11c6e

```

## Junio C Hamano, 2009-01-20 08:35

Subject: Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
Message-ID: <7v4ozul6hi.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7v4ozul6hi.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <alpine.DEB.1.00.0901191331590.3586@pacific.mpi-cbg.de>

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

> This, together with a removal of the hard-coded limit of 16 could be 
> squashed with this patch:

Yeah, I think that makes very much sense, not because 16 is too small, but
because it does not make sense to keep track of all 16 when you asked for
the last (or the second from the last) event.

Thanks for a free sanity ;-)

```

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

Subject: Re: [PATCH] Introduce for_each_recent_reflog_ent().
Message-ID: <alpine.DEB.1.00.0901201114250.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901201114250.3586%40pacific.mpi-cbg.de
In-Reply-To: <7vpriimr93.fsf_-_@gitster.siamese.dyndns.org>

```
Hi,

On Mon, 19 Jan 2009, Junio C Hamano wrote:

> This can be used to scan only the last few kilobytes of a reflog, as a 
> cheap optimization when the data you are looking for is likely to be 
> found near the end of it.  The caller is expected to fall back to the 
> full scan if that is not the case.

FWIW I really like it, as it works around mmap() nicely.

Ciao,
Dscho

```

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

Subject: Re: [PATCH] interpret_nth_last_branch(): plug small memleak
Message-ID: <alpine.DEB.1.00.0901201115160.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901201115160.3586%40pacific.mpi-cbg.de
In-Reply-To: <7vvdsamrai.fsf_-_@gitster.siamese.dyndns.org>

```
Hi,

On Mon, 19 Jan 2009, Junio C Hamano wrote:

> The error return path leaked both cb.buf[] strbuf array itself, and the 
> strings contained in its elements.

Oops.  Thanks,
Dscho

```

## Johannes Schindelin, 2009-01-21 00:16

Subject: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
Message-ID: <alpine.DEB.1.00.0901210113500.19014@racer>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901210113500.19014%40racer
In-Reply-To: <alpine.DEB.1.00.0901191331590.3586@pacific.mpi-cbg.de>

```

Please squash in.
---

	On Mon, 19 Jan 2009, Johannes Schindelin wrote:

	> @@ -720,7 +718,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
	>  	if (target[len] == '\n' && !strncmp(match, target, len))
	>  		return 0;

	This code is still not valid, as target[len] can be well after the 
	NUL marker.

	Found by valgrind.

 sha1_name.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/sha1_name.c b/sha1_name.c
index 4d10705..803f9d2 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -735,7 +735,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 	if ((target = strstr(match, " to ")) != NULL) {
 		len = target - match;
 		target += 4;
-		if (target[len] == '\n' && !strncmp(match, target, len))
+		if (len == strlen(target) && !strncmp(match, target, len))
 			return 0;
 	}
 	else
-- 
1.6.1.243.g6c8bb35

```

## Junio C Hamano, 2009-01-21 08:45

Subject: Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
Message-ID: <7vwscpgi7t.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vwscpgi7t.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <alpine.DEB.1.00.0901210113500.19014@racer>

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

>  sha1_name.c |    2 +-
>  1 files changed, 1 insertions(+), 1 deletions(-)
>
> diff --git a/sha1_name.c b/sha1_name.c
> index 4d10705..803f9d2 100644
> --- a/sha1_name.c
> +++ b/sha1_name.c
> @@ -735,7 +735,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
>  	if ((target = strstr(match, " to ")) != NULL) {
>  		len = target - match;
>  		target += 4;
> -		if (target[len] == '\n' && !strncmp(match, target, len))
> +		if (len == strlen(target) && !strncmp(match, target, len))
>  			return 0;
>  	}
>  	else

Actually, I think this patch to a884d0c (sha1_name: tweak @{-N} lookup,
2009-01-17) would make more sense.

-- >8 --
Subject: [PATCH] Simplify parsing branch switching events in reflog

We only accept "checkout: moving from A to B" newer style reflog entries,
in order to pick up A.  There is no point computing where B begins at
after running strstr to locate " to ", nor adding 4 and then subtracting 4
from the same pointer.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 sha1_name.c |    8 ++------
 1 files changed, 2 insertions(+), 6 deletions(-)

diff --git a/sha1_name.c b/sha1_name.c
index 38c9f1b..7d95bbb 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -723,17 +723,13 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 
 	if (!prefixcmp(message, "checkout: moving from ")) {
 		match = message + strlen("checkout: moving from ");
-		if ((target = strstr(match, " to ")) != NULL)
-			target += 4;
+		target = strstr(match, " to ");
 	}
 
 	if (!match || !target)
 		return 0;
 
-	len = target - match - 4;
-	if (target[len] == '\n' && !strncmp(match, target, len))
-		return 0;
-
+	len = target - match;
 	nth = cb->cnt++ % cb->alloc;
 	strbuf_reset(&cb->buf[nth]);
 	strbuf_add(&cb->buf[nth], match, len);
-- 
1.6.1.281.g16db

```

## Thomas Rast, 2009-01-21 09:18

Subject: Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
Message-ID: <200901211019.01493.trast@student.ethz.ch>
URL: https://gitlist.dev/e/200901211019.01493.trast%40student.ethz.ch
In-Reply-To: <7vwscpgi7t.fsf@gitster.siamese.dyndns.org>

```
Junio C Hamano wrote:
> We only accept "checkout: moving from A to B" newer style reflog entries,
> in order to pick up A.  There is no point computing where B begins at
> after running strstr to locate " to ", nor adding 4 and then subtracting 4
> from the same pointer.
[...]
> -	len = target - match - 4;
> -	if (target[len] == '\n' && !strncmp(match, target, len))
> -		return 0;
> -
> +	len = target - match;

Actually the point of that exercise was to ignore branch (non)switches
of the form

  checkout: moving from A to A

I originally thought that this would be desirable behaviour, but now
that it causes so much trouble, I'm not that sure any more.  I still
think it would be more intuitive to not count them as switches (after
all git-checkout says 'Already on "$branch"'), but OTOH 'cd .; cd -'
also stays in the same directory.

Thanks for all the work you (both) are doing on this.  I hope to find
the time to read the current state of the series tonight.

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

```

## Junio C Hamano, 2009-01-21 10:13

Subject: Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
Message-ID: <7vr62xezm8.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vr62xezm8.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <200901211019.01493.trast@student.ethz.ch>

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

> Actually the point of that exercise was to ignore branch (non)switches
> of the form
>
>   checkout: moving from A to A

Ahhh, Ok, that is what I missed.

> I originally thought that this would be desirable behaviour, but now
> that it causes so much trouble, I'm not that sure any more.  I still
> think it would be more intuitive to not count them as switches (after
> all git-checkout says 'Already on "$branch"'), but OTOH 'cd .; cd -'
> also stays in the same directory.

An entry of the form "from A to A" is made only when you explicitly ask to
checkout the current branch by name (i.e. "git checkout" without any
parameter won't add such an entry to the reflog), so I tend to agree with
"cd" that the users may find it more natural if we counted them.

Having said all that, I think Dscho's one had an off-by-one (but it is
getting late and it may be I who has one).

When parsing "checkout: moving from master to side\n", match points at
"master to...", target points at "side\n", and len is 6 (length of
"master").  We want to see if target is "master\n" and ignore such an
entry, so we should be checking if target is one longer than len.

 sha1_name.c |    5 ++++-
 1 files changed, 4 insertions(+), 1 deletions(-)

diff --git c/sha1_name.c w/sha1_name.c
index 38c9f1b..9aed8ae 100644
--- c/sha1_name.c
+++ w/sha1_name.c
@@ -731,7 +731,10 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 		return 0;
 
 	len = target - match - 4;
-	if (target[len] == '\n' && !strncmp(match, target, len))
+	if (len + 1 == strlen(target) &&
+	    target[len] == '\n' &&
+	    !memcmp(target, match, len))
+		/* switching same branch "from A to A\n" */
 		return 0;
 
 	nth = cb->cnt++ % cb->alloc;

```

## Johannes Schindelin, 2009-01-21 11:56

Subject: Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
Message-ID: <alpine.DEB.1.00.0901211256370.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901211256370.3586%40pacific.mpi-cbg.de
In-Reply-To: <7vwscpgi7t.fsf@gitster.siamese.dyndns.org>

```
Hi,

On Wed, 21 Jan 2009, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> >  sha1_name.c |    2 +-
> >  1 files changed, 1 insertions(+), 1 deletions(-)
> >
> > diff --git a/sha1_name.c b/sha1_name.c
> > index 4d10705..803f9d2 100644
> > --- a/sha1_name.c
> > +++ b/sha1_name.c
> > @@ -735,7 +735,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
> >  	if ((target = strstr(match, " to ")) != NULL) {
> >  		len = target - match;
> >  		target += 4;
> > -		if (target[len] == '\n' && !strncmp(match, target, len))
> > +		if (len == strlen(target) && !strncmp(match, target, len))
> >  			return 0;
> >  	}
> >  	else
> 
> Actually, I think this patch to a884d0c (sha1_name: tweak @{-N} lookup,
> 2009-01-17) would make more sense.
> 
> -- >8 --
> Subject: [PATCH] Simplify parsing branch switching events in reflog
> 
> We only accept "checkout: moving from A to B" newer style reflog entries,
> in order to pick up A.  There is no point computing where B begins at
> after running strstr to locate " to ", nor adding 4 and then subtracting 4
> from the same pointer.

Yeah, you're right.

Ciao,
Dscho

```

## Johannes Schindelin, 2009-01-21 12:06

Subject: Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
Message-ID: <alpine.DEB.1.00.0901211306270.3586@pacific.mpi-cbg.de>
URL: https://gitlist.dev/e/alpine.DEB.1.00.0901211306270.3586%40pacific.mpi-cbg.de
In-Reply-To: <7vr62xezm8.fsf@gitster.siamese.dyndns.org>

```
Hi,

On Wed, 21 Jan 2009, Junio C Hamano wrote:

> Having said all that, I think Dscho's one had an off-by-one (but it is
> getting late and it may be I who has one).

Yep, I missed the "\n" at the end.

Ciao,
Dscho

```

## Thomas Rast, 2009-01-24 22:21

Subject: Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
Message-ID: <200901242322.02440.trast@student.ethz.ch>
URL: https://gitlist.dev/e/200901242322.02440.trast%40student.ethz.ch
In-Reply-To: <200901211019.01493.trast@student.ethz.ch>

```
Thomas Rast wrote:
> Thanks for all the work you (both) are doing on this.  I hope to find
> the time to read the current state of the series tonight.

I was off by half a week :-(

The debugging 'git log --graph ...' that I forgot about is still in,
sorry.  I'll follow up with a small patch.  Other than that, I didn't
notice anything, though admittedly it wouldn't have been very hard to
sneak something past me.

By the way, you (Junio) remarked earlier in the thread that we could
forbid any use of 'git checkout -' except in literally that command
(i.e., no options or paths).  I'm midly opposed to it because I can
see myself saying 'git checkout -b sidebranch -' (and @{} is somewhat
awkward to type on Swiss keyboards).  Admittedly 'git checkout - --
file' looks rather confusing.

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

```

## Thomas Rast, 2009-01-24 22:23

Subject: [PATCH next] t1505: remove debugging cruft
Message-ID: <1232835794-22176-1-git-send-email-trast@student.ethz.ch>
URL: https://gitlist.dev/e/1232835794-22176-1-git-send-email-trast%40student.ethz.ch
In-Reply-To: <200901242322.02440.trast@student.ethz.ch>

```
Remove a call to git-log that I introduced for debugging and that
accidentally made it into d18ba22 (sha1_name: support @{-N} syntax in
get_sha1(), 2009-01-17).

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 t/t1505-rev-parse-last.sh |    2 --
 1 files changed, 0 insertions(+), 2 deletions(-)

diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
index c745ec4..d709ecf 100755
--- a/t/t1505-rev-parse-last.sh
+++ b/t/t1505-rev-parse-last.sh
@@ -32,8 +32,6 @@ test_expect_success 'setup' '
 #
 # and 'side' should be the last branch
 
-git log --graph --all --pretty=oneline --decorate
-
 test_rev_equivalent () {
 
 	git rev-parse "$1" > expect &&
-- 
1.6.1.468.g15c0

```

## Junio C Hamano, 2009-01-25 20:35

Subject: Re: [PATCH next] t1505: remove debugging cruft
Message-ID: <7v8wozdsz7.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7v8wozdsz7.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <1232835794-22176-1-git-send-email-trast@student.ethz.ch>

```
Thanks.

```
