{"thread":{"id":"17221","subject":"[PATCH/RFC v3 5/6] sha1_name: implement @{-N} syntax for N-th last checked out","startedAt":"2009-01-17T03:30:05Z","lastAt":"2009-01-25T20:35:24Z","messageCount":56,"participants":["Thomas Rast","Johannes Schindelin","Junio C Hamano"],"isPatch":true,"patchVersion":3,"patchTotal":6},"messages":[{"id":"100795","messageId":"1232163011-20088-1-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"7v8wpcs38c.fsf@gitster.siamese.dyndns.org","subject":"[PATCH/RFC v3 0/6] N-th last checked out branch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T03:30:05Z","receivedAt":"2009-01-17T03:30:05Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n> \"cd -\" is a very good analogy why your \"-\" shortcut is a short-sighted\n> convenience feature that is too narrow and not well designed.  \"cd -\" can\n> go back, but you cannot say \"ls -\" to list the contents of the previous\n> directory.\n\nTrue.\n\n> Another reason is the one level limitation.\n\nIt shares that limitation with my mind ;-)\n\nBut well, since you all seem to want a more general solution, here's a\ndraft.\n\nI started poking around in refs.c for the backwards search, but the\ntwo reflog functions had a lot of functionality implemented very\ndifferently.  So 1-4 are just cleanup and adding that backwards\niterator.  Note that 3/6 changes for_each_reflog_ent() to die() when\nit finds a format error, unlike the previous behaviour.  No tests\nseemed to care, but maybe someone out there relies on having broken\nreflogs? ;-)\n\n>  * The code read the reflog twice, first to count how many branch\n>    switching there are and then to locate the N-th entry we are interested\n>    in, because I was lazy.  We may want an API to enumerate reflog entries\n>    in reverse.\n\nSo this might be settled.\n\n>  * The reflog parser only parses \"checkout\" and not rebase action.  It\n>    also does not notice \"git checkout HEAD^\" is not switching to a real\n>    branch.\n\nI don't handle this, but I'm not sure how much of a problem it is.  It\ncan correctly detach and re-attach even in cases such as HEAD^.\n\nOk, now that you mention it, it will probably put you on the parent of\nwhatever you happened to be on, instead of the earlier value of HEAD^.\n\n>      $ git checkout @{-1}\n\nOne problem with the syntax is that the assumption that there can only\nbe a single @{} construct is quite entrenched in sha1_name.c.  So if\nwe want to support @{-1}@{1}, that'll need some extra work.\n\n@{-1}~2 and similar work fine though.\n\n\nOther things of note:\n\nI changed the semantics to really only look at checkouts that \"did\nsomething\".  If you keep saying 'git checkout master' over and over,\nthose will not count towards the N.\n\nI changed the parser to read the 'checkout: moving from $old' instead\nof the 'to $new'.  The above semantics introduced too many extra\nconditions for my taste if we wanted to support the pre-1.5.3 reflog\ncomment format.  (You'd have to check if the $to was changed from the\nlast [newer] entry, but then there's another border case when you do\n'git init; git checkout -b side; git checkout -' because you never\nmoved to master.)\n\nThe @{date} syntax will be marginally slower after the refactoring\nbecause the old parser carefully avoided parsing numbers where it\ncould.  (I guess it could actually be done as a bisection for an extra\norder of magnitude.)\n\nIt's far too early here to be sending mail.\n\nInteractive rebase needs a \"move hunk\" feature.\n\n\nThomas Rast (6):\n  reflog: refactor parsing and checking\n  reflog: refactor log open+mmap\n  reflog: make for_each_reflog_ent use mmap\n  reflog: add backwards iterator\n  sha1_name: implement @{-N} syntax for N-th last checked out\n  checkout: implement '@{-N}' and '-' special abbreviations\n\n Documentation/git-checkout.txt  |    4 +\n Documentation/git-rev-parse.txt |    3 +\n builtin-checkout.c              |   15 ++-\n cache.h                         |    1 +\n refs.c                          |  285 +++++++++++++++++++++++----------------\n refs.h                          |    1 +\n sha1_name.c                     |   79 +++++++++++-\n t/t1505-rev-parse-last.sh       |   71 ++++++++++\n t/t2012-checkout-last.sh        |   50 +++++++\n 9 files changed, 387 insertions(+), 122 deletions(-)\n create mode 100755 t/t1505-rev-parse-last.sh\n create mode 100755 t/t2012-checkout-last.sh\n"},{"id":"100796","messageId":"1232163011-20088-2-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"1232163011-20088-1-git-send-email-trast@student.ethz.ch","subject":"[PATCH/RFC v3 1/6] reflog: refactor parsing and checking","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T03:30:06Z","receivedAt":"2009-01-17T03:30:06Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"read_ref_at() and for_each_reflog_ent() both had parsing and error\nchecking routines.  Refactor into a separate function that fully\nparses a single entry.  Note that this switches for_each_reflog_ent()\nfrom silently ignoring errors to die().\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n refs.c |  197 ++++++++++++++++++++++++++++++++--------------------------------\n 1 files changed, 98 insertions(+), 99 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 33ced65..4571fac 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1337,24 +1337,68 @@ int create_symref(const char *ref_target, const char *refs_heads_master,\n \treturn 0;\n }\n \n-static char *ref_msg(const char *line, const char *endp)\n+/*\n+ * Check and parse a single reflog line.  Assumes that there is only\n+ * one newline in the range buf[0]..buf[len-1] (but does check that it\n+ * is at buf[len-1]).\n+ */\n+static void parse_reflog_line(const char *buf, int len,\n+\t\t\t      unsigned char *osha1, unsigned char *nsha1,\n+\t\t\t      char **email,\n+\t\t\t      unsigned long *timestamp, int *tz,\n+\t\t\t      char **message,\n+\t\t\t      const char *logname)\n {\n-\tconst char *ep;\n-\tline += 82;\n-\tep = memchr(line, '\\n', endp - line);\n-\tif (!ep)\n-\t\tep = endp;\n-\treturn xmemdupz(line, ep - line);\n+\tstatic char *retbuf = NULL;\n+\tstatic int retbufsz = 0;\n+\tchar *tzstr, *email_end;\n+\n+\tif (len < 83 || buf[len-1] != '\\n')\n+\t\tdie(\"Log %s is corrupt (entry too short or unterminated).\", logname);\n+\n+\tif (get_sha1_hex(buf, osha1) || buf[40] != ' ')\n+\t\tdie(\"Log %s is corrupt (malformed old sha1).\", logname);\n+\n+\tif (get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ')\n+\t\tdie(\"Log %s is corrupt (malformed new sha1).\", logname);\n+\n+\tALLOC_GROW(retbuf, len-82+1, retbufsz);\n+\tmemcpy(retbuf, buf+82, len-82);\n+\tretbuf[len-82] = '\\0';\n+\n+\temail_end = strchr(retbuf, '>');\n+\tif (!email_end || email_end[1] != ' ')\n+\t\tdie(\"Log %s is corrupt (malformed email field).\", logname);\n+\n+\t*email = retbuf;\n+\temail_end[1] = '\\0';\n+\n+\t*timestamp = strtoul(email_end + 2, &tzstr, 10);\n+\tif (!(*timestamp) || !tzstr || tzstr[0] != ' ' ||\n+\t    (tzstr[1] != '+' && tzstr[1] != '-') ||\n+\t    !isdigit(tzstr[2]) || !isdigit(tzstr[3]) ||\n+\t    !isdigit(tzstr[4]) || !isdigit(tzstr[5]))\n+\t\tdie(\"Log %s is corrupt (malformed timezone).\", logname);\n+\tif (!(tzstr[6] == '\\t' || tzstr[6] == '\\n'))\n+\t\tdie(\"Log %s is corrupt (bad message field separator).\", logname);\n+\t*tz = strtoul(tzstr, NULL, 10);\n+\n+\tif (tzstr[6] == '\\t')\n+\t\t*message = tzstr+7;\n+\telse\n+\t\t*message = tzstr+6;\n }\n \n 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)\n {\n-\tconst char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;\n-\tchar *tz_c;\n+\tconst char *logfile, *logdata, *logend, *rec, *start;\n+\tchar *email, *message;\n \tint logfd, tz, reccnt = 0;\n \tstruct stat st;\n \tunsigned long date;\n-\tunsigned char logged_sha1[20];\n+\tunsigned char new_sha1[20];\n+\tunsigned char old_sha1[20];\n+\tunsigned char next_sha1[20];\n \tvoid *log_mapped;\n \tsize_t mapsz;\n \n@@ -1370,86 +1414,55 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *\n \tlogdata = log_mapped;\n \tclose(logfd);\n \n-\tlastrec = NULL;\n \trec = logend = logdata + st.st_size;\n+\tif (logdata < rec && *(rec-1) == '\\n')\n+\t\trec--;\n \twhile (logdata < rec) {\n-\t\treccnt++;\n-\t\tif (logdata < rec && *(rec-1) == '\\n')\n-\t\t\trec--;\n-\t\tlastgt = NULL;\n-\t\twhile (logdata < rec && *(rec-1) != '\\n') {\n-\t\t\trec--;\n-\t\t\tif (*rec == '>')\n-\t\t\t\tlastgt = rec;\n-\t\t}\n-\t\tif (!lastgt)\n-\t\t\tdie(\"Log %s is corrupt.\", logfile);\n-\t\tdate = strtoul(lastgt + 1, &tz_c, 10);\n+\t\tstart = memrchr(logdata, '\\n', rec-logdata);\n+\t\tif (start)\n+\t\t\tstart++;\n+\t\telse\n+\t\t\tstart = logdata;\n+\t\tparse_reflog_line(start, rec-start+1,\n+\t\t\t\t  old_sha1, new_sha1,\n+\t\t\t\t  &email, &date, &tz, &message,\n+\t\t\t\t  logfile);\n+\n+\t\tif (cutoff_time)\n+\t\t\t*cutoff_time = date;\n+\t\tif (cutoff_tz)\n+\t\t\t*cutoff_tz = tz;\n+\t\tif (cutoff_cnt)\n+\t\t\t*cutoff_cnt = reccnt;\n+\t\tif (msg)\n+\t\t\t*msg = message;\n+\n \t\tif (date <= at_time || cnt == 0) {\n-\t\t\ttz = strtoul(tz_c, NULL, 10);\n-\t\t\tif (msg)\n-\t\t\t\t*msg = ref_msg(rec, logend);\n-\t\t\tif (cutoff_time)\n-\t\t\t\t*cutoff_time = date;\n-\t\t\tif (cutoff_tz)\n-\t\t\t\t*cutoff_tz = tz;\n-\t\t\tif (cutoff_cnt)\n-\t\t\t\t*cutoff_cnt = reccnt - 1;\n-\t\t\tif (lastrec) {\n-\t\t\t\tif (get_sha1_hex(lastrec, logged_sha1))\n-\t\t\t\t\tdie(\"Log %s is corrupt.\", logfile);\n-\t\t\t\tif (get_sha1_hex(rec + 41, sha1))\n-\t\t\t\t\tdie(\"Log %s is corrupt.\", logfile);\n-\t\t\t\tif (hashcmp(logged_sha1, sha1)) {\n-\t\t\t\t\tfprintf(stderr,\n-\t\t\t\t\t\t\"warning: Log %s has gap after %s.\\n\",\n-\t\t\t\t\t\tlogfile, show_date(date, tz, DATE_RFC2822));\n-\t\t\t\t}\n-\t\t\t}\n-\t\t\telse if (date == at_time) {\n-\t\t\t\tif (get_sha1_hex(rec + 41, sha1))\n-\t\t\t\t\tdie(\"Log %s is corrupt.\", logfile);\n-\t\t\t}\n-\t\t\telse {\n-\t\t\t\tif (get_sha1_hex(rec + 41, logged_sha1))\n-\t\t\t\t\tdie(\"Log %s is corrupt.\", logfile);\n-\t\t\t\tif (hashcmp(logged_sha1, sha1)) {\n-\t\t\t\t\tfprintf(stderr,\n-\t\t\t\t\t\t\"warning: Log %s unexpectedly ended on %s.\\n\",\n-\t\t\t\t\t\tlogfile, show_date(date, tz, DATE_RFC2822));\n-\t\t\t\t}\n-\t\t\t}\n+\t\t\tif (reccnt && hashcmp(new_sha1, next_sha1))\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t\"warning: Log %s has gap after %s.\\n\",\n+\t\t\t\t\tlogfile, show_date(date, tz, DATE_RFC2822));\n+\t\t\tif (!reccnt && date < at_time && hashcmp(new_sha1, next_sha1))\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t\"warning: Log %s unexpectedly ended on %s.\\n\",\n+\t\t\t\t\tlogfile, show_date(date, tz, DATE_RFC2822));\n+\t\t\t\t/* leave caller's sha1 untouched */\n+\t\t\telse\n+\t\t\t\thashcpy(sha1, new_sha1);\n \t\t\tmunmap(log_mapped, mapsz);\n \t\t\treturn 0;\n \t\t}\n-\t\tlastrec = rec;\n+\n+\t\thashcpy(next_sha1, old_sha1);\n+\t\trec = start-1;\n \t\tif (cnt > 0)\n \t\t\tcnt--;\n+\t\treccnt++;\n \t}\n \n-\trec = logdata;\n-\twhile (rec < logend && *rec != '>' && *rec != '\\n')\n-\t\trec++;\n-\tif (rec == logend || *rec == '\\n')\n-\t\tdie(\"Log %s is corrupt.\", logfile);\n-\tdate = strtoul(rec + 1, &tz_c, 10);\n-\ttz = strtoul(tz_c, NULL, 10);\n-\tif (get_sha1_hex(logdata, sha1))\n-\t\tdie(\"Log %s is corrupt.\", logfile);\n-\tif (is_null_sha1(sha1)) {\n-\t\tif (get_sha1_hex(logdata + 41, sha1))\n-\t\t\tdie(\"Log %s is corrupt.\", logfile);\n-\t}\n-\tif (msg)\n-\t\t*msg = ref_msg(logdata, logend);\n-\tmunmap(log_mapped, mapsz);\n+\thashcpy(sha1, new_sha1);\n \n-\tif (cutoff_time)\n-\t\t*cutoff_time = date;\n-\tif (cutoff_tz)\n-\t\t*cutoff_tz = tz;\n-\tif (cutoff_cnt)\n-\t\t*cutoff_cnt = reccnt;\n+\tmunmap(log_mapped, mapsz);\n \treturn 1;\n }\n \n@@ -1466,30 +1479,16 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)\n \t\treturn -1;\n \twhile (fgets(buf, sizeof(buf), logfp)) {\n \t\tunsigned char osha1[20], nsha1[20];\n-\t\tchar *email_end, *message;\n+\t\tchar *email, *message;\n \t\tunsigned long timestamp;\n \t\tint len, tz;\n \n-\t\t/* old SP new SP name <email> SP time TAB msg LF */\n \t\tlen = strlen(buf);\n-\t\tif (len < 83 || buf[len-1] != '\\n' ||\n-\t\t    get_sha1_hex(buf, osha1) || buf[40] != ' ' ||\n-\t\t    get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ' ||\n-\t\t    !(email_end = strchr(buf + 82, '>')) ||\n-\t\t    email_end[1] != ' ' ||\n-\t\t    !(timestamp = strtoul(email_end + 2, &message, 10)) ||\n-\t\t    !message || message[0] != ' ' ||\n-\t\t    (message[1] != '+' && message[1] != '-') ||\n-\t\t    !isdigit(message[2]) || !isdigit(message[3]) ||\n-\t\t    !isdigit(message[4]) || !isdigit(message[5]))\n-\t\t\tcontinue; /* corrupt? */\n-\t\temail_end[1] = '\\0';\n-\t\ttz = strtol(message + 1, NULL, 10);\n-\t\tif (message[6] != '\\t')\n-\t\t\tmessage += 6;\n-\t\telse\n-\t\t\tmessage += 7;\n-\t\tret = fn(osha1, nsha1, buf+82, timestamp, tz, message, cb_data);\n+\t\tparse_reflog_line(buf, len,\n+\t\t\t\t  osha1, nsha1,\n+\t\t\t\t  &email, &timestamp, &tz, &message,\n+\t\t\t\t  logfile);\n+\t\tret = fn(osha1, nsha1, email, timestamp, tz, message, cb_data);\n \t\tif (ret)\n \t\t\tbreak;\n \t}\n-- \n1.6.1.315.g92577\n"},{"id":"100798","messageId":"1232163011-20088-3-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"1232163011-20088-2-git-send-email-trast@student.ethz.ch","subject":"[PATCH/RFC v3 2/6] reflog: refactor log open+mmap","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T03:30:07Z","receivedAt":"2009-01-17T03:30:07Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Move the open+mmap code from read_ref_at() to a separate function for\nthe next patch.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n refs.c |   54 ++++++++++++++++++++++++++++++++----------------------\n 1 files changed, 32 insertions(+), 22 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 4571fac..0a57896 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1389,12 +1389,31 @@ static void parse_reflog_line(const char *buf, int len,\n \t\t*message = tzstr+6;\n }\n \n+static char *open_reflog(const char *ref, size_t *mapsz, const char **logfile)\n+{\n+\tstruct stat st;\n+\tint logfd;\n+\tchar *map;\n+\n+\t*logfile = git_path(\"logs/%s\", ref);\n+\tlogfd = open(*logfile, O_RDONLY, 0);\n+\tif (logfd < 0)\n+\t\treturn NULL;\n+\tif (fstat(logfd, &st))\n+\t\treturn NULL;\n+\n+\t*mapsz = xsize_t(st.st_size);\n+\tmap = xmmap(NULL, *mapsz, PROT_READ, MAP_PRIVATE, logfd, 0);\n+\tclose(logfd);\n+\treturn map;\n+}\n+\n+\n 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)\n {\n-\tconst char *logfile, *logdata, *logend, *rec, *start;\n+\tconst char *logfile, *logdata, *logend, *rec, *end;\n \tchar *email, *message;\n-\tint logfd, tz, reccnt = 0;\n-\tstruct stat st;\n+\tint tz, reccnt = 0;\n \tunsigned long date;\n \tunsigned char new_sha1[20];\n \tunsigned char old_sha1[20];\n@@ -1402,28 +1421,20 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *\n \tvoid *log_mapped;\n \tsize_t mapsz;\n \n-\tlogfile = git_path(\"logs/%s\", ref);\n-\tlogfd = open(logfile, O_RDONLY, 0);\n-\tif (logfd < 0)\n+\tlogdata = log_mapped = open_reflog(ref, &mapsz, &logfile);\n+\tif (!logdata)\n \t\tdie(\"Unable to read log %s: %s\", logfile, strerror(errno));\n-\tfstat(logfd, &st);\n-\tif (!st.st_size)\n+\tif (!mapsz)\n \t\tdie(\"Log %s is empty.\", logfile);\n-\tmapsz = xsize_t(st.st_size);\n-\tlog_mapped = xmmap(NULL, mapsz, PROT_READ, MAP_PRIVATE, logfd, 0);\n-\tlogdata = log_mapped;\n-\tclose(logfd);\n \n-\trec = logend = logdata + st.st_size;\n-\tif (logdata < rec && *(rec-1) == '\\n')\n-\t\trec--;\n+\trec = logend = logdata + mapsz;\n \twhile (logdata < rec) {\n-\t\tstart = memrchr(logdata, '\\n', rec-logdata);\n-\t\tif (start)\n-\t\t\tstart++;\n-\t\telse\n-\t\t\tstart = logdata;\n-\t\tparse_reflog_line(start, rec-start+1,\n+\t\tif (logdata < rec && rec[-1] == '\\n')\n+\t\t\trec--;\n+\t\tend = rec;\n+\t\twhile (logdata < rec && rec[-1] != '\\n')\n+\t\t\trec--;\n+\t\tparse_reflog_line(rec, end-rec+1,\n \t\t\t\t  old_sha1, new_sha1,\n \t\t\t\t  &email, &date, &tz, &message,\n \t\t\t\t  logfile);\n@@ -1454,7 +1465,6 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *\n \t\t}\n \n \t\thashcpy(next_sha1, old_sha1);\n-\t\trec = start-1;\n \t\tif (cnt > 0)\n \t\t\tcnt--;\n \t\treccnt++;\n-- \n1.6.1.315.g92577\n"},{"id":"100799","messageId":"1232163011-20088-4-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"1232163011-20088-3-git-send-email-trast@student.ethz.ch","subject":"[PATCH/RFC v3 3/6] reflog: make for_each_reflog_ent use mmap","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T03:30:08Z","receivedAt":"2009-01-17T03:30:08Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Now that all building blocks have been refactored, we can implement\nfor_each_reflog_ent in terms of them, so that it uses mmap.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n refs.c |   25 ++++++++++++++++---------\n 1 files changed, 16 insertions(+), 9 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 0a57896..3848aa0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1478,22 +1478,28 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *\n \n int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)\n {\n-\tconst char *logfile;\n-\tFILE *logfp;\n-\tchar buf[1024];\n+\tconst char *log_mapped, *buf, *log_end, *logfile;\n+\tsize_t mapsz;\n \tint ret = 0;\n \n-\tlogfile = git_path(\"logs/%s\", ref);\n-\tlogfp = fopen(logfile, \"r\");\n-\tif (!logfp)\n+\tbuf = log_mapped = open_reflog(ref, &mapsz, &logfile);\n+\tif (!log_mapped)\n \t\treturn -1;\n-\twhile (fgets(buf, sizeof(buf), logfp)) {\n+\tlog_end = buf + mapsz;\n+\n+\twhile (buf < log_end) {\n \t\tunsigned char osha1[20], nsha1[20];\n \t\tchar *email, *message;\n+\t\tconst char *next;\n \t\tunsigned long timestamp;\n \t\tint len, tz;\n \n-\t\tlen = strlen(buf);\n+\t\tnext = memchr(buf, '\\n', log_end - buf);\n+\t\tif (next)\n+\t\t\tnext++;\n+\t\telse\n+\t\t\tnext = log_end;\n+\t\tlen = next - buf;\n \t\tparse_reflog_line(buf, len,\n \t\t\t\t  osha1, nsha1,\n \t\t\t\t  &email, &timestamp, &tz, &message,\n@@ -1501,8 +1507,9 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)\n \t\tret = fn(osha1, nsha1, email, timestamp, tz, message, cb_data);\n \t\tif (ret)\n \t\t\tbreak;\n+\t\tbuf = next;\n \t}\n-\tfclose(logfp);\n+\tmunmap((void*) log_mapped, mapsz);\n \treturn ret;\n }\n \n-- \n1.6.1.315.g92577\n"},{"id":"100800","messageId":"1232163011-20088-5-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"1232163011-20088-4-git-send-email-trast@student.ethz.ch","subject":"[PATCH/RFC v3 4/6] reflog: add backwards iterator","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T03:30:09Z","receivedAt":"2009-01-17T03:30:09Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Add for_each_reflog_ent_backward() which does the same as\nfor_each_reflog_ent(), except it traverses the reflog in backwards\n(newest to oldest) order.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n refs.c |   35 +++++++++++++++++++++++++++++++++++\n refs.h |    1 +\n 2 files changed, 36 insertions(+), 0 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 3848aa0..cc78f63 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1476,6 +1476,41 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *\n \treturn 1;\n }\n \n+int for_each_reflog_ent_backward(const char *ref, each_reflog_ent_fn fn, void *cb_data)\n+{\n+\tconst char *log_mapped, *buf, *log_end, *logfile;\n+\tsize_t mapsz;\n+\tint ret = 0;\n+\n+\tlog_mapped = open_reflog(ref, &mapsz, &logfile);\n+\tif (!log_mapped)\n+\t\treturn -1;\n+\tbuf = log_end = log_mapped + mapsz;\n+\n+\twhile (buf > log_mapped) {\n+\t\tunsigned char osha1[20], nsha1[20];\n+\t\tchar *email, *message;\n+\t\tconst char *end;\n+\t\tunsigned long timestamp;\n+\t\tint tz;\n+\n+\t\twhile (buf > log_mapped && buf[-1] == '\\n')\n+\t\t\tbuf--;\n+\t\tend = buf;\n+\t\twhile (buf > log_mapped && buf[-1] != '\\n')\n+\t\t\tbuf--;\n+\t\tparse_reflog_line(buf, end-buf+1,\n+\t\t\t\t  osha1, nsha1,\n+\t\t\t\t  &email, &timestamp, &tz, &message,\n+\t\t\t\t  logfile);\n+\t\tret = fn(osha1, nsha1, email, timestamp, tz, message, cb_data);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t}\n+\tmunmap((void*) log_mapped, mapsz);\n+\treturn ret;\n+}\n+\n int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)\n {\n \tconst char *log_mapped, *buf, *log_end, *logfile;\ndiff --git a/refs.h b/refs.h\nindex 06ad260..723bddc 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -60,6 +60,7 @@ struct ref_lock {\n /* iterate over reflog entries */\n typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);\n int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);\n+int for_each_reflog_ent_backward(const char *ref, each_reflog_ent_fn fn, void *cb_data);\n \n /*\n  * Calls the specified function for each reflog file until it returns nonzero,\n-- \n1.6.1.315.g92577\n"},{"id":"100794","messageId":"1232163011-20088-6-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"1232163011-20088-5-git-send-email-trast@student.ethz.ch","subject":"[PATCH/RFC v3 5/6] sha1_name: implement @{-N} syntax for N-th last checked out","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T03:30:10Z","receivedAt":"2009-01-17T03:30:10Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Implements a new syntax @{-N} that parses the reflog for the N-th last\ninteresting 'checkout: moving from $branch to $new' entry and\nsubstitutes $branch.  Here, \"interesting\" is defined as $branch !=\n$new.  We then substitute the real branch name for the parse.\n\nFor example:\n\n  git checkout foo\n  git checkout bar\n  git checkout master\n  git checkout master   # did not move, so doesn't count\n  git rev-parse @{-1}   # same as bar\n  git rev-parse @{-2}   # same as foo\n  git rev-parse @{-2}~3 # same as foo~3\n\nThanks to Junio for much of the code.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-rev-parse.txt |    3 +\n cache.h                         |    1 +\n sha1_name.c                     |   79 +++++++++++++++++++++++++++++++++++++-\n t/t1505-rev-parse-last.sh       |   71 +++++++++++++++++++++++++++++++++++\n 4 files changed, 151 insertions(+), 3 deletions(-)\n create mode 100755 t/t1505-rev-parse-last.sh\n\ndiff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\nindex 2921da3..3ccef2f 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -212,6 +212,9 @@ when you run 'git-merge'.\n   reflog of the current branch. For example, if you are on the\n   branch 'blabla', then '@\\{1\\}' means the same as 'blabla@\\{1\\}'.\n \n+* The special construct '@\\{-<n>\\}' means the <n>th branch checked out\n+  before the current one.\n+\n * A suffix '{caret}' to a revision parameter means the first parent of\n   that commit object.  '{caret}<n>' means the <n>th parent (i.e.\n   'rev{caret}'\ndiff --git a/cache.h b/cache.h\nindex 8e1af26..0dd9168 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -663,6 +663,7 @@ static inline unsigned int hexval(unsigned char c)\n extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);\n extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);\n extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);\n+extern int interpret_nth_last_branch(const char *str, struct strbuf *);\n \n extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);\n extern const char *ref_rev_parse_rules[];\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 159c2ab..b2dd302 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -297,6 +297,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n \treturn logs_found;\n }\n \n+static int get_sha1_1(const char *name, int len, unsigned char *sha1);\n+\n static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n {\n \tstatic const char *warning = \"warning: refname '%.*s' is ambiguous.\\n\";\n@@ -307,7 +309,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n \tif (len == 40 && !get_sha1_hex(str, sha1))\n \t\treturn 0;\n \n-\t/* basic@{time or number} format to query ref-log */\n+\t/* basic@{time or number or -number} format to query ref-log */\n \treflog_len = at = 0;\n \tif (str[len-1] == '}') {\n \t\tfor (at = 0; at < len - 1; at++) {\n@@ -324,6 +326,14 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n \t\treturn -1;\n \n \tif (!len && reflog_len) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tint ret;\n+\t\t/* try the @{-N} syntax for n-th checkout */\n+\t\tret = interpret_nth_last_branch(str+at, &buf);\n+\t\tif (ret > 0) {\n+\t\t\t/* substitute this branch name and restart */\n+\t\t\treturn get_sha1_1(buf.buf, buf.len, sha1);\n+\t\t}\n \t\t/* allow \"@{...}\" to mean the current branch reflog */\n \t\trefs_found = dwim_ref(\"HEAD\", 4, sha1, &real_ref);\n \t} else if (reflog_len)\n@@ -379,8 +389,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n \treturn 0;\n }\n \n-static int get_sha1_1(const char *name, int len, unsigned char *sha1);\n-\n static int get_parent(const char *name, int len,\n \t\t      unsigned char *result, int idx)\n {\n@@ -674,6 +682,71 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)\n \treturn retval;\n }\n \n+struct grab_nth_branch_switch_cbdata {\n+\tint nth;\n+\tstruct strbuf *buf;\n+};\n+\n+static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n+\t\t\t\t  const char *email, unsigned long timestamp, int tz,\n+\t\t\t\t  const char *message, void *cb_data)\n+{\n+\tstruct grab_nth_branch_switch_cbdata *cb = cb_data;\n+\tconst char *match = NULL, *target = NULL;\n+\tsize_t len;\n+\n+\tif (!prefixcmp(message, \"checkout: moving from \")) {\n+\t\tmatch = message + strlen(\"checkout: moving from \");\n+\t\tif ((target = strstr(match, \" to \")) != NULL)\n+\t\t\ttarget += 4;\n+\t}\n+\n+\tif (!match)\n+\t\treturn 0;\n+\n+\tlen = target - match - 4;\n+\tif (target[len] == '\\n' && !strncmp(match, target, len))\n+\t\treturn 0;\n+\n+\tif (--cb->nth <= 0) {\n+\t\tstrbuf_reset(cb->buf);\n+\t\tstrbuf_add(cb->buf, match, len);\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * This reads \"@{-N}\" syntax, finds the name of the Nth previous\n+ * branch we were on, and places the name of the branch in the given\n+ * buf and returns 0 if successful.\n+ *\n+ * If the input is not of the accepted format, it returns a negative\n+ * number to signal an error.\n+ */\n+int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n+{\n+\tint nth;\n+\tstruct grab_nth_branch_switch_cbdata cb;\n+\tconst char *brace;\n+\tchar *num_end;\n+\n+\tif (name[0] != '@' || name[1] != '{' || name[2] != '-')\n+\t\treturn -1;\n+\tbrace = strchr(name, '}');\n+\tif (!brace)\n+\t\treturn -1;\n+\tnth = strtol(name+3, &num_end, 10);\n+\tif (num_end != brace)\n+\t\treturn -1;\n+\n+\tcb.nth = nth;\n+\tcb.buf = buf;\n+\tfor_each_reflog_ent_backward(\"HEAD\", grab_nth_branch_switch, &cb);\n+\n+\treturn brace-name+1;\n+}\n+\n /*\n  * This is like \"get_sha1_basic()\", except it allows \"sha1 expressions\",\n  * notably \"xyz^\" for \"parent of xyz\"\ndiff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh\nnew file mode 100755\nindex 0000000..1e49dd2\n--- /dev/null\n+++ b/t/t1505-rev-parse-last.sh\n@@ -0,0 +1,71 @@\n+#!/bin/sh\n+\n+test_description='test @{-N} syntax'\n+\n+. ./test-lib.sh\n+\n+\n+make_commit () {\n+\techo \"$1\" > \"$1\" &&\n+\tgit add \"$1\" &&\n+\tgit commit -m \"$1\"\n+}\n+\n+\n+test_expect_success 'setup' '\n+\n+\tmake_commit 1 &&\n+\tgit branch side &&\n+\tmake_commit 2 &&\n+\tmake_commit 3 &&\n+\tgit checkout side &&\n+\tmake_commit 4 &&\n+\tgit merge master &&\n+\tgit checkout master\n+\n+'\n+\n+# 1 -- 2 -- 3 master\n+#  \\         \\\n+#   \\         \\\n+#    --- 4 --- 5 side\n+#\n+# and 'side' should be the last branch\n+\n+git log --graph --all --pretty=oneline --decorate\n+\n+test_rev_equivalent () {\n+\n+\tgit rev-parse \"$1\" > expect &&\n+\tgit rev-parse \"$2\" > output &&\n+\ttest_cmp expect output\n+\n+}\n+\n+test_expect_success '@{-1} works' '\n+\ttest_rev_equivalent side @{-1}\n+'\n+\n+test_expect_success '@{-1}~2 works' '\n+\ttest_rev_equivalent side~2 @{-1}~2\n+'\n+\n+test_expect_success '@{-1}^2 works' '\n+\ttest_rev_equivalent side^2 @{-1}^2\n+'\n+\n+test_expect_failure '@{-1}@{1} works' '\n+\ttest_rev_equivalent side@{1} @{-1}@{1}\n+'\n+\n+test_expect_success '@{-2} works' '\n+\ttest_rev_equivalent master @{-2}\n+'\n+\n+test_expect_success '@{-3} fails' '\n+\ttest_must_fail git rev-parse @{-3}\n+'\n+\n+test_done\n+\n+\n-- \n1.6.1.315.g92577\n"},{"id":"100797","messageId":"1232163011-20088-7-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"1232163011-20088-6-git-send-email-trast@student.ethz.ch","subject":"[PATCH/RFC v3 6/6] checkout: implement '@{-N}' and '-' special abbreviations","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T03:30:11Z","receivedAt":"2009-01-17T03:30:11Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Checks if the branch to be checked out is either '@{-N}' or the\nspecial shorthand '-' for '@{-1}' (i.e. the last checked out branch).\nIf so, we take it to mean the branch name, not the corresponding SHA,\nso that we check out an attached HEAD on that branch.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-checkout.txt |    4 +++\n builtin-checkout.c             |   15 ++++++++++-\n t/t2012-checkout-last.sh       |   50 ++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 67 insertions(+), 2 deletions(-)\n create mode 100755 t/t2012-checkout-last.sh\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 9cd5151..3bccffa 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -133,6 +133,10 @@ the conflicted merge in the specified paths.\n +\n When this parameter names a non-branch (but still a valid commit object),\n your HEAD becomes 'detached'.\n++\n+As a special case, the \"`@\\{-N\\}`\" syntax for the N-th last branch\n+checks out the branch (instead of detaching).  You may also specify\n+\"`-`\" which is synonymous with \"`@\\{-1\\}`\".\n \n \n Detached HEAD\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex b5dd9c0..b0a101b 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -361,8 +361,16 @@ struct branch_info {\n static void setup_branch_path(struct branch_info *branch)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstrbuf_addstr(&buf, \"refs/heads/\");\n-\tstrbuf_addstr(&buf, branch->name);\n+\tint ret;\n+\n+\tif ((ret = interpret_nth_last_branch(branch->name, &buf))\n+\t    && ret == strlen(branch->name)) {\n+\t\tbranch->name = xstrdup(buf.buf);\n+\t\tstrbuf_splice(&buf, 0, 0, \"refs/heads/\", 11);\n+\t} else {\n+\t\tstrbuf_addstr(&buf, \"refs/heads/\");\n+\t\tstrbuf_addstr(&buf, branch->name);\n+\t}\n \tbranch->path = strbuf_detach(&buf, NULL);\n }\n \n@@ -671,6 +679,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\targ = argv[0];\n \t\thas_dash_dash = (argc > 1) && !strcmp(argv[1], \"--\");\n \n+\t\tif (!strcmp(arg, \"-\"))\n+\t\t\targ = \"@{-1}\";\n+\n \t\tif (get_sha1(arg, rev)) {\n \t\t\tif (has_dash_dash)          /* case (1) */\n \t\t\t\tdie(\"invalid reference: %s\", arg);\ndiff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh\nnew file mode 100755\nindex 0000000..320f6eb\n--- /dev/null\n+++ b/t/t2012-checkout-last.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='checkout can switch to last branch'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\techo hello >world &&\n+\tgit add world &&\n+\tgit commit -m initial &&\n+\tgit branch other &&\n+\techo \"hello again\" >>world &&\n+\tgit add world &&\n+\tgit commit -m second\n+'\n+\n+test_expect_success '\"checkout -\" does not work initially' '\n+\ttest_must_fail git checkout -\n+'\n+\n+test_expect_success 'first branch switch' '\n+\tgit checkout other\n+'\n+\n+test_expect_success '\"checkout -\" switches back' '\n+\tgit checkout - &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/master\"\n+'\n+\n+test_expect_success '\"checkout -\" switches forth' '\n+\tgit checkout - &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/other\"\n+'\n+\n+test_expect_success 'detach HEAD' '\n+\tgit checkout $(git rev-parse HEAD)\n+'\n+\n+test_expect_success '\"checkout -\" attaches again' '\n+\tgit checkout - &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/other\"\n+'\n+\n+test_expect_success '\"checkout -\" detaches again' '\n+\tgit checkout - &&\n+\ttest \"z$(git rev-parse HEAD)\" = \"z$(git rev-parse other)\" &&\n+\ttest_must_fail git symbolic-ref HEAD\n+'\n+\n+test_done\n-- \n1.6.1.315.g92577\n"},{"id":"100805","messageId":"alpine.DEB.1.00.0901170632210.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"1232163011-20088-2-git-send-email-trast@student.ethz.ch","subject":"Re: [PATCH/RFC v3 1/6] reflog: refactor parsing and checking","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-17T05:35:08Z","receivedAt":"2009-01-17T05:35:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 17 Jan 2009, Thomas Rast wrote:\n\n> Note that this switches for_each_reflog_ent() from silently ignoring \n> errors to die().\n\nI am not convinced this is necessary, and neither that it is good.\n\nYou could return int from parse_reflog_line() just like the other \nnon-dying functions.\n\nAnd keep in mind that reflogs could be corrupted for all kinds of reasons; \nwith your patch, reflog handling would fail gracelessly, even if a \nlot could still be salvaged (by ignoring the error with a warning).\n\nCiao,\nDscho\n"},{"id":"100806","messageId":"alpine.DEB.1.00.0901170636280.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"1232163011-20088-3-git-send-email-trast@student.ethz.ch","subject":"Re: [PATCH/RFC v3 2/6] reflog: refactor log open+mmap","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-17T05:40:21Z","receivedAt":"2009-01-17T05:40:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 17 Jan 2009, Thomas Rast wrote:\n\n> +static char *open_reflog(const char *ref, size_t *mapsz, const char **logfile)\n> +{\n> +\tstruct stat st;\n> +\tint logfd;\n> +\tchar *map;\n> +\n> +\t*logfile = git_path(\"logs/%s\", ref);\n\nThat is dangerous.  git_path() returns a pointer to a static buffer.  \nBefore your patch, logfile was a local variable, and one could be \nrelatively sure that git_path() was not called during the lifetime.  Now \nthe lifetime of logfile is no longer as clear-cut, and it is much easier \nto overlook that git_path() must not be called while logfile holds a \nreference to its static buffer.\n\nCiao,\nDscho\n"},{"id":"100807","messageId":"alpine.DEB.1.00.0901170646560.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"1232163011-20088-1-git-send-email-trast@student.ethz.ch","subject":"Re: [PATCH/RFC v3 0/6] N-th last checked out branch","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-17T05:52:22Z","receivedAt":"2009-01-17T05:52:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 17 Jan 2009, Thomas Rast wrote:\n\n>  Documentation/git-checkout.txt  |    4 +\n>  Documentation/git-rev-parse.txt |    3 +\n>  builtin-checkout.c              |   15 ++-\n>  cache.h                         |    1 +\n>  refs.c                          |  285 +++++++++++++++++++++++----------------\n>  refs.h                          |    1 +\n>  sha1_name.c                     |   79 +++++++++++-\n>  t/t1505-rev-parse-last.sh       |   71 ++++++++++\n>  t/t2012-checkout-last.sh        |   50 +++++++\n>  9 files changed, 387 insertions(+), 122 deletions(-)\n>  create mode 100755 t/t1505-rev-parse-last.sh\n>  create mode 100755 t/t2012-checkout-last.sh\n\nLet's quickly compare that to what Junio sent:\n\n builtin-checkout.c |   10 +++++-\n cache.h            |    1 +\n sha1_name.c        |   78 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 87 insertions(+), 2 deletions(-)\n\nGranted, the documentation and tests are nice, but why that big change in \nrefs.c?  After all, the more you change in one go, the more you can break.\n\nLet's take one step back.  The two things that Junio's patch left to be \ndesired (in addition to documentation and tests) are\n\n- reflogs are traversed twice.  While this is not a showstopper, it is \n  easily improved by building a string_list and then picking the one entry \n  we're interested in, and\n\n- the '-' handling you seem to want.\n\nI really have to ask: why did you not work on top of Junio's patch, just \nadding docs, tests, and checkout -?  And then -- maybe -- the \nstring_list...\n\nAlthough I have to admit that I am not _that_ convinced the string_list is \nworth it: reflogs are not evaluated all the time, so it is definitely not \nperformance critical.\n\nCiao,\nDscho\n"},{"id":"100826","messageId":"200901171438.22504.trast@student.ethz.ch","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901170646560.3586@pacific.mpi-cbg.de","subject":"Re: [PATCH/RFC v3 0/6] N-th last checked out branch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T13:38:17Z","receivedAt":"2009-01-17T13:38:17Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Johannes Schindelin wrote:\n> On Sat, 17 Jan 2009, Thomas Rast wrote:\n> >  9 files changed, 387 insertions(+), 122 deletions(-)\n> \n> Let's quickly compare that to what Junio sent:\n> \n>  builtin-checkout.c |   10 +++++-\n>  cache.h            |    1 +\n>  sha1_name.c        |   78 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  3 files changed, 87 insertions(+), 2 deletions(-)\n[...]\n> - the '-' handling you seem to want.\n\nLet's be clear that the handling *I* wanted is this big:\n\n builtin-checkout.c                     |   27 ++++++++++++++++-\n\nIn any case, I'll follow up with a version that still traverses the\nlogs twice and thus doesn't need the first two.\n\n> I really have to ask: why did you not work on top of Junio's patch, just \n> adding docs, tests, and checkout -?  And then -- maybe -- the \n> string_list...\n> \n> Although I have to admit that I am not _that_ convinced the string_list is \n> worth it: reflogs are not evaluated all the time, so it is definitely not \n> performance critical.\n\nI take it you have some idea where and how string_list fits into this\ntopic?\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n\n"},{"id":"100827","messageId":"1232199621-31072-1-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"200901171438.22504.trast@student.ethz.ch","subject":"[PATCH/RFC v3bis 1/2] sha1_name: implement @{-N} syntax for N-th last checked out","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T13:40:20Z","receivedAt":"2009-01-17T13:40:20Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Implements a new syntax @{-N} that parses the reflog for the N-th last\ninteresting 'checkout: moving from $branch to $new' entry and\nsubstitutes $branch.  Here, \"interesting\" is defined as $branch !=\n$new.  We then substitute the real branch name for the parse.\n\nFor example:\n\n  git checkout foo\n  git checkout bar\n  git checkout master\n  git checkout master   # did not move, so doesn't count\n  git rev-parse @{-1}   # same as bar\n  git rev-parse @{-2}   # same as foo\n  git rev-parse @{-2}~3 # same as foo~3\n\nThanks to Junio for much of the code.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-rev-parse.txt |    3 +\n cache.h                         |    1 +\n sha1_name.c                     |   99 +++++++++++++++++++++++++++++++++++++-\n t/t1505-rev-parse-last.sh       |   71 ++++++++++++++++++++++++++++\n 4 files changed, 171 insertions(+), 3 deletions(-)\n create mode 100755 t/t1505-rev-parse-last.sh\n\ndiff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\nindex 2921da3..3ccef2f 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -212,6 +212,9 @@ when you run 'git-merge'.\n   reflog of the current branch. For example, if you are on the\n   branch 'blabla', then '@\\{1\\}' means the same as 'blabla@\\{1\\}'.\n \n+* The special construct '@\\{-<n>\\}' means the <n>th branch checked out\n+  before the current one.\n+\n * A suffix '{caret}' to a revision parameter means the first parent of\n   that commit object.  '{caret}<n>' means the <n>th parent (i.e.\n   'rev{caret}'\ndiff --git a/cache.h b/cache.h\nindex 8e1af26..0dd9168 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -663,6 +663,7 @@ static inline unsigned int hexval(unsigned char c)\n extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);\n extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);\n extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);\n+extern int interpret_nth_last_branch(const char *str, struct strbuf *);\n \n extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);\n extern const char *ref_rev_parse_rules[];\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 159c2ab..9e1538e 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -297,6 +297,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n \treturn logs_found;\n }\n \n+static int get_sha1_1(const char *name, int len, unsigned char *sha1);\n+\n static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n {\n \tstatic const char *warning = \"warning: refname '%.*s' is ambiguous.\\n\";\n@@ -307,7 +309,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n \tif (len == 40 && !get_sha1_hex(str, sha1))\n \t\treturn 0;\n \n-\t/* basic@{time or number} format to query ref-log */\n+\t/* basic@{time or number or -number} format to query ref-log */\n \treflog_len = at = 0;\n \tif (str[len-1] == '}') {\n \t\tfor (at = 0; at < len - 1; at++) {\n@@ -324,6 +326,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n \t\treturn -1;\n \n \tif (!len && reflog_len) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tint ret;\n+\t\t/* try the @{-N} syntax for n-th checkout */\n+\t\tret = interpret_nth_last_branch(str+at, &buf);\n+\t\tif (ret > 0) {\n+\t\t\t/* substitute this branch name and restart */\n+\t\t\treturn get_sha1_1(buf.buf, buf.len, sha1);\n+\t\t} else if (ret == 0) {\n+\t\t\treturn -1;\n+\t\t}\n \t\t/* allow \"@{...}\" to mean the current branch reflog */\n \t\trefs_found = dwim_ref(\"HEAD\", 4, sha1, &real_ref);\n \t} else if (reflog_len)\n@@ -379,8 +391,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n \treturn 0;\n }\n \n-static int get_sha1_1(const char *name, int len, unsigned char *sha1);\n-\n static int get_parent(const char *name, int len,\n \t\t      unsigned char *result, int idx)\n {\n@@ -674,6 +684,89 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)\n \treturn retval;\n }\n \n+struct grab_nth_branch_switch_cbdata {\n+\tint counting;\n+\tint nth;\n+\tstruct strbuf *buf;\n+};\n+\n+static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n+\t\t\t\t  const char *email, unsigned long timestamp, int tz,\n+\t\t\t\t  const char *message, void *cb_data)\n+{\n+\tstruct grab_nth_branch_switch_cbdata *cb = cb_data;\n+\tconst char *match = NULL, *target = NULL;\n+\tsize_t len;\n+\n+\tif (!prefixcmp(message, \"checkout: moving from \")) {\n+\t\tmatch = message + strlen(\"checkout: moving from \");\n+\t\tif ((target = strstr(match, \" to \")) != NULL)\n+\t\t\ttarget += 4;\n+\t}\n+\n+\tif (!match)\n+\t\treturn 0;\n+\n+\tlen = target - match - 4;\n+\tif (target[len] == '\\n' && !strncmp(match, target, len))\n+\t\treturn 0;\n+\n+\tif (cb->counting) {\n+\t\tcb->nth++;\n+\t\treturn 0;\n+\t}\n+\n+\tif (cb->nth-- <= 0) {\n+\t\tstrbuf_reset(cb->buf);\n+\t\tstrbuf_add(cb->buf, match, len);\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * This reads \"@{-N}\" syntax, finds the name of the Nth previous\n+ * branch we were on, and places the name of the branch in the given\n+ * buf and returns the number of characters parsed if successful.\n+ *\n+ * If the input is not of the accepted format, it returns a negative\n+ * number to signal an error.\n+ *\n+ * If the input was ok but there are not N branch switches in the\n+ * reflog, it returns 0.\n+ */\n+int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n+{\n+\tint nth;\n+\tstruct grab_nth_branch_switch_cbdata cb;\n+\tconst char *brace;\n+\tchar *num_end;\n+\n+\tif (name[0] != '@' || name[1] != '{' || name[2] != '-')\n+\t\treturn -1;\n+\tbrace = strchr(name, '}');\n+\tif (!brace)\n+\t\treturn -1;\n+\tnth = strtol(name+3, &num_end, 10);\n+\tif (num_end != brace)\n+\t\treturn -1;\n+\n+\tcb.counting = 1;\n+\tcb.nth = 0;\n+\tcb.buf = buf;\n+\tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n+\n+\tif (cb.nth < nth)\n+\t\treturn 0;\n+\n+\tcb.counting = 0;\n+\tcb.nth -= nth;\n+\tcb.buf = buf;\n+\tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n+\n+\treturn brace-name+1;\n+}\n+\n /*\n  * This is like \"get_sha1_basic()\", except it allows \"sha1 expressions\",\n  * notably \"xyz^\" for \"parent of xyz\"\ndiff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh\nnew file mode 100755\nindex 0000000..1e49dd2\n--- /dev/null\n+++ b/t/t1505-rev-parse-last.sh\n@@ -0,0 +1,71 @@\n+#!/bin/sh\n+\n+test_description='test @{-N} syntax'\n+\n+. ./test-lib.sh\n+\n+\n+make_commit () {\n+\techo \"$1\" > \"$1\" &&\n+\tgit add \"$1\" &&\n+\tgit commit -m \"$1\"\n+}\n+\n+\n+test_expect_success 'setup' '\n+\n+\tmake_commit 1 &&\n+\tgit branch side &&\n+\tmake_commit 2 &&\n+\tmake_commit 3 &&\n+\tgit checkout side &&\n+\tmake_commit 4 &&\n+\tgit merge master &&\n+\tgit checkout master\n+\n+'\n+\n+# 1 -- 2 -- 3 master\n+#  \\         \\\n+#   \\         \\\n+#    --- 4 --- 5 side\n+#\n+# and 'side' should be the last branch\n+\n+git log --graph --all --pretty=oneline --decorate\n+\n+test_rev_equivalent () {\n+\n+\tgit rev-parse \"$1\" > expect &&\n+\tgit rev-parse \"$2\" > output &&\n+\ttest_cmp expect output\n+\n+}\n+\n+test_expect_success '@{-1} works' '\n+\ttest_rev_equivalent side @{-1}\n+'\n+\n+test_expect_success '@{-1}~2 works' '\n+\ttest_rev_equivalent side~2 @{-1}~2\n+'\n+\n+test_expect_success '@{-1}^2 works' '\n+\ttest_rev_equivalent side^2 @{-1}^2\n+'\n+\n+test_expect_failure '@{-1}@{1} works' '\n+\ttest_rev_equivalent side@{1} @{-1}@{1}\n+'\n+\n+test_expect_success '@{-2} works' '\n+\ttest_rev_equivalent master @{-2}\n+'\n+\n+test_expect_success '@{-3} fails' '\n+\ttest_must_fail git rev-parse @{-3}\n+'\n+\n+test_done\n+\n+\n-- \n1.6.1.315.g92577\n"},{"id":"100828","messageId":"1232199621-31072-2-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"1232199621-31072-1-git-send-email-trast@student.ethz.ch","subject":"[PATCH/RFC v3bis 2/2] checkout: implement '@{-N}' and '-' special abbreviations","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T13:40:21Z","receivedAt":"2009-01-17T13:40:21Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Checks if the branch to be checked out is either '@{-N}' or the\nspecial shorthand '-' for '@{-1}' (i.e. the last checked out branch).\nIf so, we take it to mean the branch name, not the corresponding SHA,\nso that we check out an attached HEAD on that branch.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-checkout.txt |    4 +++\n builtin-checkout.c             |   15 ++++++++++-\n t/t2012-checkout-last.sh       |   50 ++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 67 insertions(+), 2 deletions(-)\n create mode 100755 t/t2012-checkout-last.sh\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 9cd5151..3bccffa 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -133,6 +133,10 @@ the conflicted merge in the specified paths.\n +\n When this parameter names a non-branch (but still a valid commit object),\n your HEAD becomes 'detached'.\n++\n+As a special case, the \"`@\\{-N\\}`\" syntax for the N-th last branch\n+checks out the branch (instead of detaching).  You may also specify\n+\"`-`\" which is synonymous with \"`@\\{-1\\}`\".\n \n \n Detached HEAD\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex b5dd9c0..b0a101b 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -361,8 +361,16 @@ struct branch_info {\n static void setup_branch_path(struct branch_info *branch)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstrbuf_addstr(&buf, \"refs/heads/\");\n-\tstrbuf_addstr(&buf, branch->name);\n+\tint ret;\n+\n+\tif ((ret = interpret_nth_last_branch(branch->name, &buf))\n+\t    && ret == strlen(branch->name)) {\n+\t\tbranch->name = xstrdup(buf.buf);\n+\t\tstrbuf_splice(&buf, 0, 0, \"refs/heads/\", 11);\n+\t} else {\n+\t\tstrbuf_addstr(&buf, \"refs/heads/\");\n+\t\tstrbuf_addstr(&buf, branch->name);\n+\t}\n \tbranch->path = strbuf_detach(&buf, NULL);\n }\n \n@@ -671,6 +679,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\targ = argv[0];\n \t\thas_dash_dash = (argc > 1) && !strcmp(argv[1], \"--\");\n \n+\t\tif (!strcmp(arg, \"-\"))\n+\t\t\targ = \"@{-1}\";\n+\n \t\tif (get_sha1(arg, rev)) {\n \t\t\tif (has_dash_dash)          /* case (1) */\n \t\t\t\tdie(\"invalid reference: %s\", arg);\ndiff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh\nnew file mode 100755\nindex 0000000..320f6eb\n--- /dev/null\n+++ b/t/t2012-checkout-last.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='checkout can switch to last branch'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\techo hello >world &&\n+\tgit add world &&\n+\tgit commit -m initial &&\n+\tgit branch other &&\n+\techo \"hello again\" >>world &&\n+\tgit add world &&\n+\tgit commit -m second\n+'\n+\n+test_expect_success '\"checkout -\" does not work initially' '\n+\ttest_must_fail git checkout -\n+'\n+\n+test_expect_success 'first branch switch' '\n+\tgit checkout other\n+'\n+\n+test_expect_success '\"checkout -\" switches back' '\n+\tgit checkout - &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/master\"\n+'\n+\n+test_expect_success '\"checkout -\" switches forth' '\n+\tgit checkout - &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/other\"\n+'\n+\n+test_expect_success 'detach HEAD' '\n+\tgit checkout $(git rev-parse HEAD)\n+'\n+\n+test_expect_success '\"checkout -\" attaches again' '\n+\tgit checkout - &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/other\"\n+'\n+\n+test_expect_success '\"checkout -\" detaches again' '\n+\tgit checkout - &&\n+\ttest \"z$(git rev-parse HEAD)\" = \"z$(git rev-parse other)\" &&\n+\ttest_must_fail git symbolic-ref HEAD\n+'\n+\n+test_done\n-- \n1.6.1.315.g92577\n"},{"id":"100831","messageId":"alpine.DEB.1.00.0901171602340.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"200901171438.22504.trast@student.ethz.ch","subject":"[PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-17T15:04:08Z","receivedAt":"2009-01-17T15:04:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nInstead of traversing them twice, we just build a list of branch switches,\npick the one we're interested in, and free the list again.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tOn Sat, 17 Jan 2009, Thomas Rast wrote:\n\n\t> Johannes Schindelin wrote:\n\t> \n\t> > I really have to ask: why did you not work on top of Junio's \n\t> > patch, just adding docs, tests, and checkout -?  And then -- maybe -- \n\t> > the string_list...\n\n\tOf course, I meant the patch as-is, with Junio as author.  But \n\they, if he does not care...\n\n\t> > Although I have to admit that I am not _that_ convinced the \n\t> > string_list is worth it: reflogs are not evaluated all the time, so \n\t> > it is definitely not performance critical.\n\t>\n\t> I take it you have some idea where and how string_list fits into \n\t> this topic?\n\n\tIndeed.\n\n\tThis patch was generated using --collapse-non-alnums for readability.\n\tDid not really help much.\n\n sha1_name.c |   72 ++++++++++++++++++++++++----------------------------------\n 1 files changed, 30 insertions(+), 42 deletions(-)\n\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 2bbc5f1..306d04b 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -6,6 +6,7 @@\n #include \"tree-walk.h\"\n #include \"refs.h\"\n #include \"cache-tree.h\"\n+#include \"string-list.h\"\n \n static int find_short_object_filename(int len, const char *name, unsigned char *sha1)\n {\n@@ -691,43 +692,31 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)\n \treturn retval;\n }\n \n-struct grab_nth_branch_switch_cbdata {\n-\tint counting;\n-\tint nth;\n-\tstruct strbuf *buf;\n-};\n-\n-static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n+static int add_one_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n \t\t\t\t  const char *email, unsigned long timestamp, int tz,\n \t\t\t\t  const char *message, void *cb_data)\n {\n-\tstruct grab_nth_branch_switch_cbdata *cb = cb_data;\n+\tstruct string_list *list = cb_data;\n \tconst char *match = NULL, *target = NULL;\n \tsize_t len;\n-\n-\tif (!prefixcmp(message, \"checkout: moving from \")) {\n-\t\tmatch = message + strlen(\"checkout: moving from \");\n-\t\tif ((target = strstr(match, \" to \")) != NULL)\n-\t\t\ttarget += 4;\n-\t}\n-\n-\tif (!match)\n+\n+\tif (prefixcmp(message, \"checkout: moving from \"))\n \t\treturn 0;\n \n-\tlen = target - match - 4;\n-\tif (target[len] == '\\n' && !strncmp(match, target, len))\n-\t\treturn 0;\n-\n-\tif (cb->counting) {\n-\t\tcb->nth++;\n-\t\treturn 0;\n-\t}\n-\n-\tif (cb->nth-- <= 0) {\n-\t\tstrbuf_reset(cb->buf);\n-\t\tstrbuf_add(cb->buf, match, len);\n-\t\treturn 1;\n-\t}\n+\tmatch = message + strlen(\"checkout: moving from \");\n+\n+\t/* Is it \"moving\" from a branch to itself?  Then ignore it. */\n+\tif ((target = strstr(match, \" to \")) != NULL) {\n+\t\ttarget += 4;\n+\t\tlen = target - match - 4;\n+\t\tif (target[len] == '\\n' && !strncmp(match, target, len))\n+\t\t\treturn 0;\n+\t}\n+\telse\n+\t\tlen = strchrnul(match, ' ') - match;\n+\n+\tstring_list_append(xstrndup(match, len), list);\n+\n \treturn 0;\n }\n \n@@ -745,7 +734,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n {\n \tint nth;\n-\tstruct grab_nth_branch_switch_cbdata cb;\n+\tstruct string_list branch_list = { NULL, 0, 0, 0 };\n \tconst char *brace;\n \tchar *num_end;\n \n@@ -758,18 +747,17 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n \tif (num_end != brace)\n \t\treturn -1;\n \n-\tcb.counting = 1;\n-\tcb.nth = 0;\n-\tcb.buf = buf;\n-\tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n-\n-\tif (cb.nth < nth)\n+\tfor_each_reflog_ent(\"HEAD\", add_one_branch_switch, &branch_list);\n+\n+\tif (branch_list.nr < nth)\n \t\treturn 0;\n-\n-\tcb.counting = 0;\n-\tcb.nth -= nth;\n-\tcb.buf = buf;\n-\tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n+\n+\tstrbuf_reset(buf);\n+\tstrbuf_addstr(buf, branch_list.items[branch_list.nr - nth].string);\n+\n+\t/* force free()ing the items */\n+\tbranch_list.strdup_strings = 1;\n+\tstring_list_clear(&branch_list, 0);\n \n \treturn brace-name+1;\n }\n-- \n1.6.1.325.g062d4\n"},{"id":"100849","messageId":"1232208597-29249-1-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901171602340.3586@pacific.mpi-cbg.de","subject":"[PATCH/RFC v4 0/5] N-th last checked out branch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T16:09:52Z","receivedAt":"2009-01-17T16:09:52Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Johannes Schindelin wrote:\n> \n> Instead of traversing them twice, we just build a list of branch switches,\n> pick the one we're interested in, and free the list again.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> \n> On Sat, 17 Jan 2009, Thomas Rast wrote:\n> \n> > Johannes Schindelin wrote:\n> > \n> > > I really have to ask: why did you not work on top of Junio's \n> > > patch, just adding docs, tests, and checkout -?  And then -- maybe -- \n> > > the string_list...\n> \n> Of course, I meant the patch as-is, with Junio as author.  But \n> hey, if he does not care...\n\n*shrug*\n\nFor one thing there was no commit message.\n\nI made up a message, split the changes I made into smaller patches,\nand added a fixed up version of your patch on top, since it had some\ncontext that is not in any version I have.\n\n\nJohannes Schindelin (1):\n  interpret_nth_last_branch(): avoid traversing the reflogs twice\n\nJunio C Hamano (1):\n  checkout: implement \"@{-N}\" shortcut name for N-th last branch\n\nThomas Rast (3):\n  sha1_name: tweak @{-N} lookup\n  sha1_name: support @{-N} syntax in get_sha1()\n  checkout: implement \"-\" abbreviation, add docs and tests\n\n Documentation/git-checkout.txt  |    4 ++\n Documentation/git-rev-parse.txt |    3 +\n builtin-checkout.c              |   15 ++++++-\n cache.h                         |    1 +\n sha1_name.c                     |   88 +++++++++++++++++++++++++++++++++++++-\n t/t1505-rev-parse-last.sh       |   71 +++++++++++++++++++++++++++++++\n t/t2012-checkout-last.sh        |   50 ++++++++++++++++++++++\n 7 files changed, 227 insertions(+), 5 deletions(-)\n create mode 100755 t/t1505-rev-parse-last.sh\n create mode 100755 t/t2012-checkout-last.sh\n"},{"id":"100852","messageId":"1232208597-29249-2-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"1232208597-29249-1-git-send-email-trast@student.ethz.ch","subject":"[PATCH/RFC v4 1/5] checkout: implement \"@{-N}\" shortcut name for N-th last branch","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T16:09:53Z","receivedAt":"2009-01-17T16:09:53Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nImplement a shortcut @{-N} for the N-th last branch checked out, that\nworks by parsing the reflog for the message added by previous\ngit-checkout invocations.  We expand the @{-N} to the branch name, so\nthat you end up on an attached HEAD on that branch.\n---\n builtin-checkout.c |   10 +++++-\n cache.h            |    1 +\n sha1_name.c        |   78 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 87 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex b5dd9c0..a3b69d6 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -361,8 +361,14 @@ struct branch_info {\n static void setup_branch_path(struct branch_info *branch)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstrbuf_addstr(&buf, \"refs/heads/\");\n-\tstrbuf_addstr(&buf, branch->name);\n+\n+\tif (!interpret_nth_last_branch(branch->name, &buf)) {\n+\t\tbranch->name = xstrdup(buf.buf);\n+\t\tstrbuf_splice(&buf, 0, 0, \"refs/heads/\", 11);\n+\t} else {\n+\t\tstrbuf_addstr(&buf, \"refs/heads/\");\n+\t\tstrbuf_addstr(&buf, branch->name);\n+\t}\n \tbranch->path = strbuf_detach(&buf, NULL);\n }\n \ndiff --git a/cache.h b/cache.h\nindex 8e1af26..0dd9168 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -663,6 +663,7 @@ static inline unsigned int hexval(unsigned char c)\n extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);\n extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);\n extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);\n+extern int interpret_nth_last_branch(const char *str, struct strbuf *);\n \n extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);\n extern const char *ref_rev_parse_rules[];\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 159c2ab..6377264 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -674,6 +674,84 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)\n \treturn retval;\n }\n \n+struct grab_nth_branch_switch_cbdata {\n+\tint counting;\n+\tint nth;\n+\tstruct strbuf *buf;\n+};\n+\n+static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n+\t\t\t\t  const char *email, unsigned long timestamp, int tz,\n+\t\t\t\t  const char *message, void *cb_data)\n+{\n+\tstruct grab_nth_branch_switch_cbdata *cb = cb_data;\n+\tconst char *match = NULL;\n+\n+\tif (!prefixcmp(message, \"checkout: moving to \"))\n+\t\tmatch = message + strlen(\"checkout: moving to \");\n+\telse if (!prefixcmp(message, \"checkout: moving from \")) {\n+\t\tconst char *cp = message + strlen(\"checkout: moving from \");\n+\t\tif ((cp = strstr(cp, \" to \")) != NULL) {\n+\t\t\tmatch = cp + 4;\n+\t\t}\n+\t}\n+\n+\tif (!match)\n+\t\treturn 0;\n+\n+\tif (cb->counting) {\n+\t\tcb->nth++;\n+\t\treturn 0;\n+\t}\n+\n+\tif (--cb->nth <= 0) {\n+\t\tsize_t len = strlen(match);\n+\t\twhile (match[len-1] == '\\n')\n+\t\t\tlen--;\n+\t\tstrbuf_reset(cb->buf);\n+\t\tstrbuf_add(cb->buf, match, len);\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * This reads \"@{-N}\" syntax, finds the name of the Nth previous\n+ * branch we were on, and places the name of the branch in the given\n+ * buf and returns 0 if successful.\n+ *\n+ * If the input is not of the accepted format, it returns a negative\n+ * number to signal an error.\n+ */\n+int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n+{\n+\tint nth, i;\n+\tstruct grab_nth_branch_switch_cbdata cb;\n+\n+\tif (name[0] != '@' || name[1] != '{' || name[2] != '-')\n+\t\treturn -1;\n+\tfor (i = 3, nth = 0; name[i] && name[i] != '}'; i++) {\n+\t\tchar ch = name[i];\n+\t\tif ('0' <= ch && ch <= '9')\n+\t\t\tnth = nth * 10 + ch - '0';\n+\t\telse\n+\t\t\treturn -1;\n+\t}\n+\tif (nth < 0 || 10 <= nth)\n+\t\treturn -1;\n+\n+\tcb.counting = 1;\n+\tcb.nth = 0;\n+\tcb.buf = buf;\n+\tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n+\n+\tcb.counting = 0;\n+\tcb.nth -= nth;\n+\tcb.buf = buf;\n+\tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n+\treturn 0;\n+}\n+\n /*\n  * This is like \"get_sha1_basic()\", except it allows \"sha1 expressions\",\n  * notably \"xyz^\" for \"parent of xyz\"\n-- \n1.6.1.315.g92577\n"},{"id":"100851","messageId":"1232208597-29249-3-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"1232208597-29249-2-git-send-email-trast@student.ethz.ch","subject":"[PATCH/RFC v4 2/5] sha1_name: tweak @{-N} lookup","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T16:09:54Z","receivedAt":"2009-01-17T16:09:54Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Have the lookup only look at \"interesting\" checkouts, meaning those\nthat tell you \"Already on ...\" don't count even though they also cause\na reflog entry.\n\nLet interpret_nth_last_branch() return the number of characters\nparsed, so that git-checkout can verify that the branch spec was\n@{-N}, not @{-1}^2 or something like that.  (The latter will be added\nlater.)\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n builtin-checkout.c |    4 ++-\n sha1_name.c        |   53 ++++++++++++++++++++++++++++-----------------------\n 2 files changed, 32 insertions(+), 25 deletions(-)\n\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex a3b69d6..dc1de06 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -361,8 +361,10 @@ struct branch_info {\n static void setup_branch_path(struct branch_info *branch)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n+\tint ret;\n \n-\tif (!interpret_nth_last_branch(branch->name, &buf)) {\n+\tif ((ret = interpret_nth_last_branch(branch->name, &buf))\n+\t    && ret == strlen(branch->name)) {\n \t\tbranch->name = xstrdup(buf.buf);\n \t\tstrbuf_splice(&buf, 0, 0, \"refs/heads/\", 11);\n \t} else {\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 6377264..34e39db 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -685,29 +685,28 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n \t\t\t\t  const char *message, void *cb_data)\n {\n \tstruct grab_nth_branch_switch_cbdata *cb = cb_data;\n-\tconst char *match = NULL;\n-\n-\tif (!prefixcmp(message, \"checkout: moving to \"))\n-\t\tmatch = message + strlen(\"checkout: moving to \");\n-\telse if (!prefixcmp(message, \"checkout: moving from \")) {\n-\t\tconst char *cp = message + strlen(\"checkout: moving from \");\n-\t\tif ((cp = strstr(cp, \" to \")) != NULL) {\n-\t\t\tmatch = cp + 4;\n-\t\t}\n+\tconst char *match = NULL, *target = NULL;\n+\tsize_t len;\n+\n+\tif (!prefixcmp(message, \"checkout: moving from \")) {\n+\t\tmatch = message + strlen(\"checkout: moving from \");\n+\t\tif ((target = strstr(match, \" to \")) != NULL)\n+\t\t\ttarget += 4;\n \t}\n \n \tif (!match)\n \t\treturn 0;\n \n+\tlen = target - match - 4;\n+\tif (target[len] == '\\n' && !strncmp(match, target, len))\n+\t\treturn 0;\n+\n \tif (cb->counting) {\n \t\tcb->nth++;\n \t\treturn 0;\n \t}\n \n-\tif (--cb->nth <= 0) {\n-\t\tsize_t len = strlen(match);\n-\t\twhile (match[len-1] == '\\n')\n-\t\t\tlen--;\n+\tif (cb->nth-- <= 0) {\n \t\tstrbuf_reset(cb->buf);\n \t\tstrbuf_add(cb->buf, match, len);\n \t\treturn 1;\n@@ -718,26 +717,28 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n /*\n  * This reads \"@{-N}\" syntax, finds the name of the Nth previous\n  * branch we were on, and places the name of the branch in the given\n- * buf and returns 0 if successful.\n+ * buf and returns the number of characters parsed if successful.\n  *\n  * If the input is not of the accepted format, it returns a negative\n  * number to signal an error.\n+ *\n+ * If the input was ok but there are not N branch switches in the\n+ * reflog, it returns 0.\n  */\n int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n {\n-\tint nth, i;\n+\tint nth;\n \tstruct grab_nth_branch_switch_cbdata cb;\n+\tconst char *brace;\n+\tchar *num_end;\n \n \tif (name[0] != '@' || name[1] != '{' || name[2] != '-')\n \t\treturn -1;\n-\tfor (i = 3, nth = 0; name[i] && name[i] != '}'; i++) {\n-\t\tchar ch = name[i];\n-\t\tif ('0' <= ch && ch <= '9')\n-\t\t\tnth = nth * 10 + ch - '0';\n-\t\telse\n-\t\t\treturn -1;\n-\t}\n-\tif (nth < 0 || 10 <= nth)\n+\tbrace = strchr(name, '}');\n+\tif (!brace)\n+\t\treturn -1;\n+\tnth = strtol(name+3, &num_end, 10);\n+\tif (num_end != brace)\n \t\treturn -1;\n \n \tcb.counting = 1;\n@@ -745,11 +746,15 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n \tcb.buf = buf;\n \tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n \n+\tif (cb.nth < nth)\n+\t\treturn 0;\n+\n \tcb.counting = 0;\n \tcb.nth -= nth;\n \tcb.buf = buf;\n \tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n-\treturn 0;\n+\n+\treturn brace-name+1;\n }\n \n /*\n-- \n1.6.1.315.g92577\n"},{"id":"100854","messageId":"1232208597-29249-4-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"1232208597-29249-3-git-send-email-trast@student.ethz.ch","subject":"[PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1()","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T16:09:55Z","receivedAt":"2009-01-17T16:09:55Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Let get_sha1() parse the @{-N} syntax, with docs and tests.\n\nNote that while @{-1}^2, @{-2}~5 and such are supported, @{-1}@{1} is\ncurrently not allowed.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-rev-parse.txt |    3 ++\n sha1_name.c                     |   16 +++++++--\n t/t1505-rev-parse-last.sh       |   71 +++++++++++++++++++++++++++++++++++++++\n 3 files changed, 87 insertions(+), 3 deletions(-)\n create mode 100755 t/t1505-rev-parse-last.sh\n\ndiff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\nindex 2921da3..3ccef2f 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -212,6 +212,9 @@ when you run 'git-merge'.\n   reflog of the current branch. For example, if you are on the\n   branch 'blabla', then '@\\{1\\}' means the same as 'blabla@\\{1\\}'.\n \n+* The special construct '@\\{-<n>\\}' means the <n>th branch checked out\n+  before the current one.\n+\n * A suffix '{caret}' to a revision parameter means the first parent of\n   that commit object.  '{caret}<n>' means the <n>th parent (i.e.\n   'rev{caret}'\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 34e39db..9e1538e 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -297,6 +297,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n \treturn logs_found;\n }\n \n+static int get_sha1_1(const char *name, int len, unsigned char *sha1);\n+\n static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n {\n \tstatic const char *warning = \"warning: refname '%.*s' is ambiguous.\\n\";\n@@ -307,7 +309,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n \tif (len == 40 && !get_sha1_hex(str, sha1))\n \t\treturn 0;\n \n-\t/* basic@{time or number} format to query ref-log */\n+\t/* basic@{time or number or -number} format to query ref-log */\n \treflog_len = at = 0;\n \tif (str[len-1] == '}') {\n \t\tfor (at = 0; at < len - 1; at++) {\n@@ -324,6 +326,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n \t\treturn -1;\n \n \tif (!len && reflog_len) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tint ret;\n+\t\t/* try the @{-N} syntax for n-th checkout */\n+\t\tret = interpret_nth_last_branch(str+at, &buf);\n+\t\tif (ret > 0) {\n+\t\t\t/* substitute this branch name and restart */\n+\t\t\treturn get_sha1_1(buf.buf, buf.len, sha1);\n+\t\t} else if (ret == 0) {\n+\t\t\treturn -1;\n+\t\t}\n \t\t/* allow \"@{...}\" to mean the current branch reflog */\n \t\trefs_found = dwim_ref(\"HEAD\", 4, sha1, &real_ref);\n \t} else if (reflog_len)\n@@ -379,8 +391,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n \treturn 0;\n }\n \n-static int get_sha1_1(const char *name, int len, unsigned char *sha1);\n-\n static int get_parent(const char *name, int len,\n \t\t      unsigned char *result, int idx)\n {\ndiff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh\nnew file mode 100755\nindex 0000000..1e49dd2\n--- /dev/null\n+++ b/t/t1505-rev-parse-last.sh\n@@ -0,0 +1,71 @@\n+#!/bin/sh\n+\n+test_description='test @{-N} syntax'\n+\n+. ./test-lib.sh\n+\n+\n+make_commit () {\n+\techo \"$1\" > \"$1\" &&\n+\tgit add \"$1\" &&\n+\tgit commit -m \"$1\"\n+}\n+\n+\n+test_expect_success 'setup' '\n+\n+\tmake_commit 1 &&\n+\tgit branch side &&\n+\tmake_commit 2 &&\n+\tmake_commit 3 &&\n+\tgit checkout side &&\n+\tmake_commit 4 &&\n+\tgit merge master &&\n+\tgit checkout master\n+\n+'\n+\n+# 1 -- 2 -- 3 master\n+#  \\         \\\n+#   \\         \\\n+#    --- 4 --- 5 side\n+#\n+# and 'side' should be the last branch\n+\n+git log --graph --all --pretty=oneline --decorate\n+\n+test_rev_equivalent () {\n+\n+\tgit rev-parse \"$1\" > expect &&\n+\tgit rev-parse \"$2\" > output &&\n+\ttest_cmp expect output\n+\n+}\n+\n+test_expect_success '@{-1} works' '\n+\ttest_rev_equivalent side @{-1}\n+'\n+\n+test_expect_success '@{-1}~2 works' '\n+\ttest_rev_equivalent side~2 @{-1}~2\n+'\n+\n+test_expect_success '@{-1}^2 works' '\n+\ttest_rev_equivalent side^2 @{-1}^2\n+'\n+\n+test_expect_failure '@{-1}@{1} works' '\n+\ttest_rev_equivalent side@{1} @{-1}@{1}\n+'\n+\n+test_expect_success '@{-2} works' '\n+\ttest_rev_equivalent master @{-2}\n+'\n+\n+test_expect_success '@{-3} fails' '\n+\ttest_must_fail git rev-parse @{-3}\n+'\n+\n+test_done\n+\n+\n-- \n1.6.1.315.g92577\n"},{"id":"100850","messageId":"1232208597-29249-5-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"1232208597-29249-4-git-send-email-trast@student.ethz.ch","subject":"[PATCH/RFC v4 4/5] checkout: implement \"-\" abbreviation, add docs and tests","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T16:09:56Z","receivedAt":"2009-01-17T16:09:56Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Have '-' mean the same as '@{-1}', i.e., the last branch we were on.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/git-checkout.txt |    4 +++\n builtin-checkout.c             |    3 ++\n t/t2012-checkout-last.sh       |   50 ++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 57 insertions(+), 0 deletions(-)\n create mode 100755 t/t2012-checkout-last.sh\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 9cd5151..3bccffa 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -133,6 +133,10 @@ the conflicted merge in the specified paths.\n +\n When this parameter names a non-branch (but still a valid commit object),\n your HEAD becomes 'detached'.\n++\n+As a special case, the \"`@\\{-N\\}`\" syntax for the N-th last branch\n+checks out the branch (instead of detaching).  You may also specify\n+\"`-`\" which is synonymous with \"`@\\{-1\\}`\".\n \n \n Detached HEAD\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex dc1de06..b0a101b 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -679,6 +679,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\targ = argv[0];\n \t\thas_dash_dash = (argc > 1) && !strcmp(argv[1], \"--\");\n \n+\t\tif (!strcmp(arg, \"-\"))\n+\t\t\targ = \"@{-1}\";\n+\n \t\tif (get_sha1(arg, rev)) {\n \t\t\tif (has_dash_dash)          /* case (1) */\n \t\t\t\tdie(\"invalid reference: %s\", arg);\ndiff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh\nnew file mode 100755\nindex 0000000..320f6eb\n--- /dev/null\n+++ b/t/t2012-checkout-last.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='checkout can switch to last branch'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\techo hello >world &&\n+\tgit add world &&\n+\tgit commit -m initial &&\n+\tgit branch other &&\n+\techo \"hello again\" >>world &&\n+\tgit add world &&\n+\tgit commit -m second\n+'\n+\n+test_expect_success '\"checkout -\" does not work initially' '\n+\ttest_must_fail git checkout -\n+'\n+\n+test_expect_success 'first branch switch' '\n+\tgit checkout other\n+'\n+\n+test_expect_success '\"checkout -\" switches back' '\n+\tgit checkout - &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/master\"\n+'\n+\n+test_expect_success '\"checkout -\" switches forth' '\n+\tgit checkout - &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/other\"\n+'\n+\n+test_expect_success 'detach HEAD' '\n+\tgit checkout $(git rev-parse HEAD)\n+'\n+\n+test_expect_success '\"checkout -\" attaches again' '\n+\tgit checkout - &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/other\"\n+'\n+\n+test_expect_success '\"checkout -\" detaches again' '\n+\tgit checkout - &&\n+\ttest \"z$(git rev-parse HEAD)\" = \"z$(git rev-parse other)\" &&\n+\ttest_must_fail git symbolic-ref HEAD\n+'\n+\n+test_done\n-- \n1.6.1.315.g92577\n"},{"id":"100853","messageId":"1232208597-29249-6-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"1232208597-29249-5-git-send-email-trast@student.ethz.ch","subject":"[PATCH/RFC v4 5/5] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-17T16:09:57Z","receivedAt":"2009-01-17T16:09:57Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"From: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\nInstead of traversing them twice, we just build a list of branch switches,\npick the one we're interested in, and free the list again.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n sha1_name.c |   61 ++++++++++++++++++++++++----------------------------------\n 1 files changed, 25 insertions(+), 36 deletions(-)\n\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 9e1538e..b21a1f0 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -5,6 +5,8 @@\n #include \"blob.h\"\n #include \"tree-walk.h\"\n #include \"refs.h\"\n+#include \"cache-tree.h\"\n+#include \"string-list.h\"\n \n static int find_short_object_filename(int len, const char *name, unsigned char *sha1)\n {\n@@ -684,43 +686,31 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)\n \treturn retval;\n }\n \n-struct grab_nth_branch_switch_cbdata {\n-\tint counting;\n-\tint nth;\n-\tstruct strbuf *buf;\n-};\n-\n-static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n+static int add_one_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n \t\t\t\t  const char *email, unsigned long timestamp, int tz,\n \t\t\t\t  const char *message, void *cb_data)\n {\n-\tstruct grab_nth_branch_switch_cbdata *cb = cb_data;\n+\tstruct string_list *list = cb_data;\n \tconst char *match = NULL, *target = NULL;\n \tsize_t len;\n \n-\tif (!prefixcmp(message, \"checkout: moving from \")) {\n-\t\tmatch = message + strlen(\"checkout: moving from \");\n-\t\tif ((target = strstr(match, \" to \")) != NULL)\n-\t\t\ttarget += 4;\n-\t}\n-\n-\tif (!match)\n+\tif (prefixcmp(message, \"checkout: moving from \"))\n \t\treturn 0;\n \n-\tlen = target - match - 4;\n-\tif (target[len] == '\\n' && !strncmp(match, target, len))\n-\t\treturn 0;\n+\tmatch = message + strlen(\"checkout: moving from \");\n \n-\tif (cb->counting) {\n-\t\tcb->nth++;\n-\t\treturn 0;\n+\t/* Is it \"moving\" from a branch to itself?  Then ignore it. */\n+\tif ((target = strstr(match, \" to \")) != NULL) {\n+\t\ttarget += 4;\n+\t\tlen = target - match - 4;\n+\t\tif (target[len] == '\\n' && !strncmp(match, target, len))\n+\t\t\treturn 0;\n \t}\n+\telse\n+\t\tlen = strchrnul(match, ' ') - match;\n+\n+\tstring_list_append(xstrndup(match, len), list);\n \n-\tif (cb->nth-- <= 0) {\n-\t\tstrbuf_reset(cb->buf);\n-\t\tstrbuf_add(cb->buf, match, len);\n-\t\treturn 1;\n-\t}\n \treturn 0;\n }\n \n@@ -738,7 +728,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n {\n \tint nth;\n-\tstruct grab_nth_branch_switch_cbdata cb;\n+\tstruct string_list branch_list = { NULL, 0, 0, 0 };\n \tconst char *brace;\n \tchar *num_end;\n \n@@ -751,18 +741,17 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n \tif (num_end != brace)\n \t\treturn -1;\n \n-\tcb.counting = 1;\n-\tcb.nth = 0;\n-\tcb.buf = buf;\n-\tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n+\tfor_each_reflog_ent(\"HEAD\", add_one_branch_switch, &branch_list);\n \n-\tif (cb.nth < nth)\n+\tif (branch_list.nr < nth)\n \t\treturn 0;\n \n-\tcb.counting = 0;\n-\tcb.nth -= nth;\n-\tcb.buf = buf;\n-\tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n+\tstrbuf_reset(buf);\n+\tstrbuf_addstr(buf, branch_list.items[branch_list.nr - nth].string);\n+\n+\t/* force free()ing the items */\n+\tbranch_list.strdup_strings = 1;\n+\tstring_list_clear(&branch_list, 0);\n \n \treturn brace-name+1;\n }\n-- \n1.6.1.315.g92577\n"},{"id":"100864","messageId":"alpine.DEB.1.00.0901171747290.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"1232208597-29249-1-git-send-email-trast@student.ethz.ch","subject":"Re: [PATCH/RFC v4 0/5] N-th last checked out branch","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-17T16:49:31Z","receivedAt":"2009-01-17T16:49:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 17 Jan 2009, Thomas Rast wrote:\n\n> I [...] added a fixed up version of your patch on top, since it had some \n> context that is not in any version I have.\n\nThanks.\n\nYeah, I know, I have too many patches in my fork, but I'm working on it... \n:-)\n\nCiao,\nDscho\n"},{"id":"100866","messageId":"alpine.DEB.1.00.0901171854060.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"1232208597-29249-4-git-send-email-trast@student.ethz.ch","subject":"Re: [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-17T17:55:02Z","receivedAt":"2009-01-17T17:55:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 17 Jan 2009, Thomas Rast wrote:\n\n> diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh\n> new file mode 100755\n> index 0000000..1e49dd2\n> --- /dev/null\n> +++ b/t/t1505-rev-parse-last.sh\n> @@ -0,0 +1,71 @@\n> +#!/bin/sh\n> +\n> +test_description='test @{-N} syntax'\n> +\n> +. ./test-lib.sh\n> +\n> +\n> +make_commit () {\n> +\techo \"$1\" > \"$1\" &&\n> +\tgit add \"$1\" &&\n> +\tgit commit -m \"$1\"\n> +}\n> +\n> +\n> +test_expect_success 'setup' '\n> +\n> +\tmake_commit 1 &&\n> +\tgit branch side &&\n> +\tmake_commit 2 &&\n> +\tmake_commit 3 &&\n> +\tgit checkout side &&\n> +\tmake_commit 4 &&\n> +\tgit merge master &&\n> +\tgit checkout master\n> +\n> +'\n> +\n> +# 1 -- 2 -- 3 master\n> +#  \\         \\\n> +#   \\         \\\n> +#    --- 4 --- 5 side\n> +#\n> +# and 'side' should be the last branch\n> +\n> +git log --graph --all --pretty=oneline --decorate\n> +\n\nMaybe you want to squash this in, so that the output of \"make test\" is \nnot cluttered by the graph?\n\n-- snipsnap --\ndiff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh\nindex 1e49dd2..72e8322 100755\n--- a/t/t1505-rev-parse-last.sh\n+++ b/t/t1505-rev-parse-last.sh\n@@ -32,7 +32,11 @@ test_expect_success 'setup' '\n #\n # and 'side' should be the last branch\n \n-git log --graph --all --pretty=oneline --decorate\n+test_expect_success 'show a log (for debugging)' '\n+\n+\tgit log --graph --all --pretty=oneline --decorate\n+\n+'\n \n test_rev_equivalent () {\n \n-- \n1.6.1.332.g9a59d\n"},{"id":"100867","messageId":"alpine.DEB.1.00.0901171907530.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"1232208597-29249-6-git-send-email-trast@student.ethz.ch","subject":"[PATCH 6/5] Fix parsing of @{-1}@{1}","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-17T18:08:12Z","receivedAt":"2009-01-17T18:08:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nTo do that, Git no longer looks forward for the '@{' corresponding to the\nclosing '}' but backward, and dwim_ref() as well as dwim_log() learnt\nabout the @{-<N>} notation.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tThe modifications of dwim_ref() and dwim_log() are probably\n\tmore important than the issue I tried to fix...\n\n sha1_name.c               |   25 ++++++++++++++++++++++++-\n t/t1505-rev-parse-last.sh |    2 +-\n 2 files changed, 25 insertions(+), 2 deletions(-)\n\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 306d04b..ee0c456 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -240,8 +240,28 @@ static int ambiguous_path(const char *path, int len)\n \treturn slash;\n }\n \n+/*\n+ * *string and *len will only be substituted, and *string returned (for\n+ * later free()ing) if the string passed in is of the form @{-<n>}.\n+ */\n+static char *substitute_nth_last_branch(const char **string, int *len)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret = interpret_nth_last_branch(*string, &buf);\n+\n+\tif (ret == *len) {\n+\t\tsize_t size;\n+\t\t*string = strbuf_detach(&buf, &size);\n+\t\t*len = size;\n+\t\treturn (char *)*string;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)\n {\n+\tchar *last_branch = substitute_nth_last_branch(&str, &len);\n \tconst char **p, *r;\n \tint refs_found = 0;\n \n@@ -261,11 +281,13 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)\n \t\t\t\tbreak;\n \t\t}\n \t}\n+\tfree(last_branch);\n \treturn refs_found;\n }\n \n int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n {\n+\tchar *last_branch = substitute_nth_last_branch(&str, &len);\n \tconst char **p;\n \tint logs_found = 0;\n \n@@ -296,6 +318,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n \t\tif (!warn_ambiguous_refs)\n \t\t\tbreak;\n \t}\n+\tfree(last_branch);\n \treturn logs_found;\n }\n \n@@ -314,7 +337,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n \t/* basic@{time or number or -number} format to query ref-log */\n \treflog_len = at = 0;\n \tif (str[len-1] == '}') {\n-\t\tfor (at = 0; at < len - 1; at++) {\n+\t\tfor (at = len-2; at >= 0; at--) {\n \t\t\tif (str[at] == '@' && str[at+1] == '{') {\n \t\t\t\treflog_len = (len-1) - (at+2);\n \t\t\t\tlen = at;\ndiff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh\nindex 72e8322..2d6b31e 100755\n--- a/t/t1505-rev-parse-last.sh\n+++ b/t/t1505-rev-parse-last.sh\n@@ -58,7 +58,7 @@ test_expect_success '@{-1}^2 works' '\n \ttest_rev_equivalent side^2 @{-1}^2\n '\n \n-test_expect_failure '@{-1}@{1} works' '\n+test_expect_success '@{-1}@{1} works' '\n \ttest_rev_equivalent side@{1} @{-1}@{1}\n '\n \n-- \n1.6.1.332.g9a59d\n"},{"id":"100870","messageId":"7vljt97nld.fsf@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901171602340.3586@pacific.mpi-cbg.de","subject":"Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-17T19:13:18Z","receivedAt":"2009-01-17T19:13:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Instead of traversing them twice, we just build a list of branch switches,\n> pick the one we're interested in, and free the list again.\n\nIsn't the code keeping them all in core, or am I reading the patch wrong?\n\nIf you know that you are interested in the nth-from-the-last switch, and\nif you are reading from the beginning, you would need to keep at most n \nlast switches you have seen in core, wouldn't you?  \n"},{"id":"100872","messageId":"alpine.DEB.1.00.0901172028470.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"7vljt97nld.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-17T19:29:40Z","receivedAt":"2009-01-17T19:29:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 17 Jan 2009, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > Instead of traversing them twice, we just build a list of branch switches,\n> > pick the one we're interested in, and free the list again.\n> \n> Isn't the code keeping them all in core, or am I reading the patch wrong?\n> \n> If you know that you are interested in the nth-from-the-last switch, and\n> if you are reading from the beginning, you would need to keep at most n \n> last switches you have seen in core, wouldn't you?  \n\nThat is correct.  But this is such a highly uncritical code path that I'd \nlike to keep this simple rather than fast.\n\nCiao,\nDscho\n"},{"id":"100873","messageId":"7v63kd7mgv.fsf@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"1232208597-29249-4-git-send-email-trast@student.ethz.ch","subject":"Re: [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-17T19:37:36Z","receivedAt":"2009-01-17T19:37:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n> Let get_sha1() parse the @{-N} syntax, with docs and tests.\n>\n> Note that while @{-1}^2, @{-2}~5 and such are supported, @{-1}@{1} is\n> currently not allowed.\n>\n> Signed-off-by: Thomas Rast <trast@student.ethz.ch>\n> ...\n> @@ -324,6 +326,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n>  \t\treturn -1;\n>  \n>  \tif (!len && reflog_len) {\n> +\t\tstruct strbuf buf = STRBUF_INIT;\n> +\t\tint ret;\n> +\t\t/* try the @{-N} syntax for n-th checkout */\n> +\t\tret = interpret_nth_last_branch(str+at, &buf);\n> +\t\tif (ret > 0) {\n> +\t\t\t/* substitute this branch name and restart */\n> +\t\t\treturn get_sha1_1(buf.buf, buf.len, sha1);\n> +\t\t} else if (ret == 0) {\n> +\t\t\treturn -1;\n> +\t\t}\n\nWhat are the possible failure cases, and what do we want to tell the\nend-user?\n\n - You asked for 3rd but there weren't that many switches yet, and ask\n   \"git rev-parse --verify @{-3}\".\n\n   Are we Ok with \"fatal: Needed a single revision\" from rev-parse?  Do we\n   want to show \"fatal: @{-3}: not that many branch switches yet\"?\n\n   What happens to \"git checkout @{-3}\" in this case?  Having checkout say\n   \"fatal: invalid reference: @{-3}\" would be fine in this case, I think.\n\n - You try \"git checkout @{-3}\", you were on \"frotz\" branch back then, but\n   the branch does not exist anymore.\n\n   I think you will get \"fatal: invalid reference: frotz\" from checkout,\n   which should be fine.\n\nThere also is a case where nth_last_branch() may find something that is\nnot a branch (e.g. \"git checkout HEAD^\"), but I am hoping we can label\nthat as a bug in nth_last_branch() and fix it later.\n"},{"id":"100876","messageId":"7vwsct66yv.fsf@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"1232208597-29249-5-git-send-email-trast@student.ethz.ch","subject":"Re: [PATCH/RFC v4 4/5] checkout: implement \"-\" abbreviation, add docs and tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-17T19:57:44Z","receivedAt":"2009-01-17T19:57:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n> @@ -133,6 +133,10 @@ the conflicted merge in the specified paths.\n>  +\n>  When this parameter names a non-branch (but still a valid commit object),\n>  your HEAD becomes 'detached'.\n> ++\n> +As a special case, the \"`@\\{-N\\}`\" syntax for the N-th last branch\n> +checks out the branch (instead of detaching).  You may also specify\n> +\"`-`\" which is synonymous with \"`@\\{-1\\}`\".\n\nI mildly disagree with this wording.\n\nThe new syntax is supposed to be a new way to name a branch, not a random\nnon-branch committish that is special cased by \"git checkout\".  I would\nfurther suggest that we should teach \"git rev-parse --symbolic-full-name\"\nand \"git rev-parse --symbolic\" about the new syntax, so that scripts can\nuse the syntax to find out the same information.\n\nThe \"-\" thing deserves a mention here in the documentation.  That _is_ a\nspecial case that only applies to the \"git checkout\" command.\n\n> diff --git a/builtin-checkout.c b/builtin-checkout.c\n> index dc1de06..b0a101b 100644\n> --- a/builtin-checkout.c\n> +++ b/builtin-checkout.c\n> @@ -679,6 +679,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n>  \t\targ = argv[0];\n>  \t\thas_dash_dash = (argc > 1) && !strcmp(argv[1], \"--\");\n>  \n> +\t\tif (!strcmp(arg, \"-\"))\n> +\t\t\targ = \"@{-1}\";\n> +\n\nThis is not quite nice as it could be, but it probably is Ok.  If the\ninterpretation of @{-1} errors out, the user won't see an error message\nthat talks about \"-\" but instead the user will see \"@{-1}\".\n\nAlso it will look somewhat inconsistent to the end user who does not know\nthe internals for \"-\" claim to be a synonym for @{-1} but it really isn't.\nFor example, \"git checkout -^0\" does not work as \"git checkout @{-1}^0\".\n\nTo avoid such confusion, we could instead make \"git checkout - <ENTER>\" a\nsynonym for \"git checkout @{-1} <ENTER>\", without claiming to make \"-\" a\nsynonym for \"@{-1}\".  In other words, \"git checkout -\" can become a very\nnarrow, focused special case that does not allow anything else, such as\npathspecs, \"--\" separator, nor --force and other options.\n"},{"id":"100877","messageId":"7vsknh66qu.fsf@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901171907530.3586@pacific.mpi-cbg.de","subject":"Re: [PATCH 6/5] Fix parsing of @{-1}@{1}","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-17T20:02:33Z","receivedAt":"2009-01-17T20:02:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> To do that, Git no longer looks forward for the '@{' corresponding to the\n> closing '}' but backward, and dwim_ref() as well as dwim_log() learnt\n> about the @{-<N>} notation.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>\n> \tThe modifications of dwim_ref() and dwim_log() are probably\n> \tmore important than the issue I tried to fix...\n\nGood, so we can say things like:\n\n\tgit log -g @{-1}\n        git show-branch -g @{-1}\n\nwith this?\n\nBy the way, I noticed that without these patch series we show something\nwhen \"git rev-parse --verify @{-1}\" is asked for.  What is it trying to\nshow?\n"},{"id":"100884","messageId":"alpine.DEB.1.00.0901172214120.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"7vsknh66qu.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 6/5] Fix parsing of @{-1}@{1}","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-17T21:22:30Z","receivedAt":"2009-01-17T21:22:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 17 Jan 2009, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > To do that, Git no longer looks forward for the '@{' corresponding to the\n> > closing '}' but backward, and dwim_ref() as well as dwim_log() learnt\n> > about the @{-<N>} notation.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >\n> > \tThe modifications of dwim_ref() and dwim_log() are probably\n> > \tmore important than the issue I tried to fix...\n> \n> Good, so we can say things like:\n> \n> \tgit log -g @{-1}\n>         git show-branch -g @{-1}\n> \n> with this?\n\nI _hope_ :-)\n\n> By the way, I noticed that without these patch series we show something \n> when \"git rev-parse --verify @{-1}\" is asked for.  What is it trying to \n> show?\n\nApparently the same as @{1.Jan}: in get_sha1_basic(), we have this code:\n\n                /* Is it asking for N-th entry, or approxidate? */\n                for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {\n                        char ch = str[at+2+i];\n                        if ('0' <= ch && ch <= '9')\n                                nth = nth * 10 + ch - '0';\n                        else\n                                nth = -1;\n                }\n                if (100000000 <= nth) {\n                        at_time = nth;\n                        nth = -1;\n                } else if (0 <= nth)\n                        at_time = 0;\n                else {\n                        char *tmp = xstrndup(str + at + 2, reflog_len);\n                        at_time = approxidate(tmp);\n                        free(tmp);\n                }\n\nSo in the loop, nth is set to -1 because of a non-digit, and later, \napproxidate is called for nth == -1, which does this:\n\n\t$ ./test-date now\n\tnow -> bad -> Thu Jan  1 01:00:00 1970\n\tnow -> Sat Jan 17 22:21:20 2009\n\nCiao,\nDscho\n"},{"id":"100895","messageId":"7vmydp5tqj.fsf@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901172028470.3586@pacific.mpi-cbg.de","subject":"Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-18T00:43:32Z","receivedAt":"2009-01-18T00:43:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> That is correct.  But this is such a highly uncritical code path that I'd \n> like to keep this simple rather than fast.\n\nI actually not worried about \"fast\" at all; it was more about unbounded\nmemory consumption.\n"},{"id":"100898","messageId":"7v4ozx5t8b.fsf@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"1232208597-29249-3-git-send-email-trast@student.ethz.ch","subject":"Re: [PATCH/RFC v4 2/5] sha1_name: tweak @{-N} lookup","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-18T00:54:28Z","receivedAt":"2009-01-18T00:54:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n> Have the lookup only look at \"interesting\" checkouts, meaning those\n> that tell you \"Already on ...\" don't count even though they also cause\n> a reflog entry.\n>\n> Let interpret_nth_last_branch() return the number of characters\n> parsed, so that git-checkout can verify that the branch spec was\n> @{-N}, not @{-1}^2 or something like that.  (The latter will be added\n> later.)\n\nThanks; you seem to have handled the issues I pointed out in response to\nmy own weatherbaloon patch.  I think it is probably better to squash the\nfirst two (and you take the authorship).\n\n> diff --git a/sha1_name.c b/sha1_name.c\n> index 6377264..34e39db 100644\n> --- a/sha1_name.c\n> +++ b/sha1_name.c\n> @@ -685,29 +685,28 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n>  \t\t\t\t  const char *message, void *cb_data)\n>  {\n>  \tstruct grab_nth_branch_switch_cbdata *cb = cb_data;\n> -\tconst char *match = NULL;\n> -\n> -\tif (!prefixcmp(message, \"checkout: moving to \"))\n> -\t\tmatch = message + strlen(\"checkout: moving to \");\n> -\telse if (!prefixcmp(message, \"checkout: moving from \")) {\n> -\t\tconst char *cp = message + strlen(\"checkout: moving from \");\n> -\t\tif ((cp = strstr(cp, \" to \")) != NULL) {\n> -\t\t\tmatch = cp + 4;\n> -\t\t}\n> +\tconst char *match = NULL, *target = NULL;\n> +\tsize_t len;\n> +\n> +\tif (!prefixcmp(message, \"checkout: moving from \")) {\n> +\t\tmatch = message + strlen(\"checkout: moving from \");\n> +\t\tif ((target = strstr(match, \" to \")) != NULL)\n> +\t\t\ttarget += 4;\n>  \t}\n\nThis drops support for older reflog records, but I think it would be Ok.\nThis \"N-th\" support is really meant to be for small number of N anyway.\n\n> -\tif (--cb->nth <= 0) {\n> -\t\tsize_t len = strlen(match);\n> -\t\twhile (match[len-1] == '\\n')\n> -\t\t\tlen--;\n> +\tif (cb->nth-- <= 0) {\n>  \t\tstrbuf_reset(cb->buf);\n>  \t\tstrbuf_add(cb->buf, match, len);\n>  \t\treturn 1;\n\nHmm, did I have an off-by-one I did not notice? ;-)\n\n>  int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n>  {\n> -\tint nth, i;\n> +\tint nth;\n>  \tstruct grab_nth_branch_switch_cbdata cb;\n> +\tconst char *brace;\n> +\tchar *num_end;\n>  \n>  \tif (name[0] != '@' || name[1] != '{' || name[2] != '-')\n>  \t\treturn -1;\n> -\tfor (i = 3, nth = 0; name[i] && name[i] != '}'; i++) {\n> -\t\tchar ch = name[i];\n> -\t\tif ('0' <= ch && ch <= '9')\n> -\t\t\tnth = nth * 10 + ch - '0';\n> -\t\telse\n> -\t\t\treturn -1;\n> -\t}\n> -\tif (nth < 0 || 10 <= nth)\n\nThe removal of \"limit to reasonably small recent N\" I somewhat have\nreservations on, but I think we can later re-add something based on\nconfiguration variable if we need to.\n"},{"id":"100901","messageId":"alpine.DEB.1.00.0901180201070.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"7vmydp5tqj.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-18T01:12:02Z","receivedAt":"2009-01-18T01:12:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 17 Jan 2009, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > That is correct.  But this is such a highly uncritical code path that \n> > I'd like to keep this simple rather than fast.\n> \n> I actually not worried about \"fast\" at all; it was more about unbounded \n> memory consumption.\n\nLet's just assume that I make a branch switch per minute, continuously, \nfor 90 days (until the reflogs are expired), and let's say that all my \nbranchnames have 20 characters.  Conservatively, the memory requirement \nwould be 100 * 2000 * 30 = 3 megabyte.\n\nNote: these are the memory requirements after some really unrealistically \nhigh activity, and the memory is free()d during parameter parsing.\n\nA much more realistical expectation would be to switch branches maybe 20 \ntimes a day, which would amount to something like 36 kilobyte.  And again, \nthey are free()d before the action really starts.\n\nCiao,\nDscho\n"},{"id":"100922","messageId":"7vprilyt1w.fsf@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901180201070.3586@pacific.mpi-cbg.de","subject":"Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-18T07:25:31Z","receivedAt":"2009-01-18T07:25:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Note: these are the memory requirements after some really unrealistically \n> high activity, and the memory is free()d during parameter parsing.\n>\n> A much more realistical expectation would be to switch branches maybe 20 \n> times a day, which would amount to something like 36 kilobyte.  And again, \n> they are free()d before the action really starts.\n\nMy HEAD reflog is 7MB long with 39000 entries, and among them, 13100\nentries have \"checkout: moving \".\n\nI know I will never want to switch back to the 10000th from the last\nbranch.  I am quite sure that I would forget which branch I was on after\nswitching branches three or four times (hence my original hardcoded\nlimitation of 10 which \"should be plenty\").  When I know I only have to\nkeep track of 10 entries, having to keep track of 13100 entries, even if\nit is 36kB (it would actually be 260kB in my case) feels there is\nsomething wrong in the design.\n"},{"id":"101056","messageId":"alpine.DEB.1.00.0901182152010.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"7vprilyt1w.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-18T20:59:40Z","receivedAt":"2009-01-18T20:59:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 17 Jan 2009, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > Note: these are the memory requirements after some really unrealistically \n> > high activity, and the memory is free()d during parameter parsing.\n> >\n> > A much more realistical expectation would be to switch branches maybe 20 \n> > times a day, which would amount to something like 36 kilobyte.  And again, \n> > they are free()d before the action really starts.\n> \n> My HEAD reflog is 7MB long with 39000 entries, and among them, 13100\n> entries have \"checkout: moving \".\n> \n> I know I will never want to switch back to the 10000th from the last \n> branch.  I am quite sure that I would forget which branch I was on after \n> switching branches three or four times (hence my original hardcoded \n> limitation of 10 which \"should be plenty\").  When I know I only have to \n> keep track of 10 entries, having to keep track of 13100 entries, even if \n> it is 36kB (it would actually be 260kB in my case) feels there is \n> something wrong in the design.\n\nHrm.  So let's leave it as a two-pass thing?\n\nCiao,\nDscho\n"},{"id":"101109","messageId":"7vprijra52.fsf@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901182152010.3586@pacific.mpi-cbg.de","subject":"Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-19T08:08:09Z","receivedAt":"2009-01-19T08:08:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> On Sat, 17 Jan 2009, Junio C Hamano wrote:\n> ...\n>> My HEAD reflog is 7MB long with 39000 entries, and among them, 13100\n>> entries have \"checkout: moving \".\n>> \n>> I know I will never want to switch back to the 10000th from the last \n>> branch.  I am quite sure that I would forget which branch I was on after \n>> switching branches three or four times (hence my original hardcoded \n>> limitation of 10 which \"should be plenty\").  When I know I only have to \n>> keep track of 10 entries, having to keep track of 13100 entries, even if \n>> it is 36kB (it would actually be 260kB in my case) feels there is \n>> something wrong in the design.\n>\n> Hrm.  So let's leave it as a two-pass thing?\n\nWell, I would rather be in favor of something like this.\n\n-- >8 --\nSubject: interpret_nth_last_branch(): avoid traversing the reflog twice\n\nYou can have quite a many reflog entries, but you typically won't recall\nwhich branch you were on after switching branches for more than several\ntimes.\n\nInstead of reading the reflog twice, this reads the branch switching event\nand keeps the latest 16 (which is an arbitrary limitation that should be\nplenty) such entry, to switch back to the branch we were recently on.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n sha1_name.c              |   48 +++++++++++++++++++++------------------------\n t/t2012-checkout-last.sh |   44 ++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 66 insertions(+), 26 deletions(-)\n\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 9e1538e..d6622f2 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -684,10 +684,11 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)\n \treturn retval;\n }\n \n+#define MAX_PREVIOUS_BRANCH 16\n+\n struct grab_nth_branch_switch_cbdata {\n-\tint counting;\n-\tint nth;\n-\tstruct strbuf *buf;\n+\tlong cnt;\n+\tstruct strbuf buf[MAX_PREVIOUS_BRANCH];\n };\n \n static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n@@ -697,6 +698,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n \tstruct grab_nth_branch_switch_cbdata *cb = cb_data;\n \tconst char *match = NULL, *target = NULL;\n \tsize_t len;\n+\tint nth;\n \n \tif (!prefixcmp(message, \"checkout: moving from \")) {\n \t\tmatch = message + strlen(\"checkout: moving from \");\n@@ -711,16 +713,9 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n \tif (target[len] == '\\n' && !strncmp(match, target, len))\n \t\treturn 0;\n \n-\tif (cb->counting) {\n-\t\tcb->nth++;\n-\t\treturn 0;\n-\t}\n-\n-\tif (cb->nth-- <= 0) {\n-\t\tstrbuf_reset(cb->buf);\n-\t\tstrbuf_add(cb->buf, match, len);\n-\t\treturn 1;\n-\t}\n+\tnth = cb->cnt++ % MAX_PREVIOUS_BRANCH;\n+\tstrbuf_reset(&cb->buf[nth]);\n+\tstrbuf_add(&cb->buf[nth], match, len);\n \treturn 0;\n }\n \n@@ -737,7 +732,8 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n  */\n int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n {\n-\tint nth;\n+\tlong nth;\n+\tint i;\n \tstruct grab_nth_branch_switch_cbdata cb;\n \tconst char *brace;\n \tchar *num_end;\n@@ -750,19 +746,19 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n \tnth = strtol(name+3, &num_end, 10);\n \tif (num_end != brace)\n \t\treturn -1;\n-\n-\tcb.counting = 1;\n-\tcb.nth = 0;\n-\tcb.buf = buf;\n-\tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n-\n-\tif (cb.nth < nth)\n-\t\treturn 0;\n-\n-\tcb.counting = 0;\n-\tcb.nth -= nth;\n-\tcb.buf = buf;\n+\tif (nth <= 0 || MAX_PREVIOUS_BRANCH < nth)\n+\t\treturn -1;\n+\tfor (i = 0; i < MAX_PREVIOUS_BRANCH; i++)\n+\t\tstrbuf_init(&cb.buf[i], 20);\n+\tcb.cnt = 0;\n \tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n+\tif (cb.cnt < nth)\n+\t\treturn -1;\n+\ti = (cb.cnt + MAX_PREVIOUS_BRANCH - nth) % MAX_PREVIOUS_BRANCH;\n+\tstrbuf_reset(buf);\n+\tstrbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);\n+\tfor (i = 0; i < MAX_PREVIOUS_BRANCH; i++)\n+\t\tstrbuf_release(&cb.buf[i]);\n \n \treturn brace-name+1;\n }\ndiff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh\nindex 320f6eb..87b30a2 100755\n--- a/t/t2012-checkout-last.sh\n+++ b/t/t2012-checkout-last.sh\n@@ -47,4 +47,48 @@ test_expect_success '\"checkout -\" detaches again' '\n \ttest_must_fail git symbolic-ref HEAD\n '\n \n+test_expect_success 'more switches' '\n+\tfor i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1\n+\tdo\n+\t\tgit checkout -b branch$i\n+\tdone\n+'\n+\n+more_switches () {\n+\tfor i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1\n+\tdo\n+\t\tgit checkout branch$i\n+\tdone\n+}\n+\n+test_expect_success 'switch to the last' '\n+\tmore_switches &&\n+\tgit checkout @{-1} &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/branch2\"\n+'\n+\n+test_expect_success 'switch to second from the last' '\n+\tmore_switches &&\n+\tgit checkout @{-2} &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/branch3\"\n+'\n+\n+test_expect_success 'switch to third from the last' '\n+\tmore_switches &&\n+\tgit checkout @{-3} &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/branch4\"\n+'\n+\n+test_expect_success 'switch to fourth from the last' '\n+\tmore_switches &&\n+\tgit checkout @{-4} &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/branch5\"\n+'\n+\n+test_expect_success 'switch to twelfth from the last' '\n+\tmore_switches &&\n+\tgit checkout @{-12} &&\n+\ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/branch13\"\n+'\n+\n test_done\n-- \n1.6.1.245.gdd9f9\n"},{"id":"101110","messageId":"7vljt7r9mq.fsf@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"7vprijra52.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-19T08:19:09Z","receivedAt":"2009-01-19T08:19:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Well, I would rather be in favor of something like this.\n>\n> -- >8 --\n> Subject: interpret_nth_last_branch(): avoid traversing the reflog twice\n>\n> You can have quite a many reflog entries, but you typically won't recall\n> which branch you were on after switching branches for more than several\n> times.\n>\n> Instead of reading the reflog twice, this reads the branch switching event\n> and keeps the latest 16 (which is an arbitrary limitation that should be\n> plenty) such entry, to switch back to the branch we were recently on.\n>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>  sha1_name.c              |   48 +++++++++++++++++++++------------------------\n>  t/t2012-checkout-last.sh |   44 ++++++++++++++++++++++++++++++++++++++++++\n>  2 files changed, 66 insertions(+), 26 deletions(-)\n>\n> diff --git a/sha1_name.c b/sha1_name.c\n> index 9e1538e..d6622f2 100644\n> --- a/sha1_name.c\n> +++ b/sha1_name.c\n> @@ -750,19 +746,19 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n>  \tnth = strtol(name+3, &num_end, 10);\n>  \tif (num_end != brace)\n>  \t\treturn -1;\n> ...\n> -\tif (cb.nth < nth)\n> -\t\treturn 0;\n> ...\n> +\tif (cb.cnt < nth)\n> +\t\treturn -1;\n\nThis should (obviously) be \"return 0\".\n"},{"id":"101128","messageId":"alpine.DEB.1.00.0901191331590.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"7vljt7r9mq.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-19T12:33:34Z","receivedAt":"2009-01-19T12:33:34Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 19 Jan 2009, Junio C Hamano wrote:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > Well, I would rather be in favor of something like this.\n> >\n> > -- >8 --\n> > Subject: interpret_nth_last_branch(): avoid traversing the reflog twice\n> >\n> > You can have quite a many reflog entries, but you typically won't recall\n> > which branch you were on after switching branches for more than several\n> > times.\n> >\n> > Instead of reading the reflog twice, this reads the branch switching event\n> > and keeps the latest 16 (which is an arbitrary limitation that should be\n> > plenty) such entry, to switch back to the branch we were recently on.\n> >\n> > Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> > ---\n> >  sha1_name.c              |   48 +++++++++++++++++++++------------------------\n> >  t/t2012-checkout-last.sh |   44 ++++++++++++++++++++++++++++++++++++++++++\n> >  2 files changed, 66 insertions(+), 26 deletions(-)\n> >\n> > diff --git a/sha1_name.c b/sha1_name.c\n> > index 9e1538e..d6622f2 100644\n> > --- a/sha1_name.c\n> > +++ b/sha1_name.c\n> > @@ -750,19 +746,19 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n> >  \tnth = strtol(name+3, &num_end, 10);\n> >  \tif (num_end != brace)\n> >  \t\treturn -1;\n> > ...\n> > -\tif (cb.nth < nth)\n> > -\t\treturn 0;\n> > ...\n> > +\tif (cb.cnt < nth)\n> > +\t\treturn -1;\n> \n> This should (obviously) be \"return 0\".\n\nThis, together with a removal of the hard-coded limit of 16 could be \nsquashed with this patch:\n\n-- snipsnap --\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 2c5461e..9e5f444 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -691,11 +691,9 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)\n \treturn retval;\n }\n \n-#define MAX_PREVIOUS_BRANCH 16\n-\n struct grab_nth_branch_switch_cbdata {\n-\tlong cnt;\n-\tstruct strbuf buf[MAX_PREVIOUS_BRANCH];\n+\tlong cnt, alloc;\n+\tstruct strbuf *buf;\n };\n \n static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n@@ -720,7 +718,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n \tif (target[len] == '\\n' && !strncmp(match, target, len))\n \t\treturn 0;\n \n-\tnth = cb->cnt++ % MAX_PREVIOUS_BRANCH;\n+\tnth = cb->cnt++ % cb->alloc;\n \tstrbuf_reset(&cb->buf[nth]);\n \tstrbuf_add(&cb->buf[nth], match, len);\n \treturn 0;\n@@ -753,19 +751,22 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n \tnth = strtol(name+3, &num_end, 10);\n \tif (num_end != brace)\n \t\treturn -1;\n-\tif (nth <= 0 || MAX_PREVIOUS_BRANCH < nth)\n+\tif (nth <= 0)\n \t\treturn -1;\n-\tfor (i = 0; i < MAX_PREVIOUS_BRANCH; i++)\n+\tcb.alloc = nth;\n+\tcb.buf = xmalloc(nth * sizeof(struct strbuf));\n+\tfor (i = 0; i < nth; i++)\n \t\tstrbuf_init(&cb.buf[i], 20);\n \tcb.cnt = 0;\n \tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n \tif (cb.cnt < nth)\n-\t\treturn -1;\n-\ti = (cb.cnt + MAX_PREVIOUS_BRANCH - nth) % MAX_PREVIOUS_BRANCH;\n+\t\treturn 0;\n+\ti = cb.cnt % nth;\n \tstrbuf_reset(buf);\n \tstrbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);\n-\tfor (i = 0; i < MAX_PREVIOUS_BRANCH; i++)\n+\tfor (i = 0; i < nth; i++)\n \t\tstrbuf_release(&cb.buf[i]);\n+\tfree(cb.buf);\n \n \treturn brace-name+1;\n }\n-- \n1.6.1.347.g7b62749\n"},{"id":"101129","messageId":"alpine.DEB.1.00.0901191338470.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"7vljt7r9mq.fsf@gitster.siamese.dyndns.org","subject":"[PATCH] @{-<n>}: avoid crash with corrupt reflog","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-19T12:41:05Z","receivedAt":"2009-01-19T12:41:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nThe earlier code checked if a \" to \" was found after \"checkout: Moving\nfrom \".  However, it then went on to access the pointer to \" to \",\nregardless if it was still NULL (if no \" to \" was found) or not.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tYou might want to squash this into \"sha1_name: tweak @{-N} \n\tlookup\", just as a safety belt.\n\n sha1_name.c |   20 ++++++++++----------\n 1 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 9e5f444..853bac6 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -705,18 +705,18 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n \tsize_t len;\n \tint nth;\n \n-\tif (!prefixcmp(message, \"checkout: moving from \")) {\n-\t\tmatch = message + strlen(\"checkout: moving from \");\n-\t\tif ((target = strstr(match, \" to \")) != NULL)\n-\t\t\ttarget += 4;\n-\t}\n-\n-\tif (!match)\n+\tif (prefixcmp(message, \"checkout: moving from \"))\n \t\treturn 0;\n \n-\tlen = target - match - 4;\n-\tif (target[len] == '\\n' && !strncmp(match, target, len))\n-\t\treturn 0;\n+\tmatch = message + strlen(\"checkout: moving from \");\n+\tif ((target = strstr(match, \" to \")) != NULL) {\n+\t\tlen = target - match - 4;\n+\t\ttarget += 4;\n+\t\tif (target[len] == '\\n' && !strncmp(match, target, len))\n+\t\t\treturn 0;\n+\t}\n+\telse\n+\t\tlen = strchrnul(match, ' ') - match;\n \n \tnth = cb->cnt++ % cb->alloc;\n \tstrbuf_reset(&cb->buf[nth]);\n-- \n1.6.1.347.g7b62749\n"},{"id":"101140","messageId":"alpine.DEB.1.00.0901191547220.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901191338470.3586@pacific.mpi-cbg.de","subject":"Re: [PATCH] @{-<n>}: avoid crash with corrupt reflog","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-19T14:57:34Z","receivedAt":"2009-01-19T14:57:34Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 19 Jan 2009, Johannes Schindelin wrote:\n\n> diff --git a/sha1_name.c b/sha1_name.c\n> index 9e5f444..853bac6 100644\n> --- a/sha1_name.c\n> +++ b/sha1_name.c\n> @@ -705,18 +705,18 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n>  \tsize_t len;\n>  \tint nth;\n>  \n> -\tif (!prefixcmp(message, \"checkout: moving from \")) {\n> -\t\tmatch = message + strlen(\"checkout: moving from \");\n> -\t\tif ((target = strstr(match, \" to \")) != NULL)\n> -\t\t\ttarget += 4;\n> -\t}\n> -\n> -\tif (!match)\n> +\tif (prefixcmp(message, \"checkout: moving from \"))\n>  \t\treturn 0;\n>  \n> -\tlen = target - match - 4;\n> -\tif (target[len] == '\\n' && !strncmp(match, target, len))\n> -\t\treturn 0;\n> +\tmatch = message + strlen(\"checkout: moving from \");\n> +\tif ((target = strstr(match, \" to \")) != NULL) {\n> +\t\tlen = target - match - 4;\n\nAargh, the \"- 4\" is wrong, of course.\n\n> +\t\ttarget += 4;\n> +\t\tif (target[len] == '\\n' && !strncmp(match, target, len))\n> +\t\t\treturn 0;\n> +\t}\n> +\telse\n> +\t\tlen = strchrnul(match, ' ') - match;\n>  \n>  \tnth = cb->cnt++ % cb->alloc;\n>  \tstrbuf_reset(&cb->buf[nth]);\n\nSorry for the noise,\nDscho\n"},{"id":"101193","messageId":"200901200111.38917.trast@student.ethz.ch","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901191331590.3586@pacific.mpi-cbg.de","subject":"Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-20T00:11:32Z","receivedAt":"2009-01-20T00:11:32Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Johannes Schindelin wrote:\n> > Junio C Hamano <gitster@pobox.com> writes:\n> > > You can have quite a many reflog entries, but you typically won't recall\n> > > which branch you were on after switching branches for more than several\n> > > times.\n> \n> This, together with a removal of the hard-coded limit of 16 could be \n> squashed with this patch:\n\nYou know, I'm quite puzzled as to why we had working code that could\nread the reflog backwards earlier in this thread, but it got shot down\nsolely based on impact and line counts, and you now have to jump\nthrough hoops to work around the lack of this exact functionality.\n\nSo how about I resurrect the part about for_each_reflog_ent() and\n_backward(), without touching read_ref_at().  This would actually\navoid the worst (hard to check) part of the patch since the\nrefactoring of for_each_reflog_ent()'s error checking is quite trivial\nand IMHO actually results in more readable code.\n\nI'm just asking because I'm not particularly inclined to do it first\nand get rejected _again_.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"101195","messageId":"alpine.DEB.1.00.0901200121190.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"200901200111.38917.trast@student.ethz.ch","subject":"Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-20T00:23:59Z","receivedAt":"2009-01-20T00:23:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Tue, 20 Jan 2009, Thomas Rast wrote:\n\n> Johannes Schindelin wrote:\n> > > Junio C Hamano <gitster@pobox.com> writes:\n> > > > You can have quite a many reflog entries, but you typically won't recall\n> > > > which branch you were on after switching branches for more than several\n> > > > times.\n> > \n> > This, together with a removal of the hard-coded limit of 16 could be \n> > squashed with this patch:\n> \n> You know, I'm quite puzzled as to why we had working code that could\n> read the reflog backwards earlier in this thread, but it got shot down\n> solely based on impact and line counts, and you now have to jump\n> through hoops to work around the lack of this exact functionality.\n\nOkay, I should have told you what my two main concerns with the patch \nwere.\n\n1) it introduces a lot of code, with a lot of possibility for bugs to hide \n   (and I found it not simple enough to slap my head and say \"of course, \n   this is obvious\" as I did with Junio's code (except the modulo thing \n   which I had to thing about for half a minute)).\n\n2) on Windows, mmap() is really implemented as xmalloc() && fread().  So \n   all the shortcomings of what Junio said about my array approach would \n   hold true for your approach, too.\n\nCiao,\nDscho\n"},{"id":"101196","messageId":"200901200141.14129.trast@student.ethz.ch","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901200121190.3586@pacific.mpi-cbg.de","subject":"Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-20T00:41:08Z","receivedAt":"2009-01-20T00:41:08Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Johannes Schindelin wrote:\n> 1) it introduces a lot of code, with a lot of possibility for bugs to hide \n\nAnd I can understand in the case of read_ref_at() since there the\ntranslation is really nontrivial (though I'd readily put the blame on\nthe optimized-to-death original code ;-).  But in the case of\nfor_each_reflog_ent(), it should be really straightforward.\n\n> 2) on Windows, mmap() is really implemented as xmalloc() && fread().  So \n>    all the shortcomings of what Junio said about my array approach would \n>    hold true for your approach, too.\n\nBut the @{} syntax _already_ uses mmap via read_ref_at().  And both\nuses of for_each_reflog_ent() I'm aware of, the existing git log -g\nand the present @{-N} syntax, have to read to the end anyway because\nthey're mostly interested in the newer stuff.\n\nSo while the mmap() might occasionally grab a few more MB of RAM than\nwould actually be required with simple line-based input, Windows\nhas to read the whole reflog no matter what.\n\n(Well, unless we make a for_each_reflog_ent_backward() that can jump\nin somewhere near the end and start parsing lines backwards.)\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"101217","messageId":"7vvdsamrai.fsf_-_@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901191331590.3586@pacific.mpi-cbg.de","subject":"[PATCH] interpret_nth_last_branch(): plug small memleak","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-20T06:21:09Z","receivedAt":"2009-01-20T06:21:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The error return path leaked both cb.buf[] strbuf array itself, and the\nstrings contained in its elements.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n sha1_name.c |   10 +++++++---\n 1 files changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/sha1_name.c b/sha1_name.c\nindex f54b6cb..4c0370b 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -754,7 +754,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n {\n \tlong nth;\n-\tint i;\n+\tint i, retval;\n \tstruct grab_nth_branch_switch_cbdata cb;\n \tconst char *brace;\n \tchar *num_end;\n@@ -774,17 +774,21 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n \tfor (i = 0; i < nth; i++)\n \t\tstrbuf_init(&cb.buf[i], 20);\n \tcb.cnt = 0;\n+\tretval = 0;\n \tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n \tif (cb.cnt < nth)\n-\t\treturn 0;\n+\t\tgoto release_return;\n \ti = cb.cnt % nth;\n \tstrbuf_reset(buf);\n \tstrbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);\n+\tretval = brace-name+1;\n+\n+release_return:\n \tfor (i = 0; i < nth; i++)\n \t\tstrbuf_release(&cb.buf[i]);\n \tfree(cb.buf);\n \n-\treturn brace-name+1;\n+\treturn retval;\n }\n \n /*\n-- \n1.6.1.267.g11c6e\n"},{"id":"101218","messageId":"7vpriimr93.fsf_-_@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901191331590.3586@pacific.mpi-cbg.de","subject":"[PATCH] Introduce for_each_recent_reflog_ent().","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-20T06:22:00Z","receivedAt":"2009-01-20T06:22:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This can be used to scan only the last few kilobytes of a reflog, as a\ncheap optimization when the data you are looking for is likely to be\nfound near the end of it.  The caller is expected to fall back to the\nfull scan if that is not the case.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n refs.c      |   17 ++++++++++++++++-\n refs.h      |    1 +\n sha1_name.c |    8 +++++++-\n 3 files changed, 24 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 33ced65..024211d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1453,7 +1453,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *\n \treturn 1;\n }\n \n-int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)\n+int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data)\n {\n \tconst char *logfile;\n \tFILE *logfp;\n@@ -1464,6 +1464,16 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)\n \tlogfp = fopen(logfile, \"r\");\n \tif (!logfp)\n \t\treturn -1;\n+\n+\tif (ofs) {\n+\t\tstruct stat statbuf;\n+\t\tif (fstat(fileno(logfp), &statbuf) ||\n+\t\t    statbuf.st_size < ofs ||\n+\t\t    fseek(logfp, -ofs, SEEK_END) ||\n+\t\t    fgets(buf, sizeof(buf), logfp))\n+\t\t\treturn -1;\n+\t}\n+\n \twhile (fgets(buf, sizeof(buf), logfp)) {\n \t\tunsigned char osha1[20], nsha1[20];\n \t\tchar *email_end, *message;\n@@ -1497,6 +1507,11 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)\n \treturn ret;\n }\n \n+int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)\n+{\n+\treturn for_each_recent_reflog_ent(ref, fn, 0, cb_data);\n+}\n+\n static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)\n {\n \tDIR *dir = opendir(git_path(\"logs/%s\", base));\ndiff --git a/refs.h b/refs.h\nindex 06ad260..3bb529d 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -60,6 +60,7 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned\n /* iterate over reflog entries */\n typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);\n int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);\n+int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, void *cb_data);\n \n /*\n  * Calls the specified function for each reflog file until it returns nonzero,\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 4c0370b..38c9f1b 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -775,7 +775,13 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)\n \t\tstrbuf_init(&cb.buf[i], 20);\n \tcb.cnt = 0;\n \tretval = 0;\n-\tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n+\tfor_each_recent_reflog_ent(\"HEAD\", grab_nth_branch_switch, 40960, &cb);\n+\tif (cb.cnt < nth) {\n+\t\tcb.cnt = 0;\n+\t\tfor (i = 0; i < nth; i++)\n+\t\t\tstrbuf_release(&cb.buf[i]);\n+\t\tfor_each_reflog_ent(\"HEAD\", grab_nth_branch_switch, &cb);\n+\t}\n \tif (cb.cnt < nth)\n \t\tgoto release_return;\n \ti = cb.cnt % nth;\n-- \n1.6.1.267.g11c6e\n"},{"id":"101233","messageId":"7v4ozul6hi.fsf@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901191331590.3586@pacific.mpi-cbg.de","subject":"Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-20T08:35:53Z","receivedAt":"2009-01-20T08:35:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> This, together with a removal of the hard-coded limit of 16 could be \n> squashed with this patch:\n\nYeah, I think that makes very much sense, not because 16 is too small, but\nbecause it does not make sense to keep track of all 16 when you asked for\nthe last (or the second from the last) event.\n\nThanks for a free sanity ;-)\n"},{"id":"101241","messageId":"alpine.DEB.1.00.0901201114250.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"7vpriimr93.fsf_-_@gitster.siamese.dyndns.org","subject":"Re: [PATCH] Introduce for_each_recent_reflog_ent().","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-20T10:15:07Z","receivedAt":"2009-01-20T10:15:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 19 Jan 2009, Junio C Hamano wrote:\n\n> This can be used to scan only the last few kilobytes of a reflog, as a \n> cheap optimization when the data you are looking for is likely to be \n> found near the end of it.  The caller is expected to fall back to the \n> full scan if that is not the case.\n\nFWIW I really like it, as it works around mmap() nicely.\n\nCiao,\nDscho\n"},{"id":"101242","messageId":"alpine.DEB.1.00.0901201115160.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"7vvdsamrai.fsf_-_@gitster.siamese.dyndns.org","subject":"Re: [PATCH] interpret_nth_last_branch(): plug small memleak","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-20T10:15:30Z","receivedAt":"2009-01-20T10:15:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 19 Jan 2009, Junio C Hamano wrote:\n\n> The error return path leaked both cb.buf[] strbuf array itself, and the \n> strings contained in its elements.\n\nOops.  Thanks,\nDscho\n"},{"id":"101325","messageId":"alpine.DEB.1.00.0901210113500.19014@racer","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901191331590.3586@pacific.mpi-cbg.de","subject":"[VALGRIND PATCH for nth_last patch series] Fix invalid memory access","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-21T00:16:37Z","receivedAt":"2009-01-21T00:16:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nPlease squash in.\n---\n\n\tOn Mon, 19 Jan 2009, Johannes Schindelin wrote:\n\n\t> @@ -720,7 +718,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n\t>  \tif (target[len] == '\\n' && !strncmp(match, target, len))\n\t>  \t\treturn 0;\n\n\tThis code is still not valid, as target[len] can be well after the \n\tNUL marker.\n\n\tFound by valgrind.\n\n sha1_name.c |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 4d10705..803f9d2 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -735,7 +735,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n \tif ((target = strstr(match, \" to \")) != NULL) {\n \t\tlen = target - match;\n \t\ttarget += 4;\n-\t\tif (target[len] == '\\n' && !strncmp(match, target, len))\n+\t\tif (len == strlen(target) && !strncmp(match, target, len))\n \t\t\treturn 0;\n \t}\n \telse\n-- \n1.6.1.243.g6c8bb35\n"},{"id":"101357","messageId":"7vwscpgi7t.fsf@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"alpine.DEB.1.00.0901210113500.19014@racer","subject":"Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-21T08:45:58Z","receivedAt":"2009-01-21T08:45:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>  sha1_name.c |    2 +-\n>  1 files changed, 1 insertions(+), 1 deletions(-)\n>\n> diff --git a/sha1_name.c b/sha1_name.c\n> index 4d10705..803f9d2 100644\n> --- a/sha1_name.c\n> +++ b/sha1_name.c\n> @@ -735,7 +735,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n>  \tif ((target = strstr(match, \" to \")) != NULL) {\n>  \t\tlen = target - match;\n>  \t\ttarget += 4;\n> -\t\tif (target[len] == '\\n' && !strncmp(match, target, len))\n> +\t\tif (len == strlen(target) && !strncmp(match, target, len))\n>  \t\t\treturn 0;\n>  \t}\n>  \telse\n\nActually, I think this patch to a884d0c (sha1_name: tweak @{-N} lookup,\n2009-01-17) would make more sense.\n\n-- >8 --\nSubject: [PATCH] Simplify parsing branch switching events in reflog\n\nWe only accept \"checkout: moving from A to B\" newer style reflog entries,\nin order to pick up A.  There is no point computing where B begins at\nafter running strstr to locate \" to \", nor adding 4 and then subtracting 4\nfrom the same pointer.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n sha1_name.c |    8 ++------\n 1 files changed, 2 insertions(+), 6 deletions(-)\n\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 38c9f1b..7d95bbb 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -723,17 +723,13 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n \n \tif (!prefixcmp(message, \"checkout: moving from \")) {\n \t\tmatch = message + strlen(\"checkout: moving from \");\n-\t\tif ((target = strstr(match, \" to \")) != NULL)\n-\t\t\ttarget += 4;\n+\t\ttarget = strstr(match, \" to \");\n \t}\n \n \tif (!match || !target)\n \t\treturn 0;\n \n-\tlen = target - match - 4;\n-\tif (target[len] == '\\n' && !strncmp(match, target, len))\n-\t\treturn 0;\n-\n+\tlen = target - match;\n \tnth = cb->cnt++ % cb->alloc;\n \tstrbuf_reset(&cb->buf[nth]);\n \tstrbuf_add(&cb->buf[nth], match, len);\n-- \n1.6.1.281.g16db\n"},{"id":"101360","messageId":"200901211019.01493.trast@student.ethz.ch","threadId":"17221","inReplyTo":"7vwscpgi7t.fsf@gitster.siamese.dyndns.org","subject":"Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-21T09:18:57Z","receivedAt":"2009-01-21T09:18:57Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Junio C Hamano wrote:\n> We only accept \"checkout: moving from A to B\" newer style reflog entries,\n> in order to pick up A.  There is no point computing where B begins at\n> after running strstr to locate \" to \", nor adding 4 and then subtracting 4\n> from the same pointer.\n[...]\n> -\tlen = target - match - 4;\n> -\tif (target[len] == '\\n' && !strncmp(match, target, len))\n> -\t\treturn 0;\n> -\n> +\tlen = target - match;\n\nActually the point of that exercise was to ignore branch (non)switches\nof the form\n\n  checkout: moving from A to A\n\nI originally thought that this would be desirable behaviour, but now\nthat it causes so much trouble, I'm not that sure any more.  I still\nthink it would be more intuitive to not count them as switches (after\nall git-checkout says 'Already on \"$branch\"'), but OTOH 'cd .; cd -'\nalso stays in the same directory.\n\nThanks for all the work you (both) are doing on this.  I hope to find\nthe time to read the current state of the series tonight.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"101364","messageId":"7vr62xezm8.fsf@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"200901211019.01493.trast@student.ethz.ch","subject":"Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-21T10:13:03Z","receivedAt":"2009-01-21T10:13:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n> Actually the point of that exercise was to ignore branch (non)switches\n> of the form\n>\n>   checkout: moving from A to A\n\nAhhh, Ok, that is what I missed.\n\n> I originally thought that this would be desirable behaviour, but now\n> that it causes so much trouble, I'm not that sure any more.  I still\n> think it would be more intuitive to not count them as switches (after\n> all git-checkout says 'Already on \"$branch\"'), but OTOH 'cd .; cd -'\n> also stays in the same directory.\n\nAn entry of the form \"from A to A\" is made only when you explicitly ask to\ncheckout the current branch by name (i.e. \"git checkout\" without any\nparameter won't add such an entry to the reflog), so I tend to agree with\n\"cd\" that the users may find it more natural if we counted them.\n\nHaving said all that, I think Dscho's one had an off-by-one (but it is\ngetting late and it may be I who has one).\n\nWhen parsing \"checkout: moving from master to side\\n\", match points at\n\"master to...\", target points at \"side\\n\", and len is 6 (length of\n\"master\").  We want to see if target is \"master\\n\" and ignore such an\nentry, so we should be checking if target is one longer than len.\n\n sha1_name.c |    5 ++++-\n 1 files changed, 4 insertions(+), 1 deletions(-)\n\ndiff --git c/sha1_name.c w/sha1_name.c\nindex 38c9f1b..9aed8ae 100644\n--- c/sha1_name.c\n+++ w/sha1_name.c\n@@ -731,7 +731,10 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n \t\treturn 0;\n \n \tlen = target - match - 4;\n-\tif (target[len] == '\\n' && !strncmp(match, target, len))\n+\tif (len + 1 == strlen(target) &&\n+\t    target[len] == '\\n' &&\n+\t    !memcmp(target, match, len))\n+\t\t/* switching same branch \"from A to A\\n\" */\n \t\treturn 0;\n \n \tnth = cb->cnt++ % cb->alloc;\n"},{"id":"101374","messageId":"alpine.DEB.1.00.0901211256370.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"7vwscpgi7t.fsf@gitster.siamese.dyndns.org","subject":"Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-21T11:56:59Z","receivedAt":"2009-01-21T11:56:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Wed, 21 Jan 2009, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> >  sha1_name.c |    2 +-\n> >  1 files changed, 1 insertions(+), 1 deletions(-)\n> >\n> > diff --git a/sha1_name.c b/sha1_name.c\n> > index 4d10705..803f9d2 100644\n> > --- a/sha1_name.c\n> > +++ b/sha1_name.c\n> > @@ -735,7 +735,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n> >  \tif ((target = strstr(match, \" to \")) != NULL) {\n> >  \t\tlen = target - match;\n> >  \t\ttarget += 4;\n> > -\t\tif (target[len] == '\\n' && !strncmp(match, target, len))\n> > +\t\tif (len == strlen(target) && !strncmp(match, target, len))\n> >  \t\t\treturn 0;\n> >  \t}\n> >  \telse\n> \n> Actually, I think this patch to a884d0c (sha1_name: tweak @{-N} lookup,\n> 2009-01-17) would make more sense.\n> \n> -- >8 --\n> Subject: [PATCH] Simplify parsing branch switching events in reflog\n> \n> We only accept \"checkout: moving from A to B\" newer style reflog entries,\n> in order to pick up A.  There is no point computing where B begins at\n> after running strstr to locate \" to \", nor adding 4 and then subtracting 4\n> from the same pointer.\n\nYeah, you're right.\n\nCiao,\nDscho\n"},{"id":"101376","messageId":"alpine.DEB.1.00.0901211306270.3586@pacific.mpi-cbg.de","threadId":"17221","inReplyTo":"7vr62xezm8.fsf@gitster.siamese.dyndns.org","subject":"Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-01-21T12:06:46Z","receivedAt":"2009-01-21T12:06:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Wed, 21 Jan 2009, Junio C Hamano wrote:\n\n> Having said all that, I think Dscho's one had an off-by-one (but it is\n> getting late and it may be I who has one).\n\nYep, I missed the \"\\n\" at the end.\n\nCiao,\nDscho\n"},{"id":"101786","messageId":"200901242322.02440.trast@student.ethz.ch","threadId":"17221","inReplyTo":"200901211019.01493.trast@student.ethz.ch","subject":"Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-24T22:21:57Z","receivedAt":"2009-01-24T22:21:57Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Thomas Rast wrote:\n> Thanks for all the work you (both) are doing on this.  I hope to find\n> the time to read the current state of the series tonight.\n\nI was off by half a week :-(\n\nThe debugging 'git log --graph ...' that I forgot about is still in,\nsorry.  I'll follow up with a small patch.  Other than that, I didn't\nnotice anything, though admittedly it wouldn't have been very hard to\nsneak something past me.\n\nBy the way, you (Junio) remarked earlier in the thread that we could\nforbid any use of 'git checkout -' except in literally that command\n(i.e., no options or paths).  I'm midly opposed to it because I can\nsee myself saying 'git checkout -b sidebranch -' (and @{} is somewhat\nawkward to type on Swiss keyboards).  Admittedly 'git checkout - --\nfile' looks rather confusing.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"101787","messageId":"1232835794-22176-1-git-send-email-trast@student.ethz.ch","threadId":"17221","inReplyTo":"200901242322.02440.trast@student.ethz.ch","subject":"[PATCH next] t1505: remove debugging cruft","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2009-01-24T22:23:14Z","receivedAt":"2009-01-24T22:23:14Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Remove a call to git-log that I introduced for debugging and that\naccidentally made it into d18ba22 (sha1_name: support @{-N} syntax in\nget_sha1(), 2009-01-17).\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n t/t1505-rev-parse-last.sh |    2 --\n 1 files changed, 0 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh\nindex c745ec4..d709ecf 100755\n--- a/t/t1505-rev-parse-last.sh\n+++ b/t/t1505-rev-parse-last.sh\n@@ -32,8 +32,6 @@ test_expect_success 'setup' '\n #\n # and 'side' should be the last branch\n \n-git log --graph --all --pretty=oneline --decorate\n-\n test_rev_equivalent () {\n \n \tgit rev-parse \"$1\" > expect &&\n-- \n1.6.1.468.g15c0\n"},{"id":"101870","messageId":"7v8wozdsz7.fsf@gitster.siamese.dyndns.org","threadId":"17221","inReplyTo":"1232835794-22176-1-git-send-email-trast@student.ethz.ch","subject":"Re: [PATCH next] t1505: remove debugging cruft","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-01-25T20:35:24Z","receivedAt":"2009-01-25T20:35:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks.\n"}]}