Skip to content

Commit

Permalink
Merge branch 'tr/previous-branch'
Browse files Browse the repository at this point in the history
* tr/previous-branch:
  t1505: remove debugging cruft
  Simplify parsing branch switching events in reflog
  Introduce for_each_recent_reflog_ent().
  interpret_nth_last_branch(): plug small memleak
  Fix reflog parsing for a malformed branch switching entry
  Fix parsing of @{-1}@{1}
  interpret_nth_last_branch(): avoid traversing the reflog twice
  checkout: implement "-" abbreviation, add docs and tests
  sha1_name: support @{-N} syntax in get_sha1()
  sha1_name: tweak @{-N} lookup
  checkout: implement "@{-N}" shortcut name for N-th last branch

Conflicts:
	sha1_name.c
  • Loading branch information
gitster committed Jan 28, 2009
2 parents cd956c7 + 73ff1a1 commit 8712b3c
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 7 deletions.
4 changes: 4 additions & 0 deletions Documentation/git-checkout.txt
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions Documentation/git-rev-parse.txt
Expand Up @@ -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}'
Expand Down
15 changes: 13 additions & 2 deletions builtin-checkout.c
Expand Up @@ -351,8 +351,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);
}

Expand Down Expand Up @@ -661,6 +669,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);
Expand Down
1 change: 1 addition & 0 deletions cache.h
Expand Up @@ -667,6 +667,7 @@ extern int read_ref(const char *filename, unsigned char *sha1);
extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
extern int interpret_nth_last_branch(const char *str, struct strbuf *);

extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
extern const char *ref_rev_parse_rules[];
Expand Down
17 changes: 16 additions & 1 deletion refs.c
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
1 change: 1 addition & 0 deletions refs.h
Expand Up @@ -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,
Expand Down
127 changes: 123 additions & 4 deletions sha1_name.c
Expand Up @@ -238,8 +238,28 @@ static int ambiguous_path(const char *path, int len)
return slash;
}

/*
* *string and *len will only be substituted, and *string returned (for
* later free()ing) if the string passed in is of the form @{-<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;

Expand All @@ -259,11 +279,13 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
break;
}
}
free(last_branch);
return refs_found;
}

int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
{
char *last_branch = substitute_nth_last_branch(&str, &len);
const char **p;
int logs_found = 0;

Expand Down Expand Up @@ -294,9 +316,12 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
if (!warn_ambiguous_refs)
break;
}
free(last_branch);
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";
Expand All @@ -307,10 +332,10 @@ 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 (len && 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;
Expand All @@ -324,6 +349,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)
Expand Down Expand Up @@ -379,8 +414,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)
{
Expand Down Expand Up @@ -674,6 +707,92 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
return retval;
}

struct grab_nth_branch_switch_cbdata {
long cnt, alloc;
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;
int nth;

if (!prefixcmp(message, "checkout: moving from ")) {
match = message + strlen("checkout: moving from ");
target = strstr(match, " to ");
}

if (!match || !target)
return 0;

len = target - match;
nth = cb->cnt++ % cb->alloc;
strbuf_reset(&cb->buf[nth]);
strbuf_add(&cb->buf[nth], match, len);
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)
{
long nth;
int i, retval;
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;
if (nth <= 0)
return -1;
cb.alloc = nth;
cb.buf = xmalloc(nth * sizeof(struct strbuf));
for (i = 0; i < nth; i++)
strbuf_init(&cb.buf[i], 20);
cb.cnt = 0;
retval = 0;
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;
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 retval;
}

/*
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
* notably "xyz^" for "parent of xyz"
Expand Down
69 changes: 69 additions & 0 deletions t/t1505-rev-parse-last.sh
@@ -0,0 +1,69 @@
#!/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

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_success '@{-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


0 comments on commit 8712b3c

Please sign in to comment.