From ae5a6c3684c378bc32c1f6ecc0e6dc45300c14c1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 17 Jan 2009 17:09:53 +0100 Subject: [PATCH 01/11] checkout: implement "@{-N}" shortcut name for N-th last branch Implement a shortcut @{-N} for the N-th last branch checked out, that works by parsing the reflog for the message added by previous git-checkout invocations. We expand the @{-N} to the branch name, so that you end up on an attached HEAD on that branch. Signed-off-by: Junio C Hamano --- builtin-checkout.c | 10 ++++-- cache.h | 1 + sha1_name.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index b5dd9c07b4..a3b69d6b94 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -361,8 +361,14 @@ struct branch_info { static void setup_branch_path(struct branch_info *branch) { struct strbuf buf = STRBUF_INIT; - strbuf_addstr(&buf, "refs/heads/"); - strbuf_addstr(&buf, branch->name); + + if (!interpret_nth_last_branch(branch->name, &buf)) { + branch->name = xstrdup(buf.buf); + strbuf_splice(&buf, 0, 0, "refs/heads/", 11); + } else { + strbuf_addstr(&buf, "refs/heads/"); + strbuf_addstr(&buf, branch->name); + } branch->path = strbuf_detach(&buf, NULL); } diff --git a/cache.h b/cache.h index 8e1af2669b..0dd9168be5 100644 --- a/cache.h +++ b/cache.h @@ -663,6 +663,7 @@ extern int read_ref(const char *filename, unsigned char *sha1); extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *); extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); +extern int interpret_nth_last_branch(const char *str, struct strbuf *); extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules); extern const char *ref_rev_parse_rules[]; diff --git a/sha1_name.c b/sha1_name.c index 159c2ab84f..6377264300 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -674,6 +674,84 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1) return retval; } +struct grab_nth_branch_switch_cbdata { + int counting; + int nth; + struct strbuf *buf; +}; + +static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) +{ + struct grab_nth_branch_switch_cbdata *cb = cb_data; + const char *match = NULL; + + if (!prefixcmp(message, "checkout: moving to ")) + match = message + strlen("checkout: moving to "); + else if (!prefixcmp(message, "checkout: moving from ")) { + const char *cp = message + strlen("checkout: moving from "); + if ((cp = strstr(cp, " to ")) != NULL) { + match = cp + 4; + } + } + + if (!match) + return 0; + + if (cb->counting) { + cb->nth++; + return 0; + } + + if (--cb->nth <= 0) { + size_t len = strlen(match); + while (match[len-1] == '\n') + len--; + strbuf_reset(cb->buf); + strbuf_add(cb->buf, match, len); + return 1; + } + return 0; +} + +/* + * This reads "@{-N}" syntax, finds the name of the Nth previous + * branch we were on, and places the name of the branch in the given + * buf and returns 0 if successful. + * + * If the input is not of the accepted format, it returns a negative + * number to signal an error. + */ +int interpret_nth_last_branch(const char *name, struct strbuf *buf) +{ + int nth, i; + struct grab_nth_branch_switch_cbdata cb; + + if (name[0] != '@' || name[1] != '{' || name[2] != '-') + return -1; + for (i = 3, nth = 0; name[i] && name[i] != '}'; i++) { + char ch = name[i]; + if ('0' <= ch && ch <= '9') + nth = nth * 10 + ch - '0'; + else + return -1; + } + if (nth < 0 || 10 <= nth) + return -1; + + cb.counting = 1; + cb.nth = 0; + cb.buf = buf; + for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb); + + cb.counting = 0; + cb.nth -= nth; + cb.buf = buf; + for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb); + return 0; +} + /* * This is like "get_sha1_basic()", except it allows "sha1 expressions", * notably "xyz^" for "parent of xyz" From a884d0cb71463c28d0329c593dce1ef9758f6177 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Sat, 17 Jan 2009 17:09:54 +0100 Subject: [PATCH 02/11] sha1_name: tweak @{-N} lookup Have the lookup only look at "interesting" checkouts, meaning those that tell you "Already on ..." don't count even though they also cause a reflog entry. Let interpret_nth_last_branch() return the number of characters parsed, so that git-checkout can verify that the branch spec was @{-N}, not @{-1}^2 or something like that. (The latter will be added later.) Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- builtin-checkout.c | 4 +++- sha1_name.c | 53 +++++++++++++++++++++++++--------------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index a3b69d6b94..dc1de06279 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -361,8 +361,10 @@ struct branch_info { static void setup_branch_path(struct branch_info *branch) { struct strbuf buf = STRBUF_INIT; + int ret; - if (!interpret_nth_last_branch(branch->name, &buf)) { + if ((ret = interpret_nth_last_branch(branch->name, &buf)) + && ret == strlen(branch->name)) { branch->name = xstrdup(buf.buf); strbuf_splice(&buf, 0, 0, "refs/heads/", 11); } else { diff --git a/sha1_name.c b/sha1_name.c index 6377264300..34e39db651 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -685,29 +685,28 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, const char *message, void *cb_data) { struct grab_nth_branch_switch_cbdata *cb = cb_data; - const char *match = NULL; - - if (!prefixcmp(message, "checkout: moving to ")) - match = message + strlen("checkout: moving to "); - else if (!prefixcmp(message, "checkout: moving from ")) { - const char *cp = message + strlen("checkout: moving from "); - if ((cp = strstr(cp, " to ")) != NULL) { - match = cp + 4; - } + const char *match = NULL, *target = NULL; + size_t len; + + if (!prefixcmp(message, "checkout: moving from ")) { + match = message + strlen("checkout: moving from "); + if ((target = strstr(match, " to ")) != NULL) + target += 4; } if (!match) return 0; + len = target - match - 4; + if (target[len] == '\n' && !strncmp(match, target, len)) + return 0; + if (cb->counting) { cb->nth++; return 0; } - if (--cb->nth <= 0) { - size_t len = strlen(match); - while (match[len-1] == '\n') - len--; + if (cb->nth-- <= 0) { strbuf_reset(cb->buf); strbuf_add(cb->buf, match, len); return 1; @@ -718,26 +717,28 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, /* * This reads "@{-N}" syntax, finds the name of the Nth previous * branch we were on, and places the name of the branch in the given - * buf and returns 0 if successful. + * buf and returns the number of characters parsed if successful. * * If the input is not of the accepted format, it returns a negative * number to signal an error. + * + * If the input was ok but there are not N branch switches in the + * reflog, it returns 0. */ int interpret_nth_last_branch(const char *name, struct strbuf *buf) { - int nth, i; + int nth; struct grab_nth_branch_switch_cbdata cb; + const char *brace; + char *num_end; if (name[0] != '@' || name[1] != '{' || name[2] != '-') return -1; - for (i = 3, nth = 0; name[i] && name[i] != '}'; i++) { - char ch = name[i]; - if ('0' <= ch && ch <= '9') - nth = nth * 10 + ch - '0'; - else - return -1; - } - if (nth < 0 || 10 <= nth) + brace = strchr(name, '}'); + if (!brace) + return -1; + nth = strtol(name+3, &num_end, 10); + if (num_end != brace) return -1; cb.counting = 1; @@ -745,11 +746,15 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf) cb.buf = buf; for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb); + if (cb.nth < nth) + return 0; + cb.counting = 0; cb.nth -= nth; cb.buf = buf; for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb); - return 0; + + return brace-name+1; } /* From d18ba22154574390dbff2c060f44b9715477e95a Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Sat, 17 Jan 2009 17:09:55 +0100 Subject: [PATCH 03/11] sha1_name: support @{-N} syntax in get_sha1() Let get_sha1() parse the @{-N} syntax, with docs and tests. Note that while @{-1}^2, @{-2}~5 and such are supported, @{-1}@{1} is currently not allowed. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- Documentation/git-rev-parse.txt | 3 ++ sha1_name.c | 16 ++++++-- t/t1505-rev-parse-last.sh | 71 +++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 3 deletions(-) create mode 100755 t/t1505-rev-parse-last.sh diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 2921da320d..3ccef2f2b3 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -212,6 +212,9 @@ when you run 'git-merge'. reflog of the current branch. For example, if you are on the branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'. +* The special construct '@\{-\}' means the th branch checked out + before the current one. + * A suffix '{caret}' to a revision parameter means the first parent of that commit object. '{caret}' means the th parent (i.e. 'rev{caret}' diff --git a/sha1_name.c b/sha1_name.c index 34e39db651..9e1538e3d2 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -297,6 +297,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) return logs_found; } +static int get_sha1_1(const char *name, int len, unsigned char *sha1); + static int get_sha1_basic(const char *str, int len, unsigned char *sha1) { static const char *warning = "warning: refname '%.*s' is ambiguous.\n"; @@ -307,7 +309,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) if (len == 40 && !get_sha1_hex(str, sha1)) return 0; - /* basic@{time or number} format to query ref-log */ + /* basic@{time or number or -number} format to query ref-log */ reflog_len = at = 0; if (str[len-1] == '}') { for (at = 0; at < len - 1; at++) { @@ -324,6 +326,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) return -1; if (!len && reflog_len) { + struct strbuf buf = STRBUF_INIT; + int ret; + /* try the @{-N} syntax for n-th checkout */ + ret = interpret_nth_last_branch(str+at, &buf); + if (ret > 0) { + /* substitute this branch name and restart */ + return get_sha1_1(buf.buf, buf.len, sha1); + } else if (ret == 0) { + return -1; + } /* allow "@{...}" to mean the current branch reflog */ refs_found = dwim_ref("HEAD", 4, sha1, &real_ref); } else if (reflog_len) @@ -379,8 +391,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) return 0; } -static int get_sha1_1(const char *name, int len, unsigned char *sha1); - static int get_parent(const char *name, int len, unsigned char *result, int idx) { diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh new file mode 100755 index 0000000000..1e49dd2c8f --- /dev/null +++ b/t/t1505-rev-parse-last.sh @@ -0,0 +1,71 @@ +#!/bin/sh + +test_description='test @{-N} syntax' + +. ./test-lib.sh + + +make_commit () { + echo "$1" > "$1" && + git add "$1" && + git commit -m "$1" +} + + +test_expect_success 'setup' ' + + make_commit 1 && + git branch side && + make_commit 2 && + make_commit 3 && + git checkout side && + make_commit 4 && + git merge master && + git checkout master + +' + +# 1 -- 2 -- 3 master +# \ \ +# \ \ +# --- 4 --- 5 side +# +# and 'side' should be the last branch + +git log --graph --all --pretty=oneline --decorate + +test_rev_equivalent () { + + git rev-parse "$1" > expect && + git rev-parse "$2" > output && + test_cmp expect output + +} + +test_expect_success '@{-1} works' ' + test_rev_equivalent side @{-1} +' + +test_expect_success '@{-1}~2 works' ' + test_rev_equivalent side~2 @{-1}~2 +' + +test_expect_success '@{-1}^2 works' ' + test_rev_equivalent side^2 @{-1}^2 +' + +test_expect_failure '@{-1}@{1} works' ' + test_rev_equivalent side@{1} @{-1}@{1} +' + +test_expect_success '@{-2} works' ' + test_rev_equivalent master @{-2} +' + +test_expect_success '@{-3} fails' ' + test_must_fail git rev-parse @{-3} +' + +test_done + + From 696acf45f9638b014c7132508de26d1a571c8a33 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Sat, 17 Jan 2009 17:09:56 +0100 Subject: [PATCH 04/11] checkout: implement "-" abbreviation, add docs and tests Have '-' mean the same as '@{-1}', i.e., the last branch we were on. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- Documentation/git-checkout.txt | 4 +++ builtin-checkout.c | 3 ++ t/t2012-checkout-last.sh | 50 ++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100755 t/t2012-checkout-last.sh diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 9cd51514db..3bccffae62 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -133,6 +133,10 @@ the conflicted merge in the specified paths. + When this parameter names a non-branch (but still a valid commit object), your HEAD becomes 'detached'. ++ +As a special case, the "`@\{-N\}`" syntax for the N-th last branch +checks out the branch (instead of detaching). You may also specify +"`-`" which is synonymous with "`@\{-1\}`". Detached HEAD diff --git a/builtin-checkout.c b/builtin-checkout.c index dc1de06279..b0a101bac7 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -679,6 +679,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) arg = argv[0]; has_dash_dash = (argc > 1) && !strcmp(argv[1], "--"); + if (!strcmp(arg, "-")) + arg = "@{-1}"; + if (get_sha1(arg, rev)) { if (has_dash_dash) /* case (1) */ die("invalid reference: %s", arg); diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh new file mode 100755 index 0000000000..320f6eb2be --- /dev/null +++ b/t/t2012-checkout-last.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +test_description='checkout can switch to last branch' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo hello >world && + git add world && + git commit -m initial && + git branch other && + echo "hello again" >>world && + git add world && + git commit -m second +' + +test_expect_success '"checkout -" does not work initially' ' + test_must_fail git checkout - +' + +test_expect_success 'first branch switch' ' + git checkout other +' + +test_expect_success '"checkout -" switches back' ' + git checkout - && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master" +' + +test_expect_success '"checkout -" switches forth' ' + git checkout - && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other" +' + +test_expect_success 'detach HEAD' ' + git checkout $(git rev-parse HEAD) +' + +test_expect_success '"checkout -" attaches again' ' + git checkout - && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other" +' + +test_expect_success '"checkout -" detaches again' ' + git checkout - && + test "z$(git rev-parse HEAD)" = "z$(git rev-parse other)" && + test_must_fail git symbolic-ref HEAD +' + +test_done From c2883e62f5b9980e5402431f2261c961354d0f15 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 19 Jan 2009 00:04:25 -0800 Subject: [PATCH 05/11] interpret_nth_last_branch(): avoid traversing the reflog twice You can have quite a many reflog entries, but you typically won't recall which branch you were on after switching branches for more than several times. Instead of reading the reflog twice, this reads the branch switching event and keeps as many entries as the user asked from the latest such entries, which is the minimum required to be able to switch back to the branch we were recently on. [jc: improvements from Dscho squashed in] Signed-off-by: Junio C Hamano --- sha1_name.c | 45 +++++++++++++++++++--------------------- t/t2012-checkout-last.sh | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 24 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index 9e1538e3d2..d6972f2d6a 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -685,8 +685,7 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1) } struct grab_nth_branch_switch_cbdata { - int counting; - int nth; + long cnt, alloc; struct strbuf *buf; }; @@ -697,6 +696,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, struct grab_nth_branch_switch_cbdata *cb = cb_data; const char *match = NULL, *target = NULL; size_t len; + int nth; if (!prefixcmp(message, "checkout: moving from ")) { match = message + strlen("checkout: moving from "); @@ -711,16 +711,9 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, if (target[len] == '\n' && !strncmp(match, target, len)) return 0; - if (cb->counting) { - cb->nth++; - return 0; - } - - if (cb->nth-- <= 0) { - strbuf_reset(cb->buf); - strbuf_add(cb->buf, match, len); - return 1; - } + nth = cb->cnt++ % cb->alloc; + strbuf_reset(&cb->buf[nth]); + strbuf_add(&cb->buf[nth], match, len); return 0; } @@ -737,7 +730,8 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, */ int interpret_nth_last_branch(const char *name, struct strbuf *buf) { - int nth; + long nth; + int i; struct grab_nth_branch_switch_cbdata cb; const char *brace; char *num_end; @@ -750,19 +744,22 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf) nth = strtol(name+3, &num_end, 10); if (num_end != brace) return -1; - - cb.counting = 1; - cb.nth = 0; - cb.buf = buf; + if (nth <= 0) + return -1; + cb.alloc = nth; + cb.buf = xmalloc(nth * sizeof(struct strbuf)); + for (i = 0; i < nth; i++) + strbuf_init(&cb.buf[i], 20); + cb.cnt = 0; for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb); - - if (cb.nth < nth) + if (cb.cnt < nth) return 0; - - cb.counting = 0; - cb.nth -= nth; - cb.buf = buf; - for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb); + i = cb.cnt % nth; + strbuf_reset(buf); + strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len); + for (i = 0; i < nth; i++) + strbuf_release(&cb.buf[i]); + free(cb.buf); return brace-name+1; } diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh index 320f6eb2be..87b30a268c 100755 --- a/t/t2012-checkout-last.sh +++ b/t/t2012-checkout-last.sh @@ -47,4 +47,48 @@ test_expect_success '"checkout -" detaches again' ' test_must_fail git symbolic-ref HEAD ' +test_expect_success 'more switches' ' + for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 + do + git checkout -b branch$i + done +' + +more_switches () { + for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 + do + git checkout branch$i + done +} + +test_expect_success 'switch to the last' ' + more_switches && + git checkout @{-1} && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch2" +' + +test_expect_success 'switch to second from the last' ' + more_switches && + git checkout @{-2} && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch3" +' + +test_expect_success 'switch to third from the last' ' + more_switches && + git checkout @{-3} && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch4" +' + +test_expect_success 'switch to fourth from the last' ' + more_switches && + git checkout @{-4} && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch5" +' + +test_expect_success 'switch to twelfth from the last' ' + more_switches && + git checkout @{-12} && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13" +' + test_done From aa9c55b66719c86896d134d35de8c263c078a481 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 17 Jan 2009 19:08:12 +0100 Subject: [PATCH 06/11] Fix parsing of @{-1}@{1} To do that, Git no longer looks forward for the '@{' corresponding to the closing '}' but backward, and dwim_ref() as well as dwim_log() learnt about the @{-} notation. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- sha1_name.c | 25 ++++++++++++++++++++++++- t/t1505-rev-parse-last.sh | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index d6972f2d6a..9d544a2633 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -238,8 +238,28 @@ static int ambiguous_path(const char *path, int len) return slash; } +/* + * *string and *len will only be substituted, and *string returned (for + * later free()ing) if the string passed in is of the form @{-}. + */ +static char *substitute_nth_last_branch(const char **string, int *len) +{ + struct strbuf buf = STRBUF_INIT; + int ret = interpret_nth_last_branch(*string, &buf); + + if (ret == *len) { + size_t size; + *string = strbuf_detach(&buf, &size); + *len = size; + return (char *)*string; + } + + return NULL; +} + int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) { + char *last_branch = substitute_nth_last_branch(&str, &len); const char **p, *r; int refs_found = 0; @@ -259,11 +279,13 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) break; } } + free(last_branch); return refs_found; } int dwim_log(const char *str, int len, unsigned char *sha1, char **log) { + char *last_branch = substitute_nth_last_branch(&str, &len); const char **p; int logs_found = 0; @@ -294,6 +316,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) if (!warn_ambiguous_refs) break; } + free(last_branch); return logs_found; } @@ -312,7 +335,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) /* basic@{time or number or -number} format to query ref-log */ reflog_len = at = 0; if (str[len-1] == '}') { - for (at = 0; at < len - 1; at++) { + for (at = len-2; at >= 0; at--) { if (str[at] == '@' && str[at+1] == '{') { reflog_len = (len-1) - (at+2); len = at; diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh index 1e49dd2c8f..c745ec4372 100755 --- a/t/t1505-rev-parse-last.sh +++ b/t/t1505-rev-parse-last.sh @@ -54,7 +54,7 @@ test_expect_success '@{-1}^2 works' ' test_rev_equivalent side^2 @{-1}^2 ' -test_expect_failure '@{-1}@{1} works' ' +test_expect_success '@{-1}@{1} works' ' test_rev_equivalent side@{1} @{-1}@{1} ' From c829774c30e10473d3139edf92a4afe36e8abdc2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 19 Jan 2009 16:44:08 -0800 Subject: [PATCH 07/11] Fix reflog parsing for a malformed branch switching entry target can be NULL when we failed to parse the message. Signed-off-by: Junio C Hamano --- sha1_name.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sha1_name.c b/sha1_name.c index 9d544a2633..f54b6cb36a 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -727,7 +727,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, target += 4; } - if (!match) + if (!match || !target) return 0; len = target - match - 4; From 39765e5941d36f74cec4764d315da0ba5743547c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 19 Jan 2009 21:58:31 -0800 Subject: [PATCH 08/11] interpret_nth_last_branch(): plug small memleak Signed-off-by: Junio C Hamano --- sha1_name.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index f54b6cb36a..4c0370bb79 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -754,7 +754,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, int interpret_nth_last_branch(const char *name, struct strbuf *buf) { long nth; - int i; + int i, retval; struct grab_nth_branch_switch_cbdata cb; const char *brace; char *num_end; @@ -774,17 +774,21 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf) for (i = 0; i < nth; i++) strbuf_init(&cb.buf[i], 20); cb.cnt = 0; + retval = 0; for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb); if (cb.cnt < nth) - return 0; + goto release_return; i = cb.cnt % nth; strbuf_reset(buf); strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len); + retval = brace-name+1; + +release_return: for (i = 0; i < nth; i++) strbuf_release(&cb.buf[i]); free(cb.buf); - return brace-name+1; + return retval; } /* From 101d15e09712a0183db99d228d975c62970654cf Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 19 Jan 2009 22:18:29 -0800 Subject: [PATCH 09/11] Introduce for_each_recent_reflog_ent(). This can be used to scan only the last few kilobytes of a reflog, as a cheap optimization when the data you are looking for is likely to be found near the end of it. The caller is expected to fall back to the full scan if that is not the case. Signed-off-by: Junio C Hamano --- refs.c | 17 ++++++++++++++++- refs.h | 1 + sha1_name.c | 8 +++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/refs.c b/refs.c index 33ced65a78..024211d72b 100644 --- a/refs.c +++ b/refs.c @@ -1453,7 +1453,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * return 1; } -int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) +int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data) { const char *logfile; FILE *logfp; @@ -1464,6 +1464,16 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) logfp = fopen(logfile, "r"); if (!logfp) return -1; + + if (ofs) { + struct stat statbuf; + if (fstat(fileno(logfp), &statbuf) || + statbuf.st_size < ofs || + fseek(logfp, -ofs, SEEK_END) || + fgets(buf, sizeof(buf), logfp)) + return -1; + } + while (fgets(buf, sizeof(buf), logfp)) { unsigned char osha1[20], nsha1[20]; char *email_end, *message; @@ -1497,6 +1507,11 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) return ret; } +int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) +{ + return for_each_recent_reflog_ent(ref, fn, 0, cb_data); +} + static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data) { DIR *dir = opendir(git_path("logs/%s", base)); diff --git a/refs.h b/refs.h index 06ad260556..3bb529d387 100644 --- a/refs.h +++ b/refs.h @@ -60,6 +60,7 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned /* iterate over reflog entries */ typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *); int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data); +int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, void *cb_data); /* * Calls the specified function for each reflog file until it returns nonzero, diff --git a/sha1_name.c b/sha1_name.c index 4c0370bb79..38c9f1b19e 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -775,7 +775,13 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf) strbuf_init(&cb.buf[i], 20); cb.cnt = 0; retval = 0; - for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb); + for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb); + if (cb.cnt < nth) { + cb.cnt = 0; + for (i = 0; i < nth; i++) + strbuf_release(&cb.buf[i]); + for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb); + } if (cb.cnt < nth) goto release_return; i = cb.cnt % nth; From d7c03c1ff98be1d22dd18b70669ffc6fb76b39b3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Jan 2009 00:37:38 -0800 Subject: [PATCH 10/11] Simplify parsing branch switching events in reflog We only accept "checkout: moving from A to B" newer style reflog entries, in order to pick up A. There is no point computing where B begins at after running strstr to locate " to ", nor adding 4 and then subtracting 4 from the same pointer. Signed-off-by: Junio C Hamano --- sha1_name.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index 38c9f1b19e..7d95bbb27a 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -723,17 +723,13 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, if (!prefixcmp(message, "checkout: moving from ")) { match = message + strlen("checkout: moving from "); - if ((target = strstr(match, " to ")) != NULL) - target += 4; + target = strstr(match, " to "); } if (!match || !target) return 0; - len = target - match - 4; - if (target[len] == '\n' && !strncmp(match, target, len)) - return 0; - + len = target - match; nth = cb->cnt++ % cb->alloc; strbuf_reset(&cb->buf[nth]); strbuf_add(&cb->buf[nth], match, len); From 73ff1a131b340633b2ec2a0e68490de721448f56 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Sat, 24 Jan 2009 23:23:14 +0100 Subject: [PATCH 11/11] t1505: remove debugging cruft Remove a call to git-log that I introduced for debugging and that accidentally made it into d18ba22 (sha1_name: support @{-N} syntax in get_sha1(), 2009-01-17). Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- t/t1505-rev-parse-last.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh index c745ec4372..d709ecf8df 100755 --- a/t/t1505-rev-parse-last.sh +++ b/t/t1505-rev-parse-last.sh @@ -32,8 +32,6 @@ test_expect_success 'setup' ' # # and 'side' should be the last branch -git log --graph --all --pretty=oneline --decorate - test_rev_equivalent () { git rev-parse "$1" > expect &&