* [PATCH/RFC v3 0/6] N-th last checked out branch
2009-01-15 20:50 ` Junio C Hamano
@ 2009-01-17 3:30 ` Thomas Rast
2009-01-17 5:52 ` Johannes Schindelin
2009-01-17 3:30 ` [PATCH/RFC v3 1/6] reflog: refactor parsing and checking Thomas Rast
` (5 subsequent siblings)
6 siblings, 1 reply; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 3:30 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland
Junio C Hamano <gitster@pobox.com> writes:
> "cd -" is a very good analogy why your "-" shortcut is a short-sighted
> convenience feature that is too narrow and not well designed. "cd -" can
> go back, but you cannot say "ls -" to list the contents of the previous
> directory.
True.
> Another reason is the one level limitation.
It shares that limitation with my mind ;-)
But well, since you all seem to want a more general solution, here's a
draft.
I started poking around in refs.c for the backwards search, but the
two reflog functions had a lot of functionality implemented very
differently. So 1-4 are just cleanup and adding that backwards
iterator. Note that 3/6 changes for_each_reflog_ent() to die() when
it finds a format error, unlike the previous behaviour. No tests
seemed to care, but maybe someone out there relies on having broken
reflogs? ;-)
> * The code read the reflog twice, first to count how many branch
> switching there are and then to locate the N-th entry we are interested
> in, because I was lazy. We may want an API to enumerate reflog entries
> in reverse.
So this might be settled.
> * The reflog parser only parses "checkout" and not rebase action. It
> also does not notice "git checkout HEAD^" is not switching to a real
> branch.
I don't handle this, but I'm not sure how much of a problem it is. It
can correctly detach and re-attach even in cases such as HEAD^.
Ok, now that you mention it, it will probably put you on the parent of
whatever you happened to be on, instead of the earlier value of HEAD^.
> $ git checkout @{-1}
One problem with the syntax is that the assumption that there can only
be a single @{} construct is quite entrenched in sha1_name.c. So if
we want to support @{-1}@{1}, that'll need some extra work.
@{-1}~2 and similar work fine though.
Other things of note:
I changed the semantics to really only look at checkouts that "did
something". If you keep saying 'git checkout master' over and over,
those will not count towards the N.
I changed the parser to read the 'checkout: moving from $old' instead
of the 'to $new'. The above semantics introduced too many extra
conditions for my taste if we wanted to support the pre-1.5.3 reflog
comment format. (You'd have to check if the $to was changed from the
last [newer] entry, but then there's another border case when you do
'git init; git checkout -b side; git checkout -' because you never
moved to master.)
The @{date} syntax will be marginally slower after the refactoring
because the old parser carefully avoided parsing numbers where it
could. (I guess it could actually be done as a bisection for an extra
order of magnitude.)
It's far too early here to be sending mail.
Interactive rebase needs a "move hunk" feature.
Thomas Rast (6):
reflog: refactor parsing and checking
reflog: refactor log open+mmap
reflog: make for_each_reflog_ent use mmap
reflog: add backwards iterator
sha1_name: implement @{-N} syntax for N-th last checked out
checkout: implement '@{-N}' and '-' special abbreviations
Documentation/git-checkout.txt | 4 +
Documentation/git-rev-parse.txt | 3 +
builtin-checkout.c | 15 ++-
cache.h | 1 +
refs.c | 285 +++++++++++++++++++++++----------------
refs.h | 1 +
sha1_name.c | 79 +++++++++++-
t/t1505-rev-parse-last.sh | 71 ++++++++++
t/t2012-checkout-last.sh | 50 +++++++
9 files changed, 387 insertions(+), 122 deletions(-)
create mode 100755 t/t1505-rev-parse-last.sh
create mode 100755 t/t2012-checkout-last.sh
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH/RFC v3 0/6] N-th last checked out branch
2009-01-17 3:30 ` [PATCH/RFC v3 0/6] N-th last checked out branch Thomas Rast
@ 2009-01-17 5:52 ` Johannes Schindelin
2009-01-17 13:38 ` Thomas Rast
0 siblings, 1 reply; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-17 5:52 UTC (permalink / raw)
To: Thomas Rast; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland
Hi,
On Sat, 17 Jan 2009, Thomas Rast wrote:
> Documentation/git-checkout.txt | 4 +
> Documentation/git-rev-parse.txt | 3 +
> builtin-checkout.c | 15 ++-
> cache.h | 1 +
> refs.c | 285 +++++++++++++++++++++++----------------
> refs.h | 1 +
> sha1_name.c | 79 +++++++++++-
> t/t1505-rev-parse-last.sh | 71 ++++++++++
> t/t2012-checkout-last.sh | 50 +++++++
> 9 files changed, 387 insertions(+), 122 deletions(-)
> create mode 100755 t/t1505-rev-parse-last.sh
> create mode 100755 t/t2012-checkout-last.sh
Let's quickly compare that to what Junio sent:
builtin-checkout.c | 10 +++++-
cache.h | 1 +
sha1_name.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 87 insertions(+), 2 deletions(-)
Granted, the documentation and tests are nice, but why that big change in
refs.c? After all, the more you change in one go, the more you can break.
Let's take one step back. The two things that Junio's patch left to be
desired (in addition to documentation and tests) are
- reflogs are traversed twice. While this is not a showstopper, it is
easily improved by building a string_list and then picking the one entry
we're interested in, and
- the '-' handling you seem to want.
I really have to ask: why did you not work on top of Junio's patch, just
adding docs, tests, and checkout -? And then -- maybe -- the
string_list...
Although I have to admit that I am not _that_ convinced the string_list is
worth it: reflogs are not evaluated all the time, so it is definitely not
performance critical.
Ciao,
Dscho
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH/RFC v3 0/6] N-th last checked out branch
2009-01-17 5:52 ` Johannes Schindelin
@ 2009-01-17 13:38 ` Thomas Rast
2009-01-17 13:40 ` [PATCH/RFC v3bis 1/2] sha1_name: implement @{-N} syntax for N-th last checked out Thomas Rast
2009-01-17 15:04 ` [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice Johannes Schindelin
0 siblings, 2 replies; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 13:38 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland
[-- Attachment #1: Type: text/plain, Size: 1170 bytes --]
Johannes Schindelin wrote:
> On Sat, 17 Jan 2009, Thomas Rast wrote:
> > 9 files changed, 387 insertions(+), 122 deletions(-)
>
> Let's quickly compare that to what Junio sent:
>
> builtin-checkout.c | 10 +++++-
> cache.h | 1 +
> sha1_name.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 87 insertions(+), 2 deletions(-)
[...]
> - the '-' handling you seem to want.
Let's be clear that the handling *I* wanted is this big:
builtin-checkout.c | 27 ++++++++++++++++-
In any case, I'll follow up with a version that still traverses the
logs twice and thus doesn't need the first two.
> I really have to ask: why did you not work on top of Junio's patch, just
> adding docs, tests, and checkout -? And then -- maybe -- the
> string_list...
>
> Although I have to admit that I am not _that_ convinced the string_list is
> worth it: reflogs are not evaluated all the time, so it is definitely not
> performance critical.
I take it you have some idea where and how string_list fits into this
topic?
--
Thomas Rast
trast@{inf,student}.ethz.ch
[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 197 bytes --]
^ permalink raw reply [flat|nested] 102+ messages in thread
* [PATCH/RFC v3bis 1/2] sha1_name: implement @{-N} syntax for N-th last checked out
2009-01-17 13:38 ` Thomas Rast
@ 2009-01-17 13:40 ` Thomas Rast
2009-01-17 13:40 ` [PATCH/RFC v3bis 2/2] checkout: implement '@{-N}' and '-' special abbreviations Thomas Rast
2009-01-17 15:04 ` [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice Johannes Schindelin
1 sibling, 1 reply; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 13:40 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland
Implements a new syntax @{-N} that parses the reflog for the N-th last
interesting 'checkout: moving from $branch to $new' entry and
substitutes $branch. Here, "interesting" is defined as $branch !=
$new. We then substitute the real branch name for the parse.
For example:
git checkout foo
git checkout bar
git checkout master
git checkout master # did not move, so doesn't count
git rev-parse @{-1} # same as bar
git rev-parse @{-2} # same as foo
git rev-parse @{-2}~3 # same as foo~3
Thanks to Junio for much of the code.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
Documentation/git-rev-parse.txt | 3 +
cache.h | 1 +
sha1_name.c | 99 +++++++++++++++++++++++++++++++++++++-
t/t1505-rev-parse-last.sh | 71 ++++++++++++++++++++++++++++
4 files changed, 171 insertions(+), 3 deletions(-)
create mode 100755 t/t1505-rev-parse-last.sh
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 2921da3..3ccef2f 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -212,6 +212,9 @@ when you run 'git-merge'.
reflog of the current branch. For example, if you are on the
branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
+* The special construct '@\{-<n>\}' means the <n>th branch checked out
+ before the current one.
+
* A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e.
'rev{caret}'
diff --git a/cache.h b/cache.h
index 8e1af26..0dd9168 100644
--- a/cache.h
+++ b/cache.h
@@ -663,6 +663,7 @@ static inline unsigned int hexval(unsigned char c)
extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
+extern int interpret_nth_last_branch(const char *str, struct strbuf *);
extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
extern const char *ref_rev_parse_rules[];
diff --git a/sha1_name.c b/sha1_name.c
index 159c2ab..9e1538e 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -297,6 +297,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
return logs_found;
}
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
{
static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
@@ -307,7 +309,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
if (len == 40 && !get_sha1_hex(str, sha1))
return 0;
- /* basic@{time or number} format to query ref-log */
+ /* basic@{time or number or -number} format to query ref-log */
reflog_len = at = 0;
if (str[len-1] == '}') {
for (at = 0; at < len - 1; at++) {
@@ -324,6 +326,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
return -1;
if (!len && reflog_len) {
+ struct strbuf buf = STRBUF_INIT;
+ int ret;
+ /* try the @{-N} syntax for n-th checkout */
+ ret = interpret_nth_last_branch(str+at, &buf);
+ if (ret > 0) {
+ /* substitute this branch name and restart */
+ return get_sha1_1(buf.buf, buf.len, sha1);
+ } else if (ret == 0) {
+ return -1;
+ }
/* allow "@{...}" to mean the current branch reflog */
refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
} else if (reflog_len)
@@ -379,8 +391,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
return 0;
}
-static int get_sha1_1(const char *name, int len, unsigned char *sha1);
-
static int get_parent(const char *name, int len,
unsigned char *result, int idx)
{
@@ -674,6 +684,89 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
return retval;
}
+struct grab_nth_branch_switch_cbdata {
+ int counting;
+ int nth;
+ struct strbuf *buf;
+};
+
+static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
+{
+ struct grab_nth_branch_switch_cbdata *cb = cb_data;
+ const char *match = NULL, *target = NULL;
+ size_t len;
+
+ if (!prefixcmp(message, "checkout: moving from ")) {
+ match = message + strlen("checkout: moving from ");
+ if ((target = strstr(match, " to ")) != NULL)
+ target += 4;
+ }
+
+ if (!match)
+ return 0;
+
+ len = target - match - 4;
+ if (target[len] == '\n' && !strncmp(match, target, len))
+ return 0;
+
+ if (cb->counting) {
+ cb->nth++;
+ return 0;
+ }
+
+ if (cb->nth-- <= 0) {
+ strbuf_reset(cb->buf);
+ strbuf_add(cb->buf, match, len);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * This reads "@{-N}" syntax, finds the name of the Nth previous
+ * branch we were on, and places the name of the branch in the given
+ * buf and returns the number of characters parsed if successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ *
+ * If the input was ok but there are not N branch switches in the
+ * reflog, it returns 0.
+ */
+int interpret_nth_last_branch(const char *name, struct strbuf *buf)
+{
+ int nth;
+ struct grab_nth_branch_switch_cbdata cb;
+ const char *brace;
+ char *num_end;
+
+ if (name[0] != '@' || name[1] != '{' || name[2] != '-')
+ return -1;
+ brace = strchr(name, '}');
+ if (!brace)
+ return -1;
+ nth = strtol(name+3, &num_end, 10);
+ if (num_end != brace)
+ return -1;
+
+ cb.counting = 1;
+ cb.nth = 0;
+ cb.buf = buf;
+ for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+
+ if (cb.nth < nth)
+ return 0;
+
+ cb.counting = 0;
+ cb.nth -= nth;
+ cb.buf = buf;
+ for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+
+ return brace-name+1;
+}
+
/*
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
* notably "xyz^" for "parent of xyz"
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
new file mode 100755
index 0000000..1e49dd2
--- /dev/null
+++ b/t/t1505-rev-parse-last.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+test_description='test @{-N} syntax'
+
+. ./test-lib.sh
+
+
+make_commit () {
+ echo "$1" > "$1" &&
+ git add "$1" &&
+ git commit -m "$1"
+}
+
+
+test_expect_success 'setup' '
+
+ make_commit 1 &&
+ git branch side &&
+ make_commit 2 &&
+ make_commit 3 &&
+ git checkout side &&
+ make_commit 4 &&
+ git merge master &&
+ git checkout master
+
+'
+
+# 1 -- 2 -- 3 master
+# \ \
+# \ \
+# --- 4 --- 5 side
+#
+# and 'side' should be the last branch
+
+git log --graph --all --pretty=oneline --decorate
+
+test_rev_equivalent () {
+
+ git rev-parse "$1" > expect &&
+ git rev-parse "$2" > output &&
+ test_cmp expect output
+
+}
+
+test_expect_success '@{-1} works' '
+ test_rev_equivalent side @{-1}
+'
+
+test_expect_success '@{-1}~2 works' '
+ test_rev_equivalent side~2 @{-1}~2
+'
+
+test_expect_success '@{-1}^2 works' '
+ test_rev_equivalent side^2 @{-1}^2
+'
+
+test_expect_failure '@{-1}@{1} works' '
+ test_rev_equivalent side@{1} @{-1}@{1}
+'
+
+test_expect_success '@{-2} works' '
+ test_rev_equivalent master @{-2}
+'
+
+test_expect_success '@{-3} fails' '
+ test_must_fail git rev-parse @{-3}
+'
+
+test_done
+
+
--
1.6.1.315.g92577
^ permalink raw reply related [flat|nested] 102+ messages in thread
* [PATCH/RFC v3bis 2/2] checkout: implement '@{-N}' and '-' special abbreviations
2009-01-17 13:40 ` [PATCH/RFC v3bis 1/2] sha1_name: implement @{-N} syntax for N-th last checked out Thomas Rast
@ 2009-01-17 13:40 ` Thomas Rast
0 siblings, 0 replies; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 13:40 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland
Checks if the branch to be checked out is either '@{-N}' or the
special shorthand '-' for '@{-1}' (i.e. the last checked out branch).
If so, we take it to mean the branch name, not the corresponding SHA,
so that we check out an attached HEAD on that branch.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
Documentation/git-checkout.txt | 4 +++
builtin-checkout.c | 15 ++++++++++-
t/t2012-checkout-last.sh | 50 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 67 insertions(+), 2 deletions(-)
create mode 100755 t/t2012-checkout-last.sh
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 9cd5151..3bccffa 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -133,6 +133,10 @@ the conflicted merge in the specified paths.
+
When this parameter names a non-branch (but still a valid commit object),
your HEAD becomes 'detached'.
++
+As a special case, the "`@\{-N\}`" syntax for the N-th last branch
+checks out the branch (instead of detaching). You may also specify
+"`-`" which is synonymous with "`@\{-1\}`".
Detached HEAD
diff --git a/builtin-checkout.c b/builtin-checkout.c
index b5dd9c0..b0a101b 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -361,8 +361,16 @@ struct branch_info {
static void setup_branch_path(struct branch_info *branch)
{
struct strbuf buf = STRBUF_INIT;
- strbuf_addstr(&buf, "refs/heads/");
- strbuf_addstr(&buf, branch->name);
+ int ret;
+
+ if ((ret = interpret_nth_last_branch(branch->name, &buf))
+ && ret == strlen(branch->name)) {
+ branch->name = xstrdup(buf.buf);
+ strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
+ } else {
+ strbuf_addstr(&buf, "refs/heads/");
+ strbuf_addstr(&buf, branch->name);
+ }
branch->path = strbuf_detach(&buf, NULL);
}
@@ -671,6 +679,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
arg = argv[0];
has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
+ if (!strcmp(arg, "-"))
+ arg = "@{-1}";
+
if (get_sha1(arg, rev)) {
if (has_dash_dash) /* case (1) */
die("invalid reference: %s", arg);
diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
new file mode 100755
index 0000000..320f6eb
--- /dev/null
+++ b/t/t2012-checkout-last.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='checkout can switch to last branch'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo hello >world &&
+ git add world &&
+ git commit -m initial &&
+ git branch other &&
+ echo "hello again" >>world &&
+ git add world &&
+ git commit -m second
+'
+
+test_expect_success '"checkout -" does not work initially' '
+ test_must_fail git checkout -
+'
+
+test_expect_success 'first branch switch' '
+ git checkout other
+'
+
+test_expect_success '"checkout -" switches back' '
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master"
+'
+
+test_expect_success '"checkout -" switches forth' '
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success 'detach HEAD' '
+ git checkout $(git rev-parse HEAD)
+'
+
+test_expect_success '"checkout -" attaches again' '
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success '"checkout -" detaches again' '
+ git checkout - &&
+ test "z$(git rev-parse HEAD)" = "z$(git rev-parse other)" &&
+ test_must_fail git symbolic-ref HEAD
+'
+
+test_done
--
1.6.1.315.g92577
^ permalink raw reply related [flat|nested] 102+ messages in thread
* [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-17 13:38 ` Thomas Rast
2009-01-17 13:40 ` [PATCH/RFC v3bis 1/2] sha1_name: implement @{-N} syntax for N-th last checked out Thomas Rast
@ 2009-01-17 15:04 ` Johannes Schindelin
2009-01-17 16:09 ` [PATCH/RFC v4 0/5] N-th last checked out branch Thomas Rast
2009-01-17 19:13 ` [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice Junio C Hamano
1 sibling, 2 replies; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-17 15:04 UTC (permalink / raw)
To: Thomas Rast; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland
Instead of traversing them twice, we just build a list of branch switches,
pick the one we're interested in, and free the list again.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
On Sat, 17 Jan 2009, Thomas Rast wrote:
> Johannes Schindelin wrote:
>
> > I really have to ask: why did you not work on top of Junio's
> > patch, just adding docs, tests, and checkout -? And then -- maybe --
> > the string_list...
Of course, I meant the patch as-is, with Junio as author. But
hey, if he does not care...
> > Although I have to admit that I am not _that_ convinced the
> > string_list is worth it: reflogs are not evaluated all the time, so
> > it is definitely not performance critical.
>
> I take it you have some idea where and how string_list fits into
> this topic?
Indeed.
This patch was generated using --collapse-non-alnums for readability.
Did not really help much.
sha1_name.c | 72 ++++++++++++++++++++++++----------------------------------
1 files changed, 30 insertions(+), 42 deletions(-)
diff --git a/sha1_name.c b/sha1_name.c
index 2bbc5f1..306d04b 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -6,6 +6,7 @@
#include "tree-walk.h"
#include "refs.h"
#include "cache-tree.h"
+#include "string-list.h"
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
{
@@ -691,43 +692,31 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
return retval;
}
-struct grab_nth_branch_switch_cbdata {
- int counting;
- int nth;
- struct strbuf *buf;
-};
-
-static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
+static int add_one_branch_switch(unsigned char *osha1, unsigned char *nsha1,
const char *email, unsigned long timestamp, int tz,
const char *message, void *cb_data)
{
- struct grab_nth_branch_switch_cbdata *cb = cb_data;
+ struct string_list *list = cb_data;
const char *match = NULL, *target = NULL;
size_t len;
-
- if (!prefixcmp(message, "checkout: moving from ")) {
- match = message + strlen("checkout: moving from ");
- if ((target = strstr(match, " to ")) != NULL)
- target += 4;
- }
-
- if (!match)
+
+ if (prefixcmp(message, "checkout: moving from "))
return 0;
- len = target - match - 4;
- if (target[len] == '\n' && !strncmp(match, target, len))
- return 0;
-
- if (cb->counting) {
- cb->nth++;
- return 0;
- }
-
- if (cb->nth-- <= 0) {
- strbuf_reset(cb->buf);
- strbuf_add(cb->buf, match, len);
- return 1;
- }
+ match = message + strlen("checkout: moving from ");
+
+ /* Is it "moving" from a branch to itself? Then ignore it. */
+ if ((target = strstr(match, " to ")) != NULL) {
+ target += 4;
+ len = target - match - 4;
+ if (target[len] == '\n' && !strncmp(match, target, len))
+ return 0;
+ }
+ else
+ len = strchrnul(match, ' ') - match;
+
+ string_list_append(xstrndup(match, len), list);
+
return 0;
}
@@ -745,7 +734,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
int interpret_nth_last_branch(const char *name, struct strbuf *buf)
{
int nth;
- struct grab_nth_branch_switch_cbdata cb;
+ struct string_list branch_list = { NULL, 0, 0, 0 };
const char *brace;
char *num_end;
@@ -758,18 +747,17 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
if (num_end != brace)
return -1;
- cb.counting = 1;
- cb.nth = 0;
- cb.buf = buf;
- for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
-
- if (cb.nth < nth)
+ for_each_reflog_ent("HEAD", add_one_branch_switch, &branch_list);
+
+ if (branch_list.nr < nth)
return 0;
-
- cb.counting = 0;
- cb.nth -= nth;
- cb.buf = buf;
- for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+
+ strbuf_reset(buf);
+ strbuf_addstr(buf, branch_list.items[branch_list.nr - nth].string);
+
+ /* force free()ing the items */
+ branch_list.strdup_strings = 1;
+ string_list_clear(&branch_list, 0);
return brace-name+1;
}
--
1.6.1.325.g062d4
^ permalink raw reply related [flat|nested] 102+ messages in thread
* [PATCH/RFC v4 0/5] N-th last checked out branch
2009-01-17 15:04 ` [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice Johannes Schindelin
@ 2009-01-17 16:09 ` Thomas Rast
2009-01-17 16:09 ` [PATCH/RFC v4 1/5] checkout: implement "@{-N}" shortcut name for N-th last branch Thomas Rast
2009-01-17 16:49 ` [PATCH/RFC v4 0/5] N-th last checked out branch Johannes Schindelin
2009-01-17 19:13 ` [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice Junio C Hamano
1 sibling, 2 replies; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 16:09 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland
Johannes Schindelin wrote:
>
> Instead of traversing them twice, we just build a list of branch switches,
> pick the one we're interested in, and free the list again.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>
> On Sat, 17 Jan 2009, Thomas Rast wrote:
>
> > Johannes Schindelin wrote:
> >
> > > I really have to ask: why did you not work on top of Junio's
> > > patch, just adding docs, tests, and checkout -? And then -- maybe --
> > > the string_list...
>
> Of course, I meant the patch as-is, with Junio as author. But
> hey, if he does not care...
*shrug*
For one thing there was no commit message.
I made up a message, split the changes I made into smaller patches,
and added a fixed up version of your patch on top, since it had some
context that is not in any version I have.
Johannes Schindelin (1):
interpret_nth_last_branch(): avoid traversing the reflogs twice
Junio C Hamano (1):
checkout: implement "@{-N}" shortcut name for N-th last branch
Thomas Rast (3):
sha1_name: tweak @{-N} lookup
sha1_name: support @{-N} syntax in get_sha1()
checkout: implement "-" abbreviation, add docs and tests
Documentation/git-checkout.txt | 4 ++
Documentation/git-rev-parse.txt | 3 +
builtin-checkout.c | 15 ++++++-
cache.h | 1 +
sha1_name.c | 88 +++++++++++++++++++++++++++++++++++++-
t/t1505-rev-parse-last.sh | 71 +++++++++++++++++++++++++++++++
t/t2012-checkout-last.sh | 50 ++++++++++++++++++++++
7 files changed, 227 insertions(+), 5 deletions(-)
create mode 100755 t/t1505-rev-parse-last.sh
create mode 100755 t/t2012-checkout-last.sh
^ permalink raw reply [flat|nested] 102+ messages in thread
* [PATCH/RFC v4 1/5] checkout: implement "@{-N}" shortcut name for N-th last branch
2009-01-17 16:09 ` [PATCH/RFC v4 0/5] N-th last checked out branch Thomas Rast
@ 2009-01-17 16:09 ` Thomas Rast
2009-01-17 16:09 ` [PATCH/RFC v4 2/5] sha1_name: tweak @{-N} lookup Thomas Rast
2009-01-17 16:49 ` [PATCH/RFC v4 0/5] N-th last checked out branch Johannes Schindelin
1 sibling, 1 reply; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 16:09 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland,
Junio C Hamano
From: Junio C Hamano <gitster@pobox.com>
Implement a shortcut @{-N} for the N-th last branch checked out, that
works by parsing the reflog for the message added by previous
git-checkout invocations. We expand the @{-N} to the branch name, so
that you end up on an attached HEAD on that branch.
---
builtin-checkout.c | 10 +++++-
cache.h | 1 +
sha1_name.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 87 insertions(+), 2 deletions(-)
diff --git a/builtin-checkout.c b/builtin-checkout.c
index b5dd9c0..a3b69d6 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -361,8 +361,14 @@ struct branch_info {
static void setup_branch_path(struct branch_info *branch)
{
struct strbuf buf = STRBUF_INIT;
- strbuf_addstr(&buf, "refs/heads/");
- strbuf_addstr(&buf, branch->name);
+
+ if (!interpret_nth_last_branch(branch->name, &buf)) {
+ branch->name = xstrdup(buf.buf);
+ strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
+ } else {
+ strbuf_addstr(&buf, "refs/heads/");
+ strbuf_addstr(&buf, branch->name);
+ }
branch->path = strbuf_detach(&buf, NULL);
}
diff --git a/cache.h b/cache.h
index 8e1af26..0dd9168 100644
--- a/cache.h
+++ b/cache.h
@@ -663,6 +663,7 @@ static inline unsigned int hexval(unsigned char c)
extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
+extern int interpret_nth_last_branch(const char *str, struct strbuf *);
extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
extern const char *ref_rev_parse_rules[];
diff --git a/sha1_name.c b/sha1_name.c
index 159c2ab..6377264 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -674,6 +674,84 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
return retval;
}
+struct grab_nth_branch_switch_cbdata {
+ int counting;
+ int nth;
+ struct strbuf *buf;
+};
+
+static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
+{
+ struct grab_nth_branch_switch_cbdata *cb = cb_data;
+ const char *match = NULL;
+
+ if (!prefixcmp(message, "checkout: moving to "))
+ match = message + strlen("checkout: moving to ");
+ else if (!prefixcmp(message, "checkout: moving from ")) {
+ const char *cp = message + strlen("checkout: moving from ");
+ if ((cp = strstr(cp, " to ")) != NULL) {
+ match = cp + 4;
+ }
+ }
+
+ if (!match)
+ return 0;
+
+ if (cb->counting) {
+ cb->nth++;
+ return 0;
+ }
+
+ if (--cb->nth <= 0) {
+ size_t len = strlen(match);
+ while (match[len-1] == '\n')
+ len--;
+ strbuf_reset(cb->buf);
+ strbuf_add(cb->buf, match, len);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * This reads "@{-N}" syntax, finds the name of the Nth previous
+ * branch we were on, and places the name of the branch in the given
+ * buf and returns 0 if successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ */
+int interpret_nth_last_branch(const char *name, struct strbuf *buf)
+{
+ int nth, i;
+ struct grab_nth_branch_switch_cbdata cb;
+
+ if (name[0] != '@' || name[1] != '{' || name[2] != '-')
+ return -1;
+ for (i = 3, nth = 0; name[i] && name[i] != '}'; i++) {
+ char ch = name[i];
+ if ('0' <= ch && ch <= '9')
+ nth = nth * 10 + ch - '0';
+ else
+ return -1;
+ }
+ if (nth < 0 || 10 <= nth)
+ return -1;
+
+ cb.counting = 1;
+ cb.nth = 0;
+ cb.buf = buf;
+ for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+
+ cb.counting = 0;
+ cb.nth -= nth;
+ cb.buf = buf;
+ for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+ return 0;
+}
+
/*
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
* notably "xyz^" for "parent of xyz"
--
1.6.1.315.g92577
^ permalink raw reply related [flat|nested] 102+ messages in thread
* [PATCH/RFC v4 2/5] sha1_name: tweak @{-N} lookup
2009-01-17 16:09 ` [PATCH/RFC v4 1/5] checkout: implement "@{-N}" shortcut name for N-th last branch Thomas Rast
@ 2009-01-17 16:09 ` Thomas Rast
2009-01-17 16:09 ` [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1() Thomas Rast
2009-01-18 0:54 ` [PATCH/RFC v4 2/5] sha1_name: tweak @{-N} lookup Junio C Hamano
0 siblings, 2 replies; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 16:09 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland
Have the lookup only look at "interesting" checkouts, meaning those
that tell you "Already on ..." don't count even though they also cause
a reflog entry.
Let interpret_nth_last_branch() return the number of characters
parsed, so that git-checkout can verify that the branch spec was
@{-N}, not @{-1}^2 or something like that. (The latter will be added
later.)
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
builtin-checkout.c | 4 ++-
sha1_name.c | 53 ++++++++++++++++++++++++++++-----------------------
2 files changed, 32 insertions(+), 25 deletions(-)
diff --git a/builtin-checkout.c b/builtin-checkout.c
index a3b69d6..dc1de06 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -361,8 +361,10 @@ struct branch_info {
static void setup_branch_path(struct branch_info *branch)
{
struct strbuf buf = STRBUF_INIT;
+ int ret;
- if (!interpret_nth_last_branch(branch->name, &buf)) {
+ if ((ret = interpret_nth_last_branch(branch->name, &buf))
+ && ret == strlen(branch->name)) {
branch->name = xstrdup(buf.buf);
strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
} else {
diff --git a/sha1_name.c b/sha1_name.c
index 6377264..34e39db 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -685,29 +685,28 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
const char *message, void *cb_data)
{
struct grab_nth_branch_switch_cbdata *cb = cb_data;
- const char *match = NULL;
-
- if (!prefixcmp(message, "checkout: moving to "))
- match = message + strlen("checkout: moving to ");
- else if (!prefixcmp(message, "checkout: moving from ")) {
- const char *cp = message + strlen("checkout: moving from ");
- if ((cp = strstr(cp, " to ")) != NULL) {
- match = cp + 4;
- }
+ const char *match = NULL, *target = NULL;
+ size_t len;
+
+ if (!prefixcmp(message, "checkout: moving from ")) {
+ match = message + strlen("checkout: moving from ");
+ if ((target = strstr(match, " to ")) != NULL)
+ target += 4;
}
if (!match)
return 0;
+ len = target - match - 4;
+ if (target[len] == '\n' && !strncmp(match, target, len))
+ return 0;
+
if (cb->counting) {
cb->nth++;
return 0;
}
- if (--cb->nth <= 0) {
- size_t len = strlen(match);
- while (match[len-1] == '\n')
- len--;
+ if (cb->nth-- <= 0) {
strbuf_reset(cb->buf);
strbuf_add(cb->buf, match, len);
return 1;
@@ -718,26 +717,28 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
/*
* This reads "@{-N}" syntax, finds the name of the Nth previous
* branch we were on, and places the name of the branch in the given
- * buf and returns 0 if successful.
+ * buf and returns the number of characters parsed if successful.
*
* If the input is not of the accepted format, it returns a negative
* number to signal an error.
+ *
+ * If the input was ok but there are not N branch switches in the
+ * reflog, it returns 0.
*/
int interpret_nth_last_branch(const char *name, struct strbuf *buf)
{
- int nth, i;
+ int nth;
struct grab_nth_branch_switch_cbdata cb;
+ const char *brace;
+ char *num_end;
if (name[0] != '@' || name[1] != '{' || name[2] != '-')
return -1;
- for (i = 3, nth = 0; name[i] && name[i] != '}'; i++) {
- char ch = name[i];
- if ('0' <= ch && ch <= '9')
- nth = nth * 10 + ch - '0';
- else
- return -1;
- }
- if (nth < 0 || 10 <= nth)
+ brace = strchr(name, '}');
+ if (!brace)
+ return -1;
+ nth = strtol(name+3, &num_end, 10);
+ if (num_end != brace)
return -1;
cb.counting = 1;
@@ -745,11 +746,15 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
cb.buf = buf;
for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+ if (cb.nth < nth)
+ return 0;
+
cb.counting = 0;
cb.nth -= nth;
cb.buf = buf;
for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
- return 0;
+
+ return brace-name+1;
}
/*
--
1.6.1.315.g92577
^ permalink raw reply related [flat|nested] 102+ messages in thread
* [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1()
2009-01-17 16:09 ` [PATCH/RFC v4 2/5] sha1_name: tweak @{-N} lookup Thomas Rast
@ 2009-01-17 16:09 ` Thomas Rast
2009-01-17 16:09 ` [PATCH/RFC v4 4/5] checkout: implement "-" abbreviation, add docs and tests Thomas Rast
` (2 more replies)
2009-01-18 0:54 ` [PATCH/RFC v4 2/5] sha1_name: tweak @{-N} lookup Junio C Hamano
1 sibling, 3 replies; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 16:09 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland
Let get_sha1() parse the @{-N} syntax, with docs and tests.
Note that while @{-1}^2, @{-2}~5 and such are supported, @{-1}@{1} is
currently not allowed.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
Documentation/git-rev-parse.txt | 3 ++
sha1_name.c | 16 +++++++--
t/t1505-rev-parse-last.sh | 71 +++++++++++++++++++++++++++++++++++++++
3 files changed, 87 insertions(+), 3 deletions(-)
create mode 100755 t/t1505-rev-parse-last.sh
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 2921da3..3ccef2f 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -212,6 +212,9 @@ when you run 'git-merge'.
reflog of the current branch. For example, if you are on the
branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
+* The special construct '@\{-<n>\}' means the <n>th branch checked out
+ before the current one.
+
* A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e.
'rev{caret}'
diff --git a/sha1_name.c b/sha1_name.c
index 34e39db..9e1538e 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -297,6 +297,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
return logs_found;
}
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
{
static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
@@ -307,7 +309,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
if (len == 40 && !get_sha1_hex(str, sha1))
return 0;
- /* basic@{time or number} format to query ref-log */
+ /* basic@{time or number or -number} format to query ref-log */
reflog_len = at = 0;
if (str[len-1] == '}') {
for (at = 0; at < len - 1; at++) {
@@ -324,6 +326,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
return -1;
if (!len && reflog_len) {
+ struct strbuf buf = STRBUF_INIT;
+ int ret;
+ /* try the @{-N} syntax for n-th checkout */
+ ret = interpret_nth_last_branch(str+at, &buf);
+ if (ret > 0) {
+ /* substitute this branch name and restart */
+ return get_sha1_1(buf.buf, buf.len, sha1);
+ } else if (ret == 0) {
+ return -1;
+ }
/* allow "@{...}" to mean the current branch reflog */
refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
} else if (reflog_len)
@@ -379,8 +391,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
return 0;
}
-static int get_sha1_1(const char *name, int len, unsigned char *sha1);
-
static int get_parent(const char *name, int len,
unsigned char *result, int idx)
{
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
new file mode 100755
index 0000000..1e49dd2
--- /dev/null
+++ b/t/t1505-rev-parse-last.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+test_description='test @{-N} syntax'
+
+. ./test-lib.sh
+
+
+make_commit () {
+ echo "$1" > "$1" &&
+ git add "$1" &&
+ git commit -m "$1"
+}
+
+
+test_expect_success 'setup' '
+
+ make_commit 1 &&
+ git branch side &&
+ make_commit 2 &&
+ make_commit 3 &&
+ git checkout side &&
+ make_commit 4 &&
+ git merge master &&
+ git checkout master
+
+'
+
+# 1 -- 2 -- 3 master
+# \ \
+# \ \
+# --- 4 --- 5 side
+#
+# and 'side' should be the last branch
+
+git log --graph --all --pretty=oneline --decorate
+
+test_rev_equivalent () {
+
+ git rev-parse "$1" > expect &&
+ git rev-parse "$2" > output &&
+ test_cmp expect output
+
+}
+
+test_expect_success '@{-1} works' '
+ test_rev_equivalent side @{-1}
+'
+
+test_expect_success '@{-1}~2 works' '
+ test_rev_equivalent side~2 @{-1}~2
+'
+
+test_expect_success '@{-1}^2 works' '
+ test_rev_equivalent side^2 @{-1}^2
+'
+
+test_expect_failure '@{-1}@{1} works' '
+ test_rev_equivalent side@{1} @{-1}@{1}
+'
+
+test_expect_success '@{-2} works' '
+ test_rev_equivalent master @{-2}
+'
+
+test_expect_success '@{-3} fails' '
+ test_must_fail git rev-parse @{-3}
+'
+
+test_done
+
+
--
1.6.1.315.g92577
^ permalink raw reply related [flat|nested] 102+ messages in thread
* [PATCH/RFC v4 4/5] checkout: implement "-" abbreviation, add docs and tests
2009-01-17 16:09 ` [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1() Thomas Rast
@ 2009-01-17 16:09 ` Thomas Rast
2009-01-17 16:09 ` [PATCH/RFC v4 5/5] interpret_nth_last_branch(): avoid traversing the reflogs twice Thomas Rast
2009-01-17 19:57 ` [PATCH/RFC v4 4/5] checkout: implement "-" abbreviation, add docs and tests Junio C Hamano
2009-01-17 17:55 ` [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1() Johannes Schindelin
2009-01-17 19:37 ` Junio C Hamano
2 siblings, 2 replies; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 16:09 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland
Have '-' mean the same as '@{-1}', i.e., the last branch we were on.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
Documentation/git-checkout.txt | 4 +++
builtin-checkout.c | 3 ++
t/t2012-checkout-last.sh | 50 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 57 insertions(+), 0 deletions(-)
create mode 100755 t/t2012-checkout-last.sh
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 9cd5151..3bccffa 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -133,6 +133,10 @@ the conflicted merge in the specified paths.
+
When this parameter names a non-branch (but still a valid commit object),
your HEAD becomes 'detached'.
++
+As a special case, the "`@\{-N\}`" syntax for the N-th last branch
+checks out the branch (instead of detaching). You may also specify
+"`-`" which is synonymous with "`@\{-1\}`".
Detached HEAD
diff --git a/builtin-checkout.c b/builtin-checkout.c
index dc1de06..b0a101b 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -679,6 +679,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
arg = argv[0];
has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
+ if (!strcmp(arg, "-"))
+ arg = "@{-1}";
+
if (get_sha1(arg, rev)) {
if (has_dash_dash) /* case (1) */
die("invalid reference: %s", arg);
diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
new file mode 100755
index 0000000..320f6eb
--- /dev/null
+++ b/t/t2012-checkout-last.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='checkout can switch to last branch'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo hello >world &&
+ git add world &&
+ git commit -m initial &&
+ git branch other &&
+ echo "hello again" >>world &&
+ git add world &&
+ git commit -m second
+'
+
+test_expect_success '"checkout -" does not work initially' '
+ test_must_fail git checkout -
+'
+
+test_expect_success 'first branch switch' '
+ git checkout other
+'
+
+test_expect_success '"checkout -" switches back' '
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master"
+'
+
+test_expect_success '"checkout -" switches forth' '
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success 'detach HEAD' '
+ git checkout $(git rev-parse HEAD)
+'
+
+test_expect_success '"checkout -" attaches again' '
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success '"checkout -" detaches again' '
+ git checkout - &&
+ test "z$(git rev-parse HEAD)" = "z$(git rev-parse other)" &&
+ test_must_fail git symbolic-ref HEAD
+'
+
+test_done
--
1.6.1.315.g92577
^ permalink raw reply related [flat|nested] 102+ messages in thread
* [PATCH/RFC v4 5/5] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-17 16:09 ` [PATCH/RFC v4 4/5] checkout: implement "-" abbreviation, add docs and tests Thomas Rast
@ 2009-01-17 16:09 ` Thomas Rast
2009-01-17 18:08 ` [PATCH 6/5] Fix parsing of @{-1}@{1} Johannes Schindelin
2009-01-17 19:57 ` [PATCH/RFC v4 4/5] checkout: implement "-" abbreviation, add docs and tests Junio C Hamano
1 sibling, 1 reply; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 16:09 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland,
Johannes Schindelin
From: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Instead of traversing them twice, we just build a list of branch switches,
pick the one we're interested in, and free the list again.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
sha1_name.c | 61 ++++++++++++++++++++++++----------------------------------
1 files changed, 25 insertions(+), 36 deletions(-)
diff --git a/sha1_name.c b/sha1_name.c
index 9e1538e..b21a1f0 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -5,6 +5,8 @@
#include "blob.h"
#include "tree-walk.h"
#include "refs.h"
+#include "cache-tree.h"
+#include "string-list.h"
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
{
@@ -684,43 +686,31 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
return retval;
}
-struct grab_nth_branch_switch_cbdata {
- int counting;
- int nth;
- struct strbuf *buf;
-};
-
-static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
+static int add_one_branch_switch(unsigned char *osha1, unsigned char *nsha1,
const char *email, unsigned long timestamp, int tz,
const char *message, void *cb_data)
{
- struct grab_nth_branch_switch_cbdata *cb = cb_data;
+ struct string_list *list = cb_data;
const char *match = NULL, *target = NULL;
size_t len;
- if (!prefixcmp(message, "checkout: moving from ")) {
- match = message + strlen("checkout: moving from ");
- if ((target = strstr(match, " to ")) != NULL)
- target += 4;
- }
-
- if (!match)
+ if (prefixcmp(message, "checkout: moving from "))
return 0;
- len = target - match - 4;
- if (target[len] == '\n' && !strncmp(match, target, len))
- return 0;
+ match = message + strlen("checkout: moving from ");
- if (cb->counting) {
- cb->nth++;
- return 0;
+ /* Is it "moving" from a branch to itself? Then ignore it. */
+ if ((target = strstr(match, " to ")) != NULL) {
+ target += 4;
+ len = target - match - 4;
+ if (target[len] == '\n' && !strncmp(match, target, len))
+ return 0;
}
+ else
+ len = strchrnul(match, ' ') - match;
+
+ string_list_append(xstrndup(match, len), list);
- if (cb->nth-- <= 0) {
- strbuf_reset(cb->buf);
- strbuf_add(cb->buf, match, len);
- return 1;
- }
return 0;
}
@@ -738,7 +728,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
int interpret_nth_last_branch(const char *name, struct strbuf *buf)
{
int nth;
- struct grab_nth_branch_switch_cbdata cb;
+ struct string_list branch_list = { NULL, 0, 0, 0 };
const char *brace;
char *num_end;
@@ -751,18 +741,17 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
if (num_end != brace)
return -1;
- cb.counting = 1;
- cb.nth = 0;
- cb.buf = buf;
- for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+ for_each_reflog_ent("HEAD", add_one_branch_switch, &branch_list);
- if (cb.nth < nth)
+ if (branch_list.nr < nth)
return 0;
- cb.counting = 0;
- cb.nth -= nth;
- cb.buf = buf;
- for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+ strbuf_reset(buf);
+ strbuf_addstr(buf, branch_list.items[branch_list.nr - nth].string);
+
+ /* force free()ing the items */
+ branch_list.strdup_strings = 1;
+ string_list_clear(&branch_list, 0);
return brace-name+1;
}
--
1.6.1.315.g92577
^ permalink raw reply related [flat|nested] 102+ messages in thread
* [PATCH 6/5] Fix parsing of @{-1}@{1}
2009-01-17 16:09 ` [PATCH/RFC v4 5/5] interpret_nth_last_branch(): avoid traversing the reflogs twice Thomas Rast
@ 2009-01-17 18:08 ` Johannes Schindelin
2009-01-17 20:02 ` Junio C Hamano
0 siblings, 1 reply; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-17 18:08 UTC (permalink / raw)
To: Thomas Rast; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland
To do that, Git no longer looks forward for the '@{' corresponding to the
closing '}' but backward, and dwim_ref() as well as dwim_log() learnt
about the @{-<N>} notation.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
The modifications of dwim_ref() and dwim_log() are probably
more important than the issue I tried to fix...
sha1_name.c | 25 ++++++++++++++++++++++++-
t/t1505-rev-parse-last.sh | 2 +-
2 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/sha1_name.c b/sha1_name.c
index 306d04b..ee0c456 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -240,8 +240,28 @@ static int ambiguous_path(const char *path, int len)
return slash;
}
+/*
+ * *string and *len will only be substituted, and *string returned (for
+ * later free()ing) if the string passed in is of the form @{-<n>}.
+ */
+static char *substitute_nth_last_branch(const char **string, int *len)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int ret = interpret_nth_last_branch(*string, &buf);
+
+ if (ret == *len) {
+ size_t size;
+ *string = strbuf_detach(&buf, &size);
+ *len = size;
+ return (char *)*string;
+ }
+
+ return NULL;
+}
+
int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
{
+ char *last_branch = substitute_nth_last_branch(&str, &len);
const char **p, *r;
int refs_found = 0;
@@ -261,11 +281,13 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
break;
}
}
+ free(last_branch);
return refs_found;
}
int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
{
+ char *last_branch = substitute_nth_last_branch(&str, &len);
const char **p;
int logs_found = 0;
@@ -296,6 +318,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
if (!warn_ambiguous_refs)
break;
}
+ free(last_branch);
return logs_found;
}
@@ -314,7 +337,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
/* basic@{time or number or -number} format to query ref-log */
reflog_len = at = 0;
if (str[len-1] == '}') {
- for (at = 0; at < len - 1; at++) {
+ for (at = len-2; at >= 0; at--) {
if (str[at] == '@' && str[at+1] == '{') {
reflog_len = (len-1) - (at+2);
len = at;
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
index 72e8322..2d6b31e 100755
--- a/t/t1505-rev-parse-last.sh
+++ b/t/t1505-rev-parse-last.sh
@@ -58,7 +58,7 @@ test_expect_success '@{-1}^2 works' '
test_rev_equivalent side^2 @{-1}^2
'
-test_expect_failure '@{-1}@{1} works' '
+test_expect_success '@{-1}@{1} works' '
test_rev_equivalent side@{1} @{-1}@{1}
'
--
1.6.1.332.g9a59d
^ permalink raw reply related [flat|nested] 102+ messages in thread
* Re: [PATCH 6/5] Fix parsing of @{-1}@{1}
2009-01-17 18:08 ` [PATCH 6/5] Fix parsing of @{-1}@{1} Johannes Schindelin
@ 2009-01-17 20:02 ` Junio C Hamano
2009-01-17 21:22 ` Johannes Schindelin
0 siblings, 1 reply; 102+ messages in thread
From: Junio C Hamano @ 2009-01-17 20:02 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> To do that, Git no longer looks forward for the '@{' corresponding to the
> closing '}' but backward, and dwim_ref() as well as dwim_log() learnt
> about the @{-<N>} notation.
>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>
> The modifications of dwim_ref() and dwim_log() are probably
> more important than the issue I tried to fix...
Good, so we can say things like:
git log -g @{-1}
git show-branch -g @{-1}
with this?
By the way, I noticed that without these patch series we show something
when "git rev-parse --verify @{-1}" is asked for. What is it trying to
show?
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH 6/5] Fix parsing of @{-1}@{1}
2009-01-17 20:02 ` Junio C Hamano
@ 2009-01-17 21:22 ` Johannes Schindelin
0 siblings, 0 replies; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-17 21:22 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Hi,
On Sat, 17 Jan 2009, Junio C Hamano wrote:
> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> > To do that, Git no longer looks forward for the '@{' corresponding to the
> > closing '}' but backward, and dwim_ref() as well as dwim_log() learnt
> > about the @{-<N>} notation.
> >
> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> > ---
> >
> > The modifications of dwim_ref() and dwim_log() are probably
> > more important than the issue I tried to fix...
>
> Good, so we can say things like:
>
> git log -g @{-1}
> git show-branch -g @{-1}
>
> with this?
I _hope_ :-)
> By the way, I noticed that without these patch series we show something
> when "git rev-parse --verify @{-1}" is asked for. What is it trying to
> show?
Apparently the same as @{1.Jan}: in get_sha1_basic(), we have this code:
/* Is it asking for N-th entry, or approxidate? */
for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
char ch = str[at+2+i];
if ('0' <= ch && ch <= '9')
nth = nth * 10 + ch - '0';
else
nth = -1;
}
if (100000000 <= nth) {
at_time = nth;
nth = -1;
} else if (0 <= nth)
at_time = 0;
else {
char *tmp = xstrndup(str + at + 2, reflog_len);
at_time = approxidate(tmp);
free(tmp);
}
So in the loop, nth is set to -1 because of a non-digit, and later,
approxidate is called for nth == -1, which does this:
$ ./test-date now
now -> bad -> Thu Jan 1 01:00:00 1970
now -> Sat Jan 17 22:21:20 2009
Ciao,
Dscho
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH/RFC v4 4/5] checkout: implement "-" abbreviation, add docs and tests
2009-01-17 16:09 ` [PATCH/RFC v4 4/5] checkout: implement "-" abbreviation, add docs and tests Thomas Rast
2009-01-17 16:09 ` [PATCH/RFC v4 5/5] interpret_nth_last_branch(): avoid traversing the reflogs twice Thomas Rast
@ 2009-01-17 19:57 ` Junio C Hamano
1 sibling, 0 replies; 102+ messages in thread
From: Junio C Hamano @ 2009-01-17 19:57 UTC (permalink / raw)
To: Thomas Rast; +Cc: git, Johannes Schindelin, Johannes Sixt, Johan Herland
Thomas Rast <trast@student.ethz.ch> writes:
> @@ -133,6 +133,10 @@ the conflicted merge in the specified paths.
> +
> When this parameter names a non-branch (but still a valid commit object),
> your HEAD becomes 'detached'.
> ++
> +As a special case, the "`@\{-N\}`" syntax for the N-th last branch
> +checks out the branch (instead of detaching). You may also specify
> +"`-`" which is synonymous with "`@\{-1\}`".
I mildly disagree with this wording.
The new syntax is supposed to be a new way to name a branch, not a random
non-branch committish that is special cased by "git checkout". I would
further suggest that we should teach "git rev-parse --symbolic-full-name"
and "git rev-parse --symbolic" about the new syntax, so that scripts can
use the syntax to find out the same information.
The "-" thing deserves a mention here in the documentation. That _is_ a
special case that only applies to the "git checkout" command.
> diff --git a/builtin-checkout.c b/builtin-checkout.c
> index dc1de06..b0a101b 100644
> --- a/builtin-checkout.c
> +++ b/builtin-checkout.c
> @@ -679,6 +679,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
> arg = argv[0];
> has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
>
> + if (!strcmp(arg, "-"))
> + arg = "@{-1}";
> +
This is not quite nice as it could be, but it probably is Ok. If the
interpretation of @{-1} errors out, the user won't see an error message
that talks about "-" but instead the user will see "@{-1}".
Also it will look somewhat inconsistent to the end user who does not know
the internals for "-" claim to be a synonym for @{-1} but it really isn't.
For example, "git checkout -^0" does not work as "git checkout @{-1}^0".
To avoid such confusion, we could instead make "git checkout - <ENTER>" a
synonym for "git checkout @{-1} <ENTER>", without claiming to make "-" a
synonym for "@{-1}". In other words, "git checkout -" can become a very
narrow, focused special case that does not allow anything else, such as
pathspecs, "--" separator, nor --force and other options.
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1()
2009-01-17 16:09 ` [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1() Thomas Rast
2009-01-17 16:09 ` [PATCH/RFC v4 4/5] checkout: implement "-" abbreviation, add docs and tests Thomas Rast
@ 2009-01-17 17:55 ` Johannes Schindelin
2009-01-17 19:37 ` Junio C Hamano
2 siblings, 0 replies; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-17 17:55 UTC (permalink / raw)
To: Thomas Rast; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland
Hi,
On Sat, 17 Jan 2009, Thomas Rast wrote:
> diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
> new file mode 100755
> index 0000000..1e49dd2
> --- /dev/null
> +++ b/t/t1505-rev-parse-last.sh
> @@ -0,0 +1,71 @@
> +#!/bin/sh
> +
> +test_description='test @{-N} syntax'
> +
> +. ./test-lib.sh
> +
> +
> +make_commit () {
> + echo "$1" > "$1" &&
> + git add "$1" &&
> + git commit -m "$1"
> +}
> +
> +
> +test_expect_success 'setup' '
> +
> + make_commit 1 &&
> + git branch side &&
> + make_commit 2 &&
> + make_commit 3 &&
> + git checkout side &&
> + make_commit 4 &&
> + git merge master &&
> + git checkout master
> +
> +'
> +
> +# 1 -- 2 -- 3 master
> +# \ \
> +# \ \
> +# --- 4 --- 5 side
> +#
> +# and 'side' should be the last branch
> +
> +git log --graph --all --pretty=oneline --decorate
> +
Maybe you want to squash this in, so that the output of "make test" is
not cluttered by the graph?
-- snipsnap --
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
index 1e49dd2..72e8322 100755
--- a/t/t1505-rev-parse-last.sh
+++ b/t/t1505-rev-parse-last.sh
@@ -32,7 +32,11 @@ test_expect_success 'setup' '
#
# and 'side' should be the last branch
-git log --graph --all --pretty=oneline --decorate
+test_expect_success 'show a log (for debugging)' '
+
+ git log --graph --all --pretty=oneline --decorate
+
+'
test_rev_equivalent () {
--
1.6.1.332.g9a59d
^ permalink raw reply related [flat|nested] 102+ messages in thread
* Re: [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1()
2009-01-17 16:09 ` [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1() Thomas Rast
2009-01-17 16:09 ` [PATCH/RFC v4 4/5] checkout: implement "-" abbreviation, add docs and tests Thomas Rast
2009-01-17 17:55 ` [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1() Johannes Schindelin
@ 2009-01-17 19:37 ` Junio C Hamano
2 siblings, 0 replies; 102+ messages in thread
From: Junio C Hamano @ 2009-01-17 19:37 UTC (permalink / raw)
To: Thomas Rast; +Cc: git, Johannes Schindelin, Johannes Sixt, Johan Herland
Thomas Rast <trast@student.ethz.ch> writes:
> Let get_sha1() parse the @{-N} syntax, with docs and tests.
>
> Note that while @{-1}^2, @{-2}~5 and such are supported, @{-1}@{1} is
> currently not allowed.
>
> Signed-off-by: Thomas Rast <trast@student.ethz.ch>
> ...
> @@ -324,6 +326,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
> return -1;
>
> if (!len && reflog_len) {
> + struct strbuf buf = STRBUF_INIT;
> + int ret;
> + /* try the @{-N} syntax for n-th checkout */
> + ret = interpret_nth_last_branch(str+at, &buf);
> + if (ret > 0) {
> + /* substitute this branch name and restart */
> + return get_sha1_1(buf.buf, buf.len, sha1);
> + } else if (ret == 0) {
> + return -1;
> + }
What are the possible failure cases, and what do we want to tell the
end-user?
- You asked for 3rd but there weren't that many switches yet, and ask
"git rev-parse --verify @{-3}".
Are we Ok with "fatal: Needed a single revision" from rev-parse? Do we
want to show "fatal: @{-3}: not that many branch switches yet"?
What happens to "git checkout @{-3}" in this case? Having checkout say
"fatal: invalid reference: @{-3}" would be fine in this case, I think.
- You try "git checkout @{-3}", you were on "frotz" branch back then, but
the branch does not exist anymore.
I think you will get "fatal: invalid reference: frotz" from checkout,
which should be fine.
There also is a case where nth_last_branch() may find something that is
not a branch (e.g. "git checkout HEAD^"), but I am hoping we can label
that as a bug in nth_last_branch() and fix it later.
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH/RFC v4 2/5] sha1_name: tweak @{-N} lookup
2009-01-17 16:09 ` [PATCH/RFC v4 2/5] sha1_name: tweak @{-N} lookup Thomas Rast
2009-01-17 16:09 ` [PATCH/RFC v4 3/5] sha1_name: support @{-N} syntax in get_sha1() Thomas Rast
@ 2009-01-18 0:54 ` Junio C Hamano
1 sibling, 0 replies; 102+ messages in thread
From: Junio C Hamano @ 2009-01-18 0:54 UTC (permalink / raw)
To: Thomas Rast; +Cc: git, Johannes Schindelin, Johannes Sixt, Johan Herland
Thomas Rast <trast@student.ethz.ch> writes:
> Have the lookup only look at "interesting" checkouts, meaning those
> that tell you "Already on ..." don't count even though they also cause
> a reflog entry.
>
> Let interpret_nth_last_branch() return the number of characters
> parsed, so that git-checkout can verify that the branch spec was
> @{-N}, not @{-1}^2 or something like that. (The latter will be added
> later.)
Thanks; you seem to have handled the issues I pointed out in response to
my own weatherbaloon patch. I think it is probably better to squash the
first two (and you take the authorship).
> diff --git a/sha1_name.c b/sha1_name.c
> index 6377264..34e39db 100644
> --- a/sha1_name.c
> +++ b/sha1_name.c
> @@ -685,29 +685,28 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
> const char *message, void *cb_data)
> {
> struct grab_nth_branch_switch_cbdata *cb = cb_data;
> - const char *match = NULL;
> -
> - if (!prefixcmp(message, "checkout: moving to "))
> - match = message + strlen("checkout: moving to ");
> - else if (!prefixcmp(message, "checkout: moving from ")) {
> - const char *cp = message + strlen("checkout: moving from ");
> - if ((cp = strstr(cp, " to ")) != NULL) {
> - match = cp + 4;
> - }
> + const char *match = NULL, *target = NULL;
> + size_t len;
> +
> + if (!prefixcmp(message, "checkout: moving from ")) {
> + match = message + strlen("checkout: moving from ");
> + if ((target = strstr(match, " to ")) != NULL)
> + target += 4;
> }
This drops support for older reflog records, but I think it would be Ok.
This "N-th" support is really meant to be for small number of N anyway.
> - if (--cb->nth <= 0) {
> - size_t len = strlen(match);
> - while (match[len-1] == '\n')
> - len--;
> + if (cb->nth-- <= 0) {
> strbuf_reset(cb->buf);
> strbuf_add(cb->buf, match, len);
> return 1;
Hmm, did I have an off-by-one I did not notice? ;-)
> int interpret_nth_last_branch(const char *name, struct strbuf *buf)
> {
> - int nth, i;
> + int nth;
> struct grab_nth_branch_switch_cbdata cb;
> + const char *brace;
> + char *num_end;
>
> if (name[0] != '@' || name[1] != '{' || name[2] != '-')
> return -1;
> - for (i = 3, nth = 0; name[i] && name[i] != '}'; i++) {
> - char ch = name[i];
> - if ('0' <= ch && ch <= '9')
> - nth = nth * 10 + ch - '0';
> - else
> - return -1;
> - }
> - if (nth < 0 || 10 <= nth)
The removal of "limit to reasonably small recent N" I somewhat have
reservations on, but I think we can later re-add something based on
configuration variable if we need to.
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH/RFC v4 0/5] N-th last checked out branch
2009-01-17 16:09 ` [PATCH/RFC v4 0/5] N-th last checked out branch Thomas Rast
2009-01-17 16:09 ` [PATCH/RFC v4 1/5] checkout: implement "@{-N}" shortcut name for N-th last branch Thomas Rast
@ 2009-01-17 16:49 ` Johannes Schindelin
1 sibling, 0 replies; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-17 16:49 UTC (permalink / raw)
To: Thomas Rast; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland
Hi,
On Sat, 17 Jan 2009, Thomas Rast wrote:
> I [...] added a fixed up version of your patch on top, since it had some
> context that is not in any version I have.
Thanks.
Yeah, I know, I have too many patches in my fork, but I'm working on it...
:-)
Ciao,
Dscho
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-17 15:04 ` [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice Johannes Schindelin
2009-01-17 16:09 ` [PATCH/RFC v4 0/5] N-th last checked out branch Thomas Rast
@ 2009-01-17 19:13 ` Junio C Hamano
2009-01-17 19:29 ` Johannes Schindelin
1 sibling, 1 reply; 102+ messages in thread
From: Junio C Hamano @ 2009-01-17 19:13 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> Instead of traversing them twice, we just build a list of branch switches,
> pick the one we're interested in, and free the list again.
Isn't the code keeping them all in core, or am I reading the patch wrong?
If you know that you are interested in the nth-from-the-last switch, and
if you are reading from the beginning, you would need to keep at most n
last switches you have seen in core, wouldn't you?
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-17 19:13 ` [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice Junio C Hamano
@ 2009-01-17 19:29 ` Johannes Schindelin
2009-01-18 0:43 ` Junio C Hamano
0 siblings, 1 reply; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-17 19:29 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Hi,
On Sat, 17 Jan 2009, Junio C Hamano wrote:
> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> > Instead of traversing them twice, we just build a list of branch switches,
> > pick the one we're interested in, and free the list again.
>
> Isn't the code keeping them all in core, or am I reading the patch wrong?
>
> If you know that you are interested in the nth-from-the-last switch, and
> if you are reading from the beginning, you would need to keep at most n
> last switches you have seen in core, wouldn't you?
That is correct. But this is such a highly uncritical code path that I'd
like to keep this simple rather than fast.
Ciao,
Dscho
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-17 19:29 ` Johannes Schindelin
@ 2009-01-18 0:43 ` Junio C Hamano
2009-01-18 1:12 ` Johannes Schindelin
0 siblings, 1 reply; 102+ messages in thread
From: Junio C Hamano @ 2009-01-18 0:43 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> That is correct. But this is such a highly uncritical code path that I'd
> like to keep this simple rather than fast.
I actually not worried about "fast" at all; it was more about unbounded
memory consumption.
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-18 0:43 ` Junio C Hamano
@ 2009-01-18 1:12 ` Johannes Schindelin
2009-01-18 7:25 ` Junio C Hamano
0 siblings, 1 reply; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-18 1:12 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Hi,
On Sat, 17 Jan 2009, Junio C Hamano wrote:
> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> > That is correct. But this is such a highly uncritical code path that
> > I'd like to keep this simple rather than fast.
>
> I actually not worried about "fast" at all; it was more about unbounded
> memory consumption.
Let's just assume that I make a branch switch per minute, continuously,
for 90 days (until the reflogs are expired), and let's say that all my
branchnames have 20 characters. Conservatively, the memory requirement
would be 100 * 2000 * 30 = 3 megabyte.
Note: these are the memory requirements after some really unrealistically
high activity, and the memory is free()d during parameter parsing.
A much more realistical expectation would be to switch branches maybe 20
times a day, which would amount to something like 36 kilobyte. And again,
they are free()d before the action really starts.
Ciao,
Dscho
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-18 1:12 ` Johannes Schindelin
@ 2009-01-18 7:25 ` Junio C Hamano
2009-01-18 20:59 ` Johannes Schindelin
0 siblings, 1 reply; 102+ messages in thread
From: Junio C Hamano @ 2009-01-18 7:25 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> Note: these are the memory requirements after some really unrealistically
> high activity, and the memory is free()d during parameter parsing.
>
> A much more realistical expectation would be to switch branches maybe 20
> times a day, which would amount to something like 36 kilobyte. And again,
> they are free()d before the action really starts.
My HEAD reflog is 7MB long with 39000 entries, and among them, 13100
entries have "checkout: moving ".
I know I will never want to switch back to the 10000th from the last
branch. I am quite sure that I would forget which branch I was on after
switching branches three or four times (hence my original hardcoded
limitation of 10 which "should be plenty"). When I know I only have to
keep track of 10 entries, having to keep track of 13100 entries, even if
it is 36kB (it would actually be 260kB in my case) feels there is
something wrong in the design.
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-18 7:25 ` Junio C Hamano
@ 2009-01-18 20:59 ` Johannes Schindelin
2009-01-19 8:08 ` Junio C Hamano
0 siblings, 1 reply; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-18 20:59 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Hi,
On Sat, 17 Jan 2009, Junio C Hamano wrote:
> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> > Note: these are the memory requirements after some really unrealistically
> > high activity, and the memory is free()d during parameter parsing.
> >
> > A much more realistical expectation would be to switch branches maybe 20
> > times a day, which would amount to something like 36 kilobyte. And again,
> > they are free()d before the action really starts.
>
> My HEAD reflog is 7MB long with 39000 entries, and among them, 13100
> entries have "checkout: moving ".
>
> I know I will never want to switch back to the 10000th from the last
> branch. I am quite sure that I would forget which branch I was on after
> switching branches three or four times (hence my original hardcoded
> limitation of 10 which "should be plenty"). When I know I only have to
> keep track of 10 entries, having to keep track of 13100 entries, even if
> it is 36kB (it would actually be 260kB in my case) feels there is
> something wrong in the design.
Hrm. So let's leave it as a two-pass thing?
Ciao,
Dscho
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-18 20:59 ` Johannes Schindelin
@ 2009-01-19 8:08 ` Junio C Hamano
2009-01-19 8:19 ` Junio C Hamano
0 siblings, 1 reply; 102+ messages in thread
From: Junio C Hamano @ 2009-01-19 8:08 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> On Sat, 17 Jan 2009, Junio C Hamano wrote:
> ...
>> My HEAD reflog is 7MB long with 39000 entries, and among them, 13100
>> entries have "checkout: moving ".
>>
>> I know I will never want to switch back to the 10000th from the last
>> branch. I am quite sure that I would forget which branch I was on after
>> switching branches three or four times (hence my original hardcoded
>> limitation of 10 which "should be plenty"). When I know I only have to
>> keep track of 10 entries, having to keep track of 13100 entries, even if
>> it is 36kB (it would actually be 260kB in my case) feels there is
>> something wrong in the design.
>
> Hrm. So let's leave it as a two-pass thing?
Well, I would rather be in favor of something like this.
-- >8 --
Subject: interpret_nth_last_branch(): avoid traversing the reflog twice
You can have quite a many reflog entries, but you typically won't recall
which branch you were on after switching branches for more than several
times.
Instead of reading the reflog twice, this reads the branch switching event
and keeps the latest 16 (which is an arbitrary limitation that should be
plenty) such entry, to switch back to the branch we were recently on.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
sha1_name.c | 48 +++++++++++++++++++++------------------------
t/t2012-checkout-last.sh | 44 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 66 insertions(+), 26 deletions(-)
diff --git a/sha1_name.c b/sha1_name.c
index 9e1538e..d6622f2 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -684,10 +684,11 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
return retval;
}
+#define MAX_PREVIOUS_BRANCH 16
+
struct grab_nth_branch_switch_cbdata {
- int counting;
- int nth;
- struct strbuf *buf;
+ long cnt;
+ struct strbuf buf[MAX_PREVIOUS_BRANCH];
};
static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
@@ -697,6 +698,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
struct grab_nth_branch_switch_cbdata *cb = cb_data;
const char *match = NULL, *target = NULL;
size_t len;
+ int nth;
if (!prefixcmp(message, "checkout: moving from ")) {
match = message + strlen("checkout: moving from ");
@@ -711,16 +713,9 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
if (target[len] == '\n' && !strncmp(match, target, len))
return 0;
- if (cb->counting) {
- cb->nth++;
- return 0;
- }
-
- if (cb->nth-- <= 0) {
- strbuf_reset(cb->buf);
- strbuf_add(cb->buf, match, len);
- return 1;
- }
+ nth = cb->cnt++ % MAX_PREVIOUS_BRANCH;
+ strbuf_reset(&cb->buf[nth]);
+ strbuf_add(&cb->buf[nth], match, len);
return 0;
}
@@ -737,7 +732,8 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
*/
int interpret_nth_last_branch(const char *name, struct strbuf *buf)
{
- int nth;
+ long nth;
+ int i;
struct grab_nth_branch_switch_cbdata cb;
const char *brace;
char *num_end;
@@ -750,19 +746,19 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
nth = strtol(name+3, &num_end, 10);
if (num_end != brace)
return -1;
-
- cb.counting = 1;
- cb.nth = 0;
- cb.buf = buf;
- for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
-
- if (cb.nth < nth)
- return 0;
-
- cb.counting = 0;
- cb.nth -= nth;
- cb.buf = buf;
+ if (nth <= 0 || MAX_PREVIOUS_BRANCH < nth)
+ return -1;
+ for (i = 0; i < MAX_PREVIOUS_BRANCH; i++)
+ strbuf_init(&cb.buf[i], 20);
+ cb.cnt = 0;
for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+ if (cb.cnt < nth)
+ return -1;
+ i = (cb.cnt + MAX_PREVIOUS_BRANCH - nth) % MAX_PREVIOUS_BRANCH;
+ strbuf_reset(buf);
+ strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
+ for (i = 0; i < MAX_PREVIOUS_BRANCH; i++)
+ strbuf_release(&cb.buf[i]);
return brace-name+1;
}
diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
index 320f6eb..87b30a2 100755
--- a/t/t2012-checkout-last.sh
+++ b/t/t2012-checkout-last.sh
@@ -47,4 +47,48 @@ test_expect_success '"checkout -" detaches again' '
test_must_fail git symbolic-ref HEAD
'
+test_expect_success 'more switches' '
+ for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
+ do
+ git checkout -b branch$i
+ done
+'
+
+more_switches () {
+ for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
+ do
+ git checkout branch$i
+ done
+}
+
+test_expect_success 'switch to the last' '
+ more_switches &&
+ git checkout @{-1} &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch2"
+'
+
+test_expect_success 'switch to second from the last' '
+ more_switches &&
+ git checkout @{-2} &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch3"
+'
+
+test_expect_success 'switch to third from the last' '
+ more_switches &&
+ git checkout @{-3} &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch4"
+'
+
+test_expect_success 'switch to fourth from the last' '
+ more_switches &&
+ git checkout @{-4} &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch5"
+'
+
+test_expect_success 'switch to twelfth from the last' '
+ more_switches &&
+ git checkout @{-12} &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13"
+'
+
test_done
--
1.6.1.245.gdd9f9
^ permalink raw reply related [flat|nested] 102+ messages in thread
* Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-19 8:08 ` Junio C Hamano
@ 2009-01-19 8:19 ` Junio C Hamano
2009-01-19 12:33 ` Johannes Schindelin
2009-01-19 12:41 ` [PATCH] @{-<n>}: avoid crash with corrupt reflog Johannes Schindelin
0 siblings, 2 replies; 102+ messages in thread
From: Junio C Hamano @ 2009-01-19 8:19 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Junio C Hamano <gitster@pobox.com> writes:
> Well, I would rather be in favor of something like this.
>
> -- >8 --
> Subject: interpret_nth_last_branch(): avoid traversing the reflog twice
>
> You can have quite a many reflog entries, but you typically won't recall
> which branch you were on after switching branches for more than several
> times.
>
> Instead of reading the reflog twice, this reads the branch switching event
> and keeps the latest 16 (which is an arbitrary limitation that should be
> plenty) such entry, to switch back to the branch we were recently on.
>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
> sha1_name.c | 48 +++++++++++++++++++++------------------------
> t/t2012-checkout-last.sh | 44 ++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 66 insertions(+), 26 deletions(-)
>
> diff --git a/sha1_name.c b/sha1_name.c
> index 9e1538e..d6622f2 100644
> --- a/sha1_name.c
> +++ b/sha1_name.c
> @@ -750,19 +746,19 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
> nth = strtol(name+3, &num_end, 10);
> if (num_end != brace)
> return -1;
> ...
> - if (cb.nth < nth)
> - return 0;
> ...
> + if (cb.cnt < nth)
> + return -1;
This should (obviously) be "return 0".
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-19 8:19 ` Junio C Hamano
@ 2009-01-19 12:33 ` Johannes Schindelin
2009-01-20 0:11 ` Thomas Rast
` (4 more replies)
2009-01-19 12:41 ` [PATCH] @{-<n>}: avoid crash with corrupt reflog Johannes Schindelin
1 sibling, 5 replies; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-19 12:33 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Hi,
On Mon, 19 Jan 2009, Junio C Hamano wrote:
> Junio C Hamano <gitster@pobox.com> writes:
>
> > Well, I would rather be in favor of something like this.
> >
> > -- >8 --
> > Subject: interpret_nth_last_branch(): avoid traversing the reflog twice
> >
> > You can have quite a many reflog entries, but you typically won't recall
> > which branch you were on after switching branches for more than several
> > times.
> >
> > Instead of reading the reflog twice, this reads the branch switching event
> > and keeps the latest 16 (which is an arbitrary limitation that should be
> > plenty) such entry, to switch back to the branch we were recently on.
> >
> > Signed-off-by: Junio C Hamano <gitster@pobox.com>
> > ---
> > sha1_name.c | 48 +++++++++++++++++++++------------------------
> > t/t2012-checkout-last.sh | 44 ++++++++++++++++++++++++++++++++++++++++++
> > 2 files changed, 66 insertions(+), 26 deletions(-)
> >
> > diff --git a/sha1_name.c b/sha1_name.c
> > index 9e1538e..d6622f2 100644
> > --- a/sha1_name.c
> > +++ b/sha1_name.c
> > @@ -750,19 +746,19 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
> > nth = strtol(name+3, &num_end, 10);
> > if (num_end != brace)
> > return -1;
> > ...
> > - if (cb.nth < nth)
> > - return 0;
> > ...
> > + if (cb.cnt < nth)
> > + return -1;
>
> This should (obviously) be "return 0".
This, together with a removal of the hard-coded limit of 16 could be
squashed with this patch:
-- snipsnap --
diff --git a/sha1_name.c b/sha1_name.c
index 2c5461e..9e5f444 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -691,11 +691,9 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
return retval;
}
-#define MAX_PREVIOUS_BRANCH 16
-
struct grab_nth_branch_switch_cbdata {
- long cnt;
- struct strbuf buf[MAX_PREVIOUS_BRANCH];
+ long cnt, alloc;
+ struct strbuf *buf;
};
static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
@@ -720,7 +718,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
if (target[len] == '\n' && !strncmp(match, target, len))
return 0;
- nth = cb->cnt++ % MAX_PREVIOUS_BRANCH;
+ nth = cb->cnt++ % cb->alloc;
strbuf_reset(&cb->buf[nth]);
strbuf_add(&cb->buf[nth], match, len);
return 0;
@@ -753,19 +751,22 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
nth = strtol(name+3, &num_end, 10);
if (num_end != brace)
return -1;
- if (nth <= 0 || MAX_PREVIOUS_BRANCH < nth)
+ if (nth <= 0)
return -1;
- for (i = 0; i < MAX_PREVIOUS_BRANCH; i++)
+ cb.alloc = nth;
+ cb.buf = xmalloc(nth * sizeof(struct strbuf));
+ for (i = 0; i < nth; i++)
strbuf_init(&cb.buf[i], 20);
cb.cnt = 0;
for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
if (cb.cnt < nth)
- return -1;
- i = (cb.cnt + MAX_PREVIOUS_BRANCH - nth) % MAX_PREVIOUS_BRANCH;
+ return 0;
+ i = cb.cnt % nth;
strbuf_reset(buf);
strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
- for (i = 0; i < MAX_PREVIOUS_BRANCH; i++)
+ for (i = 0; i < nth; i++)
strbuf_release(&cb.buf[i]);
+ free(cb.buf);
return brace-name+1;
}
--
1.6.1.347.g7b62749
^ permalink raw reply related [flat|nested] 102+ messages in thread
* Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-19 12:33 ` Johannes Schindelin
@ 2009-01-20 0:11 ` Thomas Rast
2009-01-20 0:23 ` Johannes Schindelin
2009-01-20 6:21 ` [PATCH] interpret_nth_last_branch(): plug small memleak Junio C Hamano
` (3 subsequent siblings)
4 siblings, 1 reply; 102+ messages in thread
From: Thomas Rast @ 2009-01-20 0:11 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Junio C Hamano, git, Johannes Sixt, Johan Herland
[-- Attachment #1: Type: text/plain, Size: 1100 bytes --]
Johannes Schindelin wrote:
> > Junio C Hamano <gitster@pobox.com> writes:
> > > You can have quite a many reflog entries, but you typically won't recall
> > > which branch you were on after switching branches for more than several
> > > times.
>
> This, together with a removal of the hard-coded limit of 16 could be
> squashed with this patch:
You know, I'm quite puzzled as to why we had working code that could
read the reflog backwards earlier in this thread, but it got shot down
solely based on impact and line counts, and you now have to jump
through hoops to work around the lack of this exact functionality.
So how about I resurrect the part about for_each_reflog_ent() and
_backward(), without touching read_ref_at(). This would actually
avoid the worst (hard to check) part of the patch since the
refactoring of for_each_reflog_ent()'s error checking is quite trivial
and IMHO actually results in more readable code.
I'm just asking because I'm not particularly inclined to do it first
and get rejected _again_.
--
Thomas Rast
trast@{inf,student}.ethz.ch
[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 197 bytes --]
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-20 0:11 ` Thomas Rast
@ 2009-01-20 0:23 ` Johannes Schindelin
2009-01-20 0:41 ` Thomas Rast
0 siblings, 1 reply; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-20 0:23 UTC (permalink / raw)
To: Thomas Rast; +Cc: Junio C Hamano, git, Johannes Sixt, Johan Herland
Hi,
On Tue, 20 Jan 2009, Thomas Rast wrote:
> Johannes Schindelin wrote:
> > > Junio C Hamano <gitster@pobox.com> writes:
> > > > You can have quite a many reflog entries, but you typically won't recall
> > > > which branch you were on after switching branches for more than several
> > > > times.
> >
> > This, together with a removal of the hard-coded limit of 16 could be
> > squashed with this patch:
>
> You know, I'm quite puzzled as to why we had working code that could
> read the reflog backwards earlier in this thread, but it got shot down
> solely based on impact and line counts, and you now have to jump
> through hoops to work around the lack of this exact functionality.
Okay, I should have told you what my two main concerns with the patch
were.
1) it introduces a lot of code, with a lot of possibility for bugs to hide
(and I found it not simple enough to slap my head and say "of course,
this is obvious" as I did with Junio's code (except the modulo thing
which I had to thing about for half a minute)).
2) on Windows, mmap() is really implemented as xmalloc() && fread(). So
all the shortcomings of what Junio said about my array approach would
hold true for your approach, too.
Ciao,
Dscho
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-20 0:23 ` Johannes Schindelin
@ 2009-01-20 0:41 ` Thomas Rast
0 siblings, 0 replies; 102+ messages in thread
From: Thomas Rast @ 2009-01-20 0:41 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Junio C Hamano, git, Johannes Sixt, Johan Herland
[-- Attachment #1: Type: text/plain, Size: 1193 bytes --]
Johannes Schindelin wrote:
> 1) it introduces a lot of code, with a lot of possibility for bugs to hide
And I can understand in the case of read_ref_at() since there the
translation is really nontrivial (though I'd readily put the blame on
the optimized-to-death original code ;-). But in the case of
for_each_reflog_ent(), it should be really straightforward.
> 2) on Windows, mmap() is really implemented as xmalloc() && fread(). So
> all the shortcomings of what Junio said about my array approach would
> hold true for your approach, too.
But the @{} syntax _already_ uses mmap via read_ref_at(). And both
uses of for_each_reflog_ent() I'm aware of, the existing git log -g
and the present @{-N} syntax, have to read to the end anyway because
they're mostly interested in the newer stuff.
So while the mmap() might occasionally grab a few more MB of RAM than
would actually be required with simple line-based input, Windows
has to read the whole reflog no matter what.
(Well, unless we make a for_each_reflog_ent_backward() that can jump
in somewhere near the end and start parsing lines backwards.)
--
Thomas Rast
trast@{inf,student}.ethz.ch
[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 197 bytes --]
^ permalink raw reply [flat|nested] 102+ messages in thread
* [PATCH] interpret_nth_last_branch(): plug small memleak
2009-01-19 12:33 ` Johannes Schindelin
2009-01-20 0:11 ` Thomas Rast
@ 2009-01-20 6:21 ` Junio C Hamano
2009-01-20 10:15 ` Johannes Schindelin
2009-01-20 6:22 ` [PATCH] Introduce for_each_recent_reflog_ent() Junio C Hamano
` (2 subsequent siblings)
4 siblings, 1 reply; 102+ messages in thread
From: Junio C Hamano @ 2009-01-20 6:21 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
The error return path leaked both cb.buf[] strbuf array itself, and the
strings contained in its elements.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
sha1_name.c | 10 +++++++---
1 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/sha1_name.c b/sha1_name.c
index f54b6cb..4c0370b 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -754,7 +754,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
int interpret_nth_last_branch(const char *name, struct strbuf *buf)
{
long nth;
- int i;
+ int i, retval;
struct grab_nth_branch_switch_cbdata cb;
const char *brace;
char *num_end;
@@ -774,17 +774,21 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
for (i = 0; i < nth; i++)
strbuf_init(&cb.buf[i], 20);
cb.cnt = 0;
+ retval = 0;
for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
if (cb.cnt < nth)
- return 0;
+ goto release_return;
i = cb.cnt % nth;
strbuf_reset(buf);
strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
+ retval = brace-name+1;
+
+release_return:
for (i = 0; i < nth; i++)
strbuf_release(&cb.buf[i]);
free(cb.buf);
- return brace-name+1;
+ return retval;
}
/*
--
1.6.1.267.g11c6e
^ permalink raw reply related [flat|nested] 102+ messages in thread
* [PATCH] Introduce for_each_recent_reflog_ent().
2009-01-19 12:33 ` Johannes Schindelin
2009-01-20 0:11 ` Thomas Rast
2009-01-20 6:21 ` [PATCH] interpret_nth_last_branch(): plug small memleak Junio C Hamano
@ 2009-01-20 6:22 ` Junio C Hamano
2009-01-20 10:15 ` Johannes Schindelin
2009-01-20 8:35 ` [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice Junio C Hamano
2009-01-21 0:16 ` [VALGRIND PATCH for nth_last patch series] Fix invalid memory access Johannes Schindelin
4 siblings, 1 reply; 102+ messages in thread
From: Junio C Hamano @ 2009-01-20 6:22 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
This can be used to scan only the last few kilobytes of a reflog, as a
cheap optimization when the data you are looking for is likely to be
found near the end of it. The caller is expected to fall back to the
full scan if that is not the case.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
refs.c | 17 ++++++++++++++++-
refs.h | 1 +
sha1_name.c | 8 +++++++-
3 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/refs.c b/refs.c
index 33ced65..024211d 100644
--- a/refs.c
+++ b/refs.c
@@ -1453,7 +1453,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
return 1;
}
-int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data)
{
const char *logfile;
FILE *logfp;
@@ -1464,6 +1464,16 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
logfp = fopen(logfile, "r");
if (!logfp)
return -1;
+
+ if (ofs) {
+ struct stat statbuf;
+ if (fstat(fileno(logfp), &statbuf) ||
+ statbuf.st_size < ofs ||
+ fseek(logfp, -ofs, SEEK_END) ||
+ fgets(buf, sizeof(buf), logfp))
+ return -1;
+ }
+
while (fgets(buf, sizeof(buf), logfp)) {
unsigned char osha1[20], nsha1[20];
char *email_end, *message;
@@ -1497,6 +1507,11 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
return ret;
}
+int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+{
+ return for_each_recent_reflog_ent(ref, fn, 0, cb_data);
+}
+
static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
{
DIR *dir = opendir(git_path("logs/%s", base));
diff --git a/refs.h b/refs.h
index 06ad260..3bb529d 100644
--- a/refs.h
+++ b/refs.h
@@ -60,6 +60,7 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned
/* iterate over reflog entries */
typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
+int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, void *cb_data);
/*
* Calls the specified function for each reflog file until it returns nonzero,
diff --git a/sha1_name.c b/sha1_name.c
index 4c0370b..38c9f1b 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -775,7 +775,13 @@ int interpret_nth_last_branch(const char *name, struct strbuf *buf)
strbuf_init(&cb.buf[i], 20);
cb.cnt = 0;
retval = 0;
- for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+ for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
+ if (cb.cnt < nth) {
+ cb.cnt = 0;
+ for (i = 0; i < nth; i++)
+ strbuf_release(&cb.buf[i]);
+ for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+ }
if (cb.cnt < nth)
goto release_return;
i = cb.cnt % nth;
--
1.6.1.267.g11c6e
^ permalink raw reply related [flat|nested] 102+ messages in thread
* Re: [PATCH] Introduce for_each_recent_reflog_ent().
2009-01-20 6:22 ` [PATCH] Introduce for_each_recent_reflog_ent() Junio C Hamano
@ 2009-01-20 10:15 ` Johannes Schindelin
0 siblings, 0 replies; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-20 10:15 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Hi,
On Mon, 19 Jan 2009, Junio C Hamano wrote:
> This can be used to scan only the last few kilobytes of a reflog, as a
> cheap optimization when the data you are looking for is likely to be
> found near the end of it. The caller is expected to fall back to the
> full scan if that is not the case.
FWIW I really like it, as it works around mmap() nicely.
Ciao,
Dscho
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice
2009-01-19 12:33 ` Johannes Schindelin
` (2 preceding siblings ...)
2009-01-20 6:22 ` [PATCH] Introduce for_each_recent_reflog_ent() Junio C Hamano
@ 2009-01-20 8:35 ` Junio C Hamano
2009-01-21 0:16 ` [VALGRIND PATCH for nth_last patch series] Fix invalid memory access Johannes Schindelin
4 siblings, 0 replies; 102+ messages in thread
From: Junio C Hamano @ 2009-01-20 8:35 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> This, together with a removal of the hard-coded limit of 16 could be
> squashed with this patch:
Yeah, I think that makes very much sense, not because 16 is too small, but
because it does not make sense to keep track of all 16 when you asked for
the last (or the second from the last) event.
Thanks for a free sanity ;-)
^ permalink raw reply [flat|nested] 102+ messages in thread
* [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
2009-01-19 12:33 ` Johannes Schindelin
` (3 preceding siblings ...)
2009-01-20 8:35 ` [PATCH] interpret_nth_last_branch(): avoid traversing the reflogs twice Junio C Hamano
@ 2009-01-21 0:16 ` Johannes Schindelin
2009-01-21 8:45 ` Junio C Hamano
4 siblings, 1 reply; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-21 0:16 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Please squash in.
---
On Mon, 19 Jan 2009, Johannes Schindelin wrote:
> @@ -720,7 +718,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
> if (target[len] == '\n' && !strncmp(match, target, len))
> return 0;
This code is still not valid, as target[len] can be well after the
NUL marker.
Found by valgrind.
sha1_name.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/sha1_name.c b/sha1_name.c
index 4d10705..803f9d2 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -735,7 +735,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
if ((target = strstr(match, " to ")) != NULL) {
len = target - match;
target += 4;
- if (target[len] == '\n' && !strncmp(match, target, len))
+ if (len == strlen(target) && !strncmp(match, target, len))
return 0;
}
else
--
1.6.1.243.g6c8bb35
^ permalink raw reply related [flat|nested] 102+ messages in thread
* Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
2009-01-21 0:16 ` [VALGRIND PATCH for nth_last patch series] Fix invalid memory access Johannes Schindelin
@ 2009-01-21 8:45 ` Junio C Hamano
2009-01-21 9:18 ` Thomas Rast
2009-01-21 11:56 ` [VALGRIND PATCH for nth_last patch series] Fix invalid memory access Johannes Schindelin
0 siblings, 2 replies; 102+ messages in thread
From: Junio C Hamano @ 2009-01-21 8:45 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> sha1_name.c | 2 +-
> 1 files changed, 1 insertions(+), 1 deletions(-)
>
> diff --git a/sha1_name.c b/sha1_name.c
> index 4d10705..803f9d2 100644
> --- a/sha1_name.c
> +++ b/sha1_name.c
> @@ -735,7 +735,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
> if ((target = strstr(match, " to ")) != NULL) {
> len = target - match;
> target += 4;
> - if (target[len] == '\n' && !strncmp(match, target, len))
> + if (len == strlen(target) && !strncmp(match, target, len))
> return 0;
> }
> else
Actually, I think this patch to a884d0c (sha1_name: tweak @{-N} lookup,
2009-01-17) would make more sense.
-- >8 --
Subject: [PATCH] Simplify parsing branch switching events in reflog
We only accept "checkout: moving from A to B" newer style reflog entries,
in order to pick up A. There is no point computing where B begins at
after running strstr to locate " to ", nor adding 4 and then subtracting 4
from the same pointer.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
sha1_name.c | 8 ++------
1 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/sha1_name.c b/sha1_name.c
index 38c9f1b..7d95bbb 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -723,17 +723,13 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
if (!prefixcmp(message, "checkout: moving from ")) {
match = message + strlen("checkout: moving from ");
- if ((target = strstr(match, " to ")) != NULL)
- target += 4;
+ target = strstr(match, " to ");
}
if (!match || !target)
return 0;
- len = target - match - 4;
- if (target[len] == '\n' && !strncmp(match, target, len))
- return 0;
-
+ len = target - match;
nth = cb->cnt++ % cb->alloc;
strbuf_reset(&cb->buf[nth]);
strbuf_add(&cb->buf[nth], match, len);
--
1.6.1.281.g16db
^ permalink raw reply related [flat|nested] 102+ messages in thread
* Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
2009-01-21 8:45 ` Junio C Hamano
@ 2009-01-21 9:18 ` Thomas Rast
2009-01-21 10:13 ` Junio C Hamano
2009-01-24 22:21 ` Thomas Rast
2009-01-21 11:56 ` [VALGRIND PATCH for nth_last patch series] Fix invalid memory access Johannes Schindelin
1 sibling, 2 replies; 102+ messages in thread
From: Thomas Rast @ 2009-01-21 9:18 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Johannes Schindelin, git, Johannes Sixt, Johan Herland
[-- Attachment #1: Type: text/plain, Size: 1045 bytes --]
Junio C Hamano wrote:
> We only accept "checkout: moving from A to B" newer style reflog entries,
> in order to pick up A. There is no point computing where B begins at
> after running strstr to locate " to ", nor adding 4 and then subtracting 4
> from the same pointer.
[...]
> - len = target - match - 4;
> - if (target[len] == '\n' && !strncmp(match, target, len))
> - return 0;
> -
> + len = target - match;
Actually the point of that exercise was to ignore branch (non)switches
of the form
checkout: moving from A to A
I originally thought that this would be desirable behaviour, but now
that it causes so much trouble, I'm not that sure any more. I still
think it would be more intuitive to not count them as switches (after
all git-checkout says 'Already on "$branch"'), but OTOH 'cd .; cd -'
also stays in the same directory.
Thanks for all the work you (both) are doing on this. I hope to find
the time to read the current state of the series tonight.
--
Thomas Rast
trast@{inf,student}.ethz.ch
[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 197 bytes --]
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
2009-01-21 9:18 ` Thomas Rast
@ 2009-01-21 10:13 ` Junio C Hamano
2009-01-21 12:06 ` Johannes Schindelin
2009-01-24 22:21 ` Thomas Rast
1 sibling, 1 reply; 102+ messages in thread
From: Junio C Hamano @ 2009-01-21 10:13 UTC (permalink / raw)
To: Thomas Rast; +Cc: Johannes Schindelin, git, Johannes Sixt, Johan Herland
Thomas Rast <trast@student.ethz.ch> writes:
> Actually the point of that exercise was to ignore branch (non)switches
> of the form
>
> checkout: moving from A to A
Ahhh, Ok, that is what I missed.
> I originally thought that this would be desirable behaviour, but now
> that it causes so much trouble, I'm not that sure any more. I still
> think it would be more intuitive to not count them as switches (after
> all git-checkout says 'Already on "$branch"'), but OTOH 'cd .; cd -'
> also stays in the same directory.
An entry of the form "from A to A" is made only when you explicitly ask to
checkout the current branch by name (i.e. "git checkout" without any
parameter won't add such an entry to the reflog), so I tend to agree with
"cd" that the users may find it more natural if we counted them.
Having said all that, I think Dscho's one had an off-by-one (but it is
getting late and it may be I who has one).
When parsing "checkout: moving from master to side\n", match points at
"master to...", target points at "side\n", and len is 6 (length of
"master"). We want to see if target is "master\n" and ignore such an
entry, so we should be checking if target is one longer than len.
sha1_name.c | 5 ++++-
1 files changed, 4 insertions(+), 1 deletions(-)
diff --git c/sha1_name.c w/sha1_name.c
index 38c9f1b..9aed8ae 100644
--- c/sha1_name.c
+++ w/sha1_name.c
@@ -731,7 +731,10 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
return 0;
len = target - match - 4;
- if (target[len] == '\n' && !strncmp(match, target, len))
+ if (len + 1 == strlen(target) &&
+ target[len] == '\n' &&
+ !memcmp(target, match, len))
+ /* switching same branch "from A to A\n" */
return 0;
nth = cb->cnt++ % cb->alloc;
^ permalink raw reply related [flat|nested] 102+ messages in thread
* Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
2009-01-21 10:13 ` Junio C Hamano
@ 2009-01-21 12:06 ` Johannes Schindelin
0 siblings, 0 replies; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-21 12:06 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Hi,
On Wed, 21 Jan 2009, Junio C Hamano wrote:
> Having said all that, I think Dscho's one had an off-by-one (but it is
> getting late and it may be I who has one).
Yep, I missed the "\n" at the end.
Ciao,
Dscho
^ permalink raw reply [flat|nested] 102+ messages in thread
* Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
2009-01-21 9:18 ` Thomas Rast
2009-01-21 10:13 ` Junio C Hamano
@ 2009-01-24 22:21 ` Thomas Rast
2009-01-24 22:23 ` [PATCH next] t1505: remove debugging cruft Thomas Rast
1 sibling, 1 reply; 102+ messages in thread
From: Thomas Rast @ 2009-01-24 22:21 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Johannes Schindelin, git, Johannes Sixt, Johan Herland
[-- Attachment #1: Type: text/plain, Size: 856 bytes --]
Thomas Rast wrote:
> Thanks for all the work you (both) are doing on this. I hope to find
> the time to read the current state of the series tonight.
I was off by half a week :-(
The debugging 'git log --graph ...' that I forgot about is still in,
sorry. I'll follow up with a small patch. Other than that, I didn't
notice anything, though admittedly it wouldn't have been very hard to
sneak something past me.
By the way, you (Junio) remarked earlier in the thread that we could
forbid any use of 'git checkout -' except in literally that command
(i.e., no options or paths). I'm midly opposed to it because I can
see myself saying 'git checkout -b sidebranch -' (and @{} is somewhat
awkward to type on Swiss keyboards). Admittedly 'git checkout - --
file' looks rather confusing.
--
Thomas Rast
trast@{inf,student}.ethz.ch
[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 197 bytes --]
^ permalink raw reply [flat|nested] 102+ messages in thread
* [PATCH next] t1505: remove debugging cruft
2009-01-24 22:21 ` Thomas Rast
@ 2009-01-24 22:23 ` Thomas Rast
2009-01-25 20:35 ` Junio C Hamano
0 siblings, 1 reply; 102+ messages in thread
From: Thomas Rast @ 2009-01-24 22:23 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin
Remove a call to git-log that I introduced for debugging and that
accidentally made it into d18ba22 (sha1_name: support @{-N} syntax in
get_sha1(), 2009-01-17).
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
t/t1505-rev-parse-last.sh | 2 --
1 files changed, 0 insertions(+), 2 deletions(-)
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
index c745ec4..d709ecf 100755
--- a/t/t1505-rev-parse-last.sh
+++ b/t/t1505-rev-parse-last.sh
@@ -32,8 +32,6 @@ test_expect_success 'setup' '
#
# and 'side' should be the last branch
-git log --graph --all --pretty=oneline --decorate
-
test_rev_equivalent () {
git rev-parse "$1" > expect &&
--
1.6.1.468.g15c0
^ permalink raw reply related [flat|nested] 102+ messages in thread
* Re: [VALGRIND PATCH for nth_last patch series] Fix invalid memory access
2009-01-21 8:45 ` Junio C Hamano
2009-01-21 9:18 ` Thomas Rast
@ 2009-01-21 11:56 ` Johannes Schindelin
1 sibling, 0 replies; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-21 11:56 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Hi,
On Wed, 21 Jan 2009, Junio C Hamano wrote:
> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> > sha1_name.c | 2 +-
> > 1 files changed, 1 insertions(+), 1 deletions(-)
> >
> > diff --git a/sha1_name.c b/sha1_name.c
> > index 4d10705..803f9d2 100644
> > --- a/sha1_name.c
> > +++ b/sha1_name.c
> > @@ -735,7 +735,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
> > if ((target = strstr(match, " to ")) != NULL) {
> > len = target - match;
> > target += 4;
> > - if (target[len] == '\n' && !strncmp(match, target, len))
> > + if (len == strlen(target) && !strncmp(match, target, len))
> > return 0;
> > }
> > else
>
> Actually, I think this patch to a884d0c (sha1_name: tweak @{-N} lookup,
> 2009-01-17) would make more sense.
>
> -- >8 --
> Subject: [PATCH] Simplify parsing branch switching events in reflog
>
> We only accept "checkout: moving from A to B" newer style reflog entries,
> in order to pick up A. There is no point computing where B begins at
> after running strstr to locate " to ", nor adding 4 and then subtracting 4
> from the same pointer.
Yeah, you're right.
Ciao,
Dscho
^ permalink raw reply [flat|nested] 102+ messages in thread
* [PATCH] @{-<n>}: avoid crash with corrupt reflog
2009-01-19 8:19 ` Junio C Hamano
2009-01-19 12:33 ` Johannes Schindelin
@ 2009-01-19 12:41 ` Johannes Schindelin
2009-01-19 14:57 ` Johannes Schindelin
1 sibling, 1 reply; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-19 12:41 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
The earlier code checked if a " to " was found after "checkout: Moving
from ". However, it then went on to access the pointer to " to ",
regardless if it was still NULL (if no " to " was found) or not.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
You might want to squash this into "sha1_name: tweak @{-N}
lookup", just as a safety belt.
sha1_name.c | 20 ++++++++++----------
1 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/sha1_name.c b/sha1_name.c
index 9e5f444..853bac6 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -705,18 +705,18 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
size_t len;
int nth;
- if (!prefixcmp(message, "checkout: moving from ")) {
- match = message + strlen("checkout: moving from ");
- if ((target = strstr(match, " to ")) != NULL)
- target += 4;
- }
-
- if (!match)
+ if (prefixcmp(message, "checkout: moving from "))
return 0;
- len = target - match - 4;
- if (target[len] == '\n' && !strncmp(match, target, len))
- return 0;
+ match = message + strlen("checkout: moving from ");
+ if ((target = strstr(match, " to ")) != NULL) {
+ len = target - match - 4;
+ target += 4;
+ if (target[len] == '\n' && !strncmp(match, target, len))
+ return 0;
+ }
+ else
+ len = strchrnul(match, ' ') - match;
nth = cb->cnt++ % cb->alloc;
strbuf_reset(&cb->buf[nth]);
--
1.6.1.347.g7b62749
^ permalink raw reply related [flat|nested] 102+ messages in thread
* Re: [PATCH] @{-<n>}: avoid crash with corrupt reflog
2009-01-19 12:41 ` [PATCH] @{-<n>}: avoid crash with corrupt reflog Johannes Schindelin
@ 2009-01-19 14:57 ` Johannes Schindelin
0 siblings, 0 replies; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-19 14:57 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Thomas Rast, git, Johannes Sixt, Johan Herland
Hi,
On Mon, 19 Jan 2009, Johannes Schindelin wrote:
> diff --git a/sha1_name.c b/sha1_name.c
> index 9e5f444..853bac6 100644
> --- a/sha1_name.c
> +++ b/sha1_name.c
> @@ -705,18 +705,18 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
> size_t len;
> int nth;
>
> - if (!prefixcmp(message, "checkout: moving from ")) {
> - match = message + strlen("checkout: moving from ");
> - if ((target = strstr(match, " to ")) != NULL)
> - target += 4;
> - }
> -
> - if (!match)
> + if (prefixcmp(message, "checkout: moving from "))
> return 0;
>
> - len = target - match - 4;
> - if (target[len] == '\n' && !strncmp(match, target, len))
> - return 0;
> + match = message + strlen("checkout: moving from ");
> + if ((target = strstr(match, " to ")) != NULL) {
> + len = target - match - 4;
Aargh, the "- 4" is wrong, of course.
> + target += 4;
> + if (target[len] == '\n' && !strncmp(match, target, len))
> + return 0;
> + }
> + else
> + len = strchrnul(match, ' ') - match;
>
> nth = cb->cnt++ % cb->alloc;
> strbuf_reset(&cb->buf[nth]);
Sorry for the noise,
Dscho
^ permalink raw reply [flat|nested] 102+ messages in thread
* [PATCH/RFC v3 1/6] reflog: refactor parsing and checking
2009-01-15 20:50 ` Junio C Hamano
2009-01-17 3:30 ` [PATCH/RFC v3 0/6] N-th last checked out branch Thomas Rast
@ 2009-01-17 3:30 ` Thomas Rast
2009-01-17 5:35 ` Johannes Schindelin
2009-01-17 3:30 ` [PATCH/RFC v3 2/6] reflog: refactor log open+mmap Thomas Rast
` (4 subsequent siblings)
6 siblings, 1 reply; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 3:30 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland
read_ref_at() and for_each_reflog_ent() both had parsing and error
checking routines. Refactor into a separate function that fully
parses a single entry. Note that this switches for_each_reflog_ent()
from silently ignoring errors to die().
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
refs.c | 197 ++++++++++++++++++++++++++++++++--------------------------------
1 files changed, 98 insertions(+), 99 deletions(-)
diff --git a/refs.c b/refs.c
index 33ced65..4571fac 100644
--- a/refs.c
+++ b/refs.c
@@ -1337,24 +1337,68 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
return 0;
}
-static char *ref_msg(const char *line, const char *endp)
+/*
+ * Check and parse a single reflog line. Assumes that there is only
+ * one newline in the range buf[0]..buf[len-1] (but does check that it
+ * is at buf[len-1]).
+ */
+static void parse_reflog_line(const char *buf, int len,
+ unsigned char *osha1, unsigned char *nsha1,
+ char **email,
+ unsigned long *timestamp, int *tz,
+ char **message,
+ const char *logname)
{
- const char *ep;
- line += 82;
- ep = memchr(line, '\n', endp - line);
- if (!ep)
- ep = endp;
- return xmemdupz(line, ep - line);
+ static char *retbuf = NULL;
+ static int retbufsz = 0;
+ char *tzstr, *email_end;
+
+ if (len < 83 || buf[len-1] != '\n')
+ die("Log %s is corrupt (entry too short or unterminated).", logname);
+
+ if (get_sha1_hex(buf, osha1) || buf[40] != ' ')
+ die("Log %s is corrupt (malformed old sha1).", logname);
+
+ if (get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ')
+ die("Log %s is corrupt (malformed new sha1).", logname);
+
+ ALLOC_GROW(retbuf, len-82+1, retbufsz);
+ memcpy(retbuf, buf+82, len-82);
+ retbuf[len-82] = '\0';
+
+ email_end = strchr(retbuf, '>');
+ if (!email_end || email_end[1] != ' ')
+ die("Log %s is corrupt (malformed email field).", logname);
+
+ *email = retbuf;
+ email_end[1] = '\0';
+
+ *timestamp = strtoul(email_end + 2, &tzstr, 10);
+ if (!(*timestamp) || !tzstr || tzstr[0] != ' ' ||
+ (tzstr[1] != '+' && tzstr[1] != '-') ||
+ !isdigit(tzstr[2]) || !isdigit(tzstr[3]) ||
+ !isdigit(tzstr[4]) || !isdigit(tzstr[5]))
+ die("Log %s is corrupt (malformed timezone).", logname);
+ if (!(tzstr[6] == '\t' || tzstr[6] == '\n'))
+ die("Log %s is corrupt (bad message field separator).", logname);
+ *tz = strtoul(tzstr, NULL, 10);
+
+ if (tzstr[6] == '\t')
+ *message = tzstr+7;
+ else
+ *message = tzstr+6;
}
int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
{
- const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
- char *tz_c;
+ const char *logfile, *logdata, *logend, *rec, *start;
+ char *email, *message;
int logfd, tz, reccnt = 0;
struct stat st;
unsigned long date;
- unsigned char logged_sha1[20];
+ unsigned char new_sha1[20];
+ unsigned char old_sha1[20];
+ unsigned char next_sha1[20];
void *log_mapped;
size_t mapsz;
@@ -1370,86 +1414,55 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
logdata = log_mapped;
close(logfd);
- lastrec = NULL;
rec = logend = logdata + st.st_size;
+ if (logdata < rec && *(rec-1) == '\n')
+ rec--;
while (logdata < rec) {
- reccnt++;
- if (logdata < rec && *(rec-1) == '\n')
- rec--;
- lastgt = NULL;
- while (logdata < rec && *(rec-1) != '\n') {
- rec--;
- if (*rec == '>')
- lastgt = rec;
- }
- if (!lastgt)
- die("Log %s is corrupt.", logfile);
- date = strtoul(lastgt + 1, &tz_c, 10);
+ start = memrchr(logdata, '\n', rec-logdata);
+ if (start)
+ start++;
+ else
+ start = logdata;
+ parse_reflog_line(start, rec-start+1,
+ old_sha1, new_sha1,
+ &email, &date, &tz, &message,
+ logfile);
+
+ if (cutoff_time)
+ *cutoff_time = date;
+ if (cutoff_tz)
+ *cutoff_tz = tz;
+ if (cutoff_cnt)
+ *cutoff_cnt = reccnt;
+ if (msg)
+ *msg = message;
+
if (date <= at_time || cnt == 0) {
- tz = strtoul(tz_c, NULL, 10);
- if (msg)
- *msg = ref_msg(rec, logend);
- if (cutoff_time)
- *cutoff_time = date;
- if (cutoff_tz)
- *cutoff_tz = tz;
- if (cutoff_cnt)
- *cutoff_cnt = reccnt - 1;
- if (lastrec) {
- if (get_sha1_hex(lastrec, logged_sha1))
- die("Log %s is corrupt.", logfile);
- if (get_sha1_hex(rec + 41, sha1))
- die("Log %s is corrupt.", logfile);
- if (hashcmp(logged_sha1, sha1)) {
- fprintf(stderr,
- "warning: Log %s has gap after %s.\n",
- logfile, show_date(date, tz, DATE_RFC2822));
- }
- }
- else if (date == at_time) {
- if (get_sha1_hex(rec + 41, sha1))
- die("Log %s is corrupt.", logfile);
- }
- else {
- if (get_sha1_hex(rec + 41, logged_sha1))
- die("Log %s is corrupt.", logfile);
- if (hashcmp(logged_sha1, sha1)) {
- fprintf(stderr,
- "warning: Log %s unexpectedly ended on %s.\n",
- logfile, show_date(date, tz, DATE_RFC2822));
- }
- }
+ if (reccnt && hashcmp(new_sha1, next_sha1))
+ fprintf(stderr,
+ "warning: Log %s has gap after %s.\n",
+ logfile, show_date(date, tz, DATE_RFC2822));
+ if (!reccnt && date < at_time && hashcmp(new_sha1, next_sha1))
+ fprintf(stderr,
+ "warning: Log %s unexpectedly ended on %s.\n",
+ logfile, show_date(date, tz, DATE_RFC2822));
+ /* leave caller's sha1 untouched */
+ else
+ hashcpy(sha1, new_sha1);
munmap(log_mapped, mapsz);
return 0;
}
- lastrec = rec;
+
+ hashcpy(next_sha1, old_sha1);
+ rec = start-1;
if (cnt > 0)
cnt--;
+ reccnt++;
}
- rec = logdata;
- while (rec < logend && *rec != '>' && *rec != '\n')
- rec++;
- if (rec == logend || *rec == '\n')
- die("Log %s is corrupt.", logfile);
- date = strtoul(rec + 1, &tz_c, 10);
- tz = strtoul(tz_c, NULL, 10);
- if (get_sha1_hex(logdata, sha1))
- die("Log %s is corrupt.", logfile);
- if (is_null_sha1(sha1)) {
- if (get_sha1_hex(logdata + 41, sha1))
- die("Log %s is corrupt.", logfile);
- }
- if (msg)
- *msg = ref_msg(logdata, logend);
- munmap(log_mapped, mapsz);
+ hashcpy(sha1, new_sha1);
- if (cutoff_time)
- *cutoff_time = date;
- if (cutoff_tz)
- *cutoff_tz = tz;
- if (cutoff_cnt)
- *cutoff_cnt = reccnt;
+ munmap(log_mapped, mapsz);
return 1;
}
@@ -1466,30 +1479,16 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
return -1;
while (fgets(buf, sizeof(buf), logfp)) {
unsigned char osha1[20], nsha1[20];
- char *email_end, *message;
+ char *email, *message;
unsigned long timestamp;
int len, tz;
- /* old SP new SP name <email> SP time TAB msg LF */
len = strlen(buf);
- if (len < 83 || buf[len-1] != '\n' ||
- get_sha1_hex(buf, osha1) || buf[40] != ' ' ||
- get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ' ||
- !(email_end = strchr(buf + 82, '>')) ||
- email_end[1] != ' ' ||
- !(timestamp = strtoul(email_end + 2, &message, 10)) ||
- !message || message[0] != ' ' ||
- (message[1] != '+' && message[1] != '-') ||
- !isdigit(message[2]) || !isdigit(message[3]) ||
- !isdigit(message[4]) || !isdigit(message[5]))
- continue; /* corrupt? */
- email_end[1] = '\0';
- tz = strtol(message + 1, NULL, 10);
- if (message[6] != '\t')
- message += 6;
- else
- message += 7;
- ret = fn(osha1, nsha1, buf+82, timestamp, tz, message, cb_data);
+ parse_reflog_line(buf, len,
+ osha1, nsha1,
+ &email, ×tamp, &tz, &message,
+ logfile);
+ ret = fn(osha1, nsha1, email, timestamp, tz, message, cb_data);
if (ret)
break;
}
--
1.6.1.315.g92577
^ permalink raw reply related [flat|nested] 102+ messages in thread
* Re: [PATCH/RFC v3 1/6] reflog: refactor parsing and checking
2009-01-17 3:30 ` [PATCH/RFC v3 1/6] reflog: refactor parsing and checking Thomas Rast
@ 2009-01-17 5:35 ` Johannes Schindelin
0 siblings, 0 replies; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-17 5:35 UTC (permalink / raw)
To: Thomas Rast; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland
Hi,
On Sat, 17 Jan 2009, Thomas Rast wrote:
> Note that this switches for_each_reflog_ent() from silently ignoring
> errors to die().
I am not convinced this is necessary, and neither that it is good.
You could return int from parse_reflog_line() just like the other
non-dying functions.
And keep in mind that reflogs could be corrupted for all kinds of reasons;
with your patch, reflog handling would fail gracelessly, even if a
lot could still be salvaged (by ignoring the error with a warning).
Ciao,
Dscho
^ permalink raw reply [flat|nested] 102+ messages in thread
* [PATCH/RFC v3 2/6] reflog: refactor log open+mmap
2009-01-15 20:50 ` Junio C Hamano
2009-01-17 3:30 ` [PATCH/RFC v3 0/6] N-th last checked out branch Thomas Rast
2009-01-17 3:30 ` [PATCH/RFC v3 1/6] reflog: refactor parsing and checking Thomas Rast
@ 2009-01-17 3:30 ` Thomas Rast
2009-01-17 5:40 ` Johannes Schindelin
2009-01-17 3:30 ` [PATCH/RFC v3 3/6] reflog: make for_each_reflog_ent use mmap Thomas Rast
` (3 subsequent siblings)
6 siblings, 1 reply; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 3:30 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland
Move the open+mmap code from read_ref_at() to a separate function for
the next patch.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
refs.c | 54 ++++++++++++++++++++++++++++++++----------------------
1 files changed, 32 insertions(+), 22 deletions(-)
diff --git a/refs.c b/refs.c
index 4571fac..0a57896 100644
--- a/refs.c
+++ b/refs.c
@@ -1389,12 +1389,31 @@ static void parse_reflog_line(const char *buf, int len,
*message = tzstr+6;
}
+static char *open_reflog(const char *ref, size_t *mapsz, const char **logfile)
+{
+ struct stat st;
+ int logfd;
+ char *map;
+
+ *logfile = git_path("logs/%s", ref);
+ logfd = open(*logfile, O_RDONLY, 0);
+ if (logfd < 0)
+ return NULL;
+ if (fstat(logfd, &st))
+ return NULL;
+
+ *mapsz = xsize_t(st.st_size);
+ map = xmmap(NULL, *mapsz, PROT_READ, MAP_PRIVATE, logfd, 0);
+ close(logfd);
+ return map;
+}
+
+
int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
{
- const char *logfile, *logdata, *logend, *rec, *start;
+ const char *logfile, *logdata, *logend, *rec, *end;
char *email, *message;
- int logfd, tz, reccnt = 0;
- struct stat st;
+ int tz, reccnt = 0;
unsigned long date;
unsigned char new_sha1[20];
unsigned char old_sha1[20];
@@ -1402,28 +1421,20 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
void *log_mapped;
size_t mapsz;
- logfile = git_path("logs/%s", ref);
- logfd = open(logfile, O_RDONLY, 0);
- if (logfd < 0)
+ logdata = log_mapped = open_reflog(ref, &mapsz, &logfile);
+ if (!logdata)
die("Unable to read log %s: %s", logfile, strerror(errno));
- fstat(logfd, &st);
- if (!st.st_size)
+ if (!mapsz)
die("Log %s is empty.", logfile);
- mapsz = xsize_t(st.st_size);
- log_mapped = xmmap(NULL, mapsz, PROT_READ, MAP_PRIVATE, logfd, 0);
- logdata = log_mapped;
- close(logfd);
- rec = logend = logdata + st.st_size;
- if (logdata < rec && *(rec-1) == '\n')
- rec--;
+ rec = logend = logdata + mapsz;
while (logdata < rec) {
- start = memrchr(logdata, '\n', rec-logdata);
- if (start)
- start++;
- else
- start = logdata;
- parse_reflog_line(start, rec-start+1,
+ if (logdata < rec && rec[-1] == '\n')
+ rec--;
+ end = rec;
+ while (logdata < rec && rec[-1] != '\n')
+ rec--;
+ parse_reflog_line(rec, end-rec+1,
old_sha1, new_sha1,
&email, &date, &tz, &message,
logfile);
@@ -1454,7 +1465,6 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
}
hashcpy(next_sha1, old_sha1);
- rec = start-1;
if (cnt > 0)
cnt--;
reccnt++;
--
1.6.1.315.g92577
^ permalink raw reply related [flat|nested] 102+ messages in thread
* Re: [PATCH/RFC v3 2/6] reflog: refactor log open+mmap
2009-01-17 3:30 ` [PATCH/RFC v3 2/6] reflog: refactor log open+mmap Thomas Rast
@ 2009-01-17 5:40 ` Johannes Schindelin
0 siblings, 0 replies; 102+ messages in thread
From: Johannes Schindelin @ 2009-01-17 5:40 UTC (permalink / raw)
To: Thomas Rast; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland
Hi,
On Sat, 17 Jan 2009, Thomas Rast wrote:
> +static char *open_reflog(const char *ref, size_t *mapsz, const char **logfile)
> +{
> + struct stat st;
> + int logfd;
> + char *map;
> +
> + *logfile = git_path("logs/%s", ref);
That is dangerous. git_path() returns a pointer to a static buffer.
Before your patch, logfile was a local variable, and one could be
relatively sure that git_path() was not called during the lifetime. Now
the lifetime of logfile is no longer as clear-cut, and it is much easier
to overlook that git_path() must not be called while logfile holds a
reference to its static buffer.
Ciao,
Dscho
^ permalink raw reply [flat|nested] 102+ messages in thread
* [PATCH/RFC v3 3/6] reflog: make for_each_reflog_ent use mmap
2009-01-15 20:50 ` Junio C Hamano
` (2 preceding siblings ...)
2009-01-17 3:30 ` [PATCH/RFC v3 2/6] reflog: refactor log open+mmap Thomas Rast
@ 2009-01-17 3:30 ` Thomas Rast
2009-01-17 3:30 ` [PATCH/RFC v3 4/6] reflog: add backwards iterator Thomas Rast
` (2 subsequent siblings)
6 siblings, 0 replies; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 3:30 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland
Now that all building blocks have been refactored, we can implement
for_each_reflog_ent in terms of them, so that it uses mmap.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
refs.c | 25 ++++++++++++++++---------
1 files changed, 16 insertions(+), 9 deletions(-)
diff --git a/refs.c b/refs.c
index 0a57896..3848aa0 100644
--- a/refs.c
+++ b/refs.c
@@ -1478,22 +1478,28 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
{
- const char *logfile;
- FILE *logfp;
- char buf[1024];
+ const char *log_mapped, *buf, *log_end, *logfile;
+ size_t mapsz;
int ret = 0;
- logfile = git_path("logs/%s", ref);
- logfp = fopen(logfile, "r");
- if (!logfp)
+ buf = log_mapped = open_reflog(ref, &mapsz, &logfile);
+ if (!log_mapped)
return -1;
- while (fgets(buf, sizeof(buf), logfp)) {
+ log_end = buf + mapsz;
+
+ while (buf < log_end) {
unsigned char osha1[20], nsha1[20];
char *email, *message;
+ const char *next;
unsigned long timestamp;
int len, tz;
- len = strlen(buf);
+ next = memchr(buf, '\n', log_end - buf);
+ if (next)
+ next++;
+ else
+ next = log_end;
+ len = next - buf;
parse_reflog_line(buf, len,
osha1, nsha1,
&email, ×tamp, &tz, &message,
@@ -1501,8 +1507,9 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
ret = fn(osha1, nsha1, email, timestamp, tz, message, cb_data);
if (ret)
break;
+ buf = next;
}
- fclose(logfp);
+ munmap((void*) log_mapped, mapsz);
return ret;
}
--
1.6.1.315.g92577
^ permalink raw reply related [flat|nested] 102+ messages in thread
* [PATCH/RFC v3 4/6] reflog: add backwards iterator
2009-01-15 20:50 ` Junio C Hamano
` (3 preceding siblings ...)
2009-01-17 3:30 ` [PATCH/RFC v3 3/6] reflog: make for_each_reflog_ent use mmap Thomas Rast
@ 2009-01-17 3:30 ` Thomas Rast
2009-01-17 3:30 ` [PATCH/RFC v3 5/6] sha1_name: implement @{-N} syntax for N-th last checked out Thomas Rast
2009-01-17 3:30 ` [PATCH/RFC v3 6/6] checkout: implement '@{-N}' and '-' special abbreviations Thomas Rast
6 siblings, 0 replies; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 3:30 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland
Add for_each_reflog_ent_backward() which does the same as
for_each_reflog_ent(), except it traverses the reflog in backwards
(newest to oldest) order.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
refs.c | 35 +++++++++++++++++++++++++++++++++++
refs.h | 1 +
2 files changed, 36 insertions(+), 0 deletions(-)
diff --git a/refs.c b/refs.c
index 3848aa0..cc78f63 100644
--- a/refs.c
+++ b/refs.c
@@ -1476,6 +1476,41 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
return 1;
}
+int for_each_reflog_ent_backward(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+{
+ const char *log_mapped, *buf, *log_end, *logfile;
+ size_t mapsz;
+ int ret = 0;
+
+ log_mapped = open_reflog(ref, &mapsz, &logfile);
+ if (!log_mapped)
+ return -1;
+ buf = log_end = log_mapped + mapsz;
+
+ while (buf > log_mapped) {
+ unsigned char osha1[20], nsha1[20];
+ char *email, *message;
+ const char *end;
+ unsigned long timestamp;
+ int tz;
+
+ while (buf > log_mapped && buf[-1] == '\n')
+ buf--;
+ end = buf;
+ while (buf > log_mapped && buf[-1] != '\n')
+ buf--;
+ parse_reflog_line(buf, end-buf+1,
+ osha1, nsha1,
+ &email, ×tamp, &tz, &message,
+ logfile);
+ ret = fn(osha1, nsha1, email, timestamp, tz, message, cb_data);
+ if (ret)
+ break;
+ }
+ munmap((void*) log_mapped, mapsz);
+ return ret;
+}
+
int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
{
const char *log_mapped, *buf, *log_end, *logfile;
diff --git a/refs.h b/refs.h
index 06ad260..723bddc 100644
--- a/refs.h
+++ b/refs.h
@@ -60,6 +60,7 @@ struct ref_lock {
/* iterate over reflog entries */
typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
+int for_each_reflog_ent_backward(const char *ref, each_reflog_ent_fn fn, void *cb_data);
/*
* Calls the specified function for each reflog file until it returns nonzero,
--
1.6.1.315.g92577
^ permalink raw reply related [flat|nested] 102+ messages in thread
* [PATCH/RFC v3 5/6] sha1_name: implement @{-N} syntax for N-th last checked out
2009-01-15 20:50 ` Junio C Hamano
` (4 preceding siblings ...)
2009-01-17 3:30 ` [PATCH/RFC v3 4/6] reflog: add backwards iterator Thomas Rast
@ 2009-01-17 3:30 ` Thomas Rast
2009-01-17 3:30 ` [PATCH/RFC v3 6/6] checkout: implement '@{-N}' and '-' special abbreviations Thomas Rast
6 siblings, 0 replies; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 3:30 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland
Implements a new syntax @{-N} that parses the reflog for the N-th last
interesting 'checkout: moving from $branch to $new' entry and
substitutes $branch. Here, "interesting" is defined as $branch !=
$new. We then substitute the real branch name for the parse.
For example:
git checkout foo
git checkout bar
git checkout master
git checkout master # did not move, so doesn't count
git rev-parse @{-1} # same as bar
git rev-parse @{-2} # same as foo
git rev-parse @{-2}~3 # same as foo~3
Thanks to Junio for much of the code.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
Documentation/git-rev-parse.txt | 3 +
cache.h | 1 +
sha1_name.c | 79 +++++++++++++++++++++++++++++++++++++-
t/t1505-rev-parse-last.sh | 71 +++++++++++++++++++++++++++++++++++
4 files changed, 151 insertions(+), 3 deletions(-)
create mode 100755 t/t1505-rev-parse-last.sh
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 2921da3..3ccef2f 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -212,6 +212,9 @@ when you run 'git-merge'.
reflog of the current branch. For example, if you are on the
branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
+* The special construct '@\{-<n>\}' means the <n>th branch checked out
+ before the current one.
+
* A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e.
'rev{caret}'
diff --git a/cache.h b/cache.h
index 8e1af26..0dd9168 100644
--- a/cache.h
+++ b/cache.h
@@ -663,6 +663,7 @@ static inline unsigned int hexval(unsigned char c)
extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
+extern int interpret_nth_last_branch(const char *str, struct strbuf *);
extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
extern const char *ref_rev_parse_rules[];
diff --git a/sha1_name.c b/sha1_name.c
index 159c2ab..b2dd302 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -297,6 +297,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
return logs_found;
}
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
{
static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
@@ -307,7 +309,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
if (len == 40 && !get_sha1_hex(str, sha1))
return 0;
- /* basic@{time or number} format to query ref-log */
+ /* basic@{time or number or -number} format to query ref-log */
reflog_len = at = 0;
if (str[len-1] == '}') {
for (at = 0; at < len - 1; at++) {
@@ -324,6 +326,14 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
return -1;
if (!len && reflog_len) {
+ struct strbuf buf = STRBUF_INIT;
+ int ret;
+ /* try the @{-N} syntax for n-th checkout */
+ ret = interpret_nth_last_branch(str+at, &buf);
+ if (ret > 0) {
+ /* substitute this branch name and restart */
+ return get_sha1_1(buf.buf, buf.len, sha1);
+ }
/* allow "@{...}" to mean the current branch reflog */
refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
} else if (reflog_len)
@@ -379,8 +389,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
return 0;
}
-static int get_sha1_1(const char *name, int len, unsigned char *sha1);
-
static int get_parent(const char *name, int len,
unsigned char *result, int idx)
{
@@ -674,6 +682,71 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
return retval;
}
+struct grab_nth_branch_switch_cbdata {
+ int nth;
+ struct strbuf *buf;
+};
+
+static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
+{
+ struct grab_nth_branch_switch_cbdata *cb = cb_data;
+ const char *match = NULL, *target = NULL;
+ size_t len;
+
+ if (!prefixcmp(message, "checkout: moving from ")) {
+ match = message + strlen("checkout: moving from ");
+ if ((target = strstr(match, " to ")) != NULL)
+ target += 4;
+ }
+
+ if (!match)
+ return 0;
+
+ len = target - match - 4;
+ if (target[len] == '\n' && !strncmp(match, target, len))
+ return 0;
+
+ if (--cb->nth <= 0) {
+ strbuf_reset(cb->buf);
+ strbuf_add(cb->buf, match, len);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * This reads "@{-N}" syntax, finds the name of the Nth previous
+ * branch we were on, and places the name of the branch in the given
+ * buf and returns 0 if successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ */
+int interpret_nth_last_branch(const char *name, struct strbuf *buf)
+{
+ int nth;
+ struct grab_nth_branch_switch_cbdata cb;
+ const char *brace;
+ char *num_end;
+
+ if (name[0] != '@' || name[1] != '{' || name[2] != '-')
+ return -1;
+ brace = strchr(name, '}');
+ if (!brace)
+ return -1;
+ nth = strtol(name+3, &num_end, 10);
+ if (num_end != brace)
+ return -1;
+
+ cb.nth = nth;
+ cb.buf = buf;
+ for_each_reflog_ent_backward("HEAD", grab_nth_branch_switch, &cb);
+
+ return brace-name+1;
+}
+
/*
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
* notably "xyz^" for "parent of xyz"
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
new file mode 100755
index 0000000..1e49dd2
--- /dev/null
+++ b/t/t1505-rev-parse-last.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+test_description='test @{-N} syntax'
+
+. ./test-lib.sh
+
+
+make_commit () {
+ echo "$1" > "$1" &&
+ git add "$1" &&
+ git commit -m "$1"
+}
+
+
+test_expect_success 'setup' '
+
+ make_commit 1 &&
+ git branch side &&
+ make_commit 2 &&
+ make_commit 3 &&
+ git checkout side &&
+ make_commit 4 &&
+ git merge master &&
+ git checkout master
+
+'
+
+# 1 -- 2 -- 3 master
+# \ \
+# \ \
+# --- 4 --- 5 side
+#
+# and 'side' should be the last branch
+
+git log --graph --all --pretty=oneline --decorate
+
+test_rev_equivalent () {
+
+ git rev-parse "$1" > expect &&
+ git rev-parse "$2" > output &&
+ test_cmp expect output
+
+}
+
+test_expect_success '@{-1} works' '
+ test_rev_equivalent side @{-1}
+'
+
+test_expect_success '@{-1}~2 works' '
+ test_rev_equivalent side~2 @{-1}~2
+'
+
+test_expect_success '@{-1}^2 works' '
+ test_rev_equivalent side^2 @{-1}^2
+'
+
+test_expect_failure '@{-1}@{1} works' '
+ test_rev_equivalent side@{1} @{-1}@{1}
+'
+
+test_expect_success '@{-2} works' '
+ test_rev_equivalent master @{-2}
+'
+
+test_expect_success '@{-3} fails' '
+ test_must_fail git rev-parse @{-3}
+'
+
+test_done
+
+
--
1.6.1.315.g92577
^ permalink raw reply related [flat|nested] 102+ messages in thread
* [PATCH/RFC v3 6/6] checkout: implement '@{-N}' and '-' special abbreviations
2009-01-15 20:50 ` Junio C Hamano
` (5 preceding siblings ...)
2009-01-17 3:30 ` [PATCH/RFC v3 5/6] sha1_name: implement @{-N} syntax for N-th last checked out Thomas Rast
@ 2009-01-17 3:30 ` Thomas Rast
6 siblings, 0 replies; 102+ messages in thread
From: Thomas Rast @ 2009-01-17 3:30 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, Johan Herland
Checks if the branch to be checked out is either '@{-N}' or the
special shorthand '-' for '@{-1}' (i.e. the last checked out branch).
If so, we take it to mean the branch name, not the corresponding SHA,
so that we check out an attached HEAD on that branch.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
Documentation/git-checkout.txt | 4 +++
builtin-checkout.c | 15 ++++++++++-
t/t2012-checkout-last.sh | 50 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 67 insertions(+), 2 deletions(-)
create mode 100755 t/t2012-checkout-last.sh
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 9cd5151..3bccffa 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -133,6 +133,10 @@ the conflicted merge in the specified paths.
+
When this parameter names a non-branch (but still a valid commit object),
your HEAD becomes 'detached'.
++
+As a special case, the "`@\{-N\}`" syntax for the N-th last branch
+checks out the branch (instead of detaching). You may also specify
+"`-`" which is synonymous with "`@\{-1\}`".
Detached HEAD
diff --git a/builtin-checkout.c b/builtin-checkout.c
index b5dd9c0..b0a101b 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -361,8 +361,16 @@ struct branch_info {
static void setup_branch_path(struct branch_info *branch)
{
struct strbuf buf = STRBUF_INIT;
- strbuf_addstr(&buf, "refs/heads/");
- strbuf_addstr(&buf, branch->name);
+ int ret;
+
+ if ((ret = interpret_nth_last_branch(branch->name, &buf))
+ && ret == strlen(branch->name)) {
+ branch->name = xstrdup(buf.buf);
+ strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
+ } else {
+ strbuf_addstr(&buf, "refs/heads/");
+ strbuf_addstr(&buf, branch->name);
+ }
branch->path = strbuf_detach(&buf, NULL);
}
@@ -671,6 +679,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
arg = argv[0];
has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
+ if (!strcmp(arg, "-"))
+ arg = "@{-1}";
+
if (get_sha1(arg, rev)) {
if (has_dash_dash) /* case (1) */
die("invalid reference: %s", arg);
diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
new file mode 100755
index 0000000..320f6eb
--- /dev/null
+++ b/t/t2012-checkout-last.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='checkout can switch to last branch'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo hello >world &&
+ git add world &&
+ git commit -m initial &&
+ git branch other &&
+ echo "hello again" >>world &&
+ git add world &&
+ git commit -m second
+'
+
+test_expect_success '"checkout -" does not work initially' '
+ test_must_fail git checkout -
+'
+
+test_expect_success 'first branch switch' '
+ git checkout other
+'
+
+test_expect_success '"checkout -" switches back' '
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master"
+'
+
+test_expect_success '"checkout -" switches forth' '
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success 'detach HEAD' '
+ git checkout $(git rev-parse HEAD)
+'
+
+test_expect_success '"checkout -" attaches again' '
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success '"checkout -" detaches again' '
+ git checkout - &&
+ test "z$(git rev-parse HEAD)" = "z$(git rev-parse other)" &&
+ test_must_fail git symbolic-ref HEAD
+'
+
+test_done
--
1.6.1.315.g92577
^ permalink raw reply related [flat|nested] 102+ messages in thread