Skip to content

Commit 8712b3c

Browse files
committed
Merge branch 'tr/previous-branch'
* 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
2 parents cd956c7 + 73ff1a1 commit 8712b3c

File tree

9 files changed

+324
-7
lines changed

9 files changed

+324
-7
lines changed

Documentation/git-checkout.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ the conflicted merge in the specified paths.
133133
+
134134
When this parameter names a non-branch (but still a valid commit object),
135135
your HEAD becomes 'detached'.
136+
+
137+
As a special case, the "`@\{-N\}`" syntax for the N-th last branch
138+
checks out the branch (instead of detaching). You may also specify
139+
"`-`" which is synonymous with "`@\{-1\}`".
136140

137141

138142
Detached HEAD

Documentation/git-rev-parse.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,9 @@ when you run 'git-merge'.
212212
reflog of the current branch. For example, if you are on the
213213
branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
214214

215+
* The special construct '@\{-<n>\}' means the <n>th branch checked out
216+
before the current one.
217+
215218
* A suffix '{caret}' to a revision parameter means the first parent of
216219
that commit object. '{caret}<n>' means the <n>th parent (i.e.
217220
'rev{caret}'

builtin-checkout.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,8 +351,16 @@ struct branch_info {
351351
static void setup_branch_path(struct branch_info *branch)
352352
{
353353
struct strbuf buf = STRBUF_INIT;
354-
strbuf_addstr(&buf, "refs/heads/");
355-
strbuf_addstr(&buf, branch->name);
354+
int ret;
355+
356+
if ((ret = interpret_nth_last_branch(branch->name, &buf))
357+
&& ret == strlen(branch->name)) {
358+
branch->name = xstrdup(buf.buf);
359+
strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
360+
} else {
361+
strbuf_addstr(&buf, "refs/heads/");
362+
strbuf_addstr(&buf, branch->name);
363+
}
356364
branch->path = strbuf_detach(&buf, NULL);
357365
}
358366

@@ -661,6 +669,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
661669
arg = argv[0];
662670
has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
663671

672+
if (!strcmp(arg, "-"))
673+
arg = "@{-1}";
674+
664675
if (get_sha1(arg, rev)) {
665676
if (has_dash_dash) /* case (1) */
666677
die("invalid reference: %s", arg);

cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,7 @@ extern int read_ref(const char *filename, unsigned char *sha1);
667667
extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
668668
extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
669669
extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
670+
extern int interpret_nth_last_branch(const char *str, struct strbuf *);
670671

671672
extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
672673
extern const char *ref_rev_parse_rules[];

refs.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1453,7 +1453,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
14531453
return 1;
14541454
}
14551455

1456-
int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
1456+
int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data)
14571457
{
14581458
const char *logfile;
14591459
FILE *logfp;
@@ -1464,6 +1464,16 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
14641464
logfp = fopen(logfile, "r");
14651465
if (!logfp)
14661466
return -1;
1467+
1468+
if (ofs) {
1469+
struct stat statbuf;
1470+
if (fstat(fileno(logfp), &statbuf) ||
1471+
statbuf.st_size < ofs ||
1472+
fseek(logfp, -ofs, SEEK_END) ||
1473+
fgets(buf, sizeof(buf), logfp))
1474+
return -1;
1475+
}
1476+
14671477
while (fgets(buf, sizeof(buf), logfp)) {
14681478
unsigned char osha1[20], nsha1[20];
14691479
char *email_end, *message;
@@ -1497,6 +1507,11 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
14971507
return ret;
14981508
}
14991509

1510+
int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
1511+
{
1512+
return for_each_recent_reflog_ent(ref, fn, 0, cb_data);
1513+
}
1514+
15001515
static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
15011516
{
15021517
DIR *dir = opendir(git_path("logs/%s", base));

refs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned
6060
/* iterate over reflog entries */
6161
typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
6262
int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
63+
int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, void *cb_data);
6364

6465
/*
6566
* Calls the specified function for each reflog file until it returns nonzero,

sha1_name.c

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,28 @@ static int ambiguous_path(const char *path, int len)
238238
return slash;
239239
}
240240

241+
/*
242+
* *string and *len will only be substituted, and *string returned (for
243+
* later free()ing) if the string passed in is of the form @{-<n>}.
244+
*/
245+
static char *substitute_nth_last_branch(const char **string, int *len)
246+
{
247+
struct strbuf buf = STRBUF_INIT;
248+
int ret = interpret_nth_last_branch(*string, &buf);
249+
250+
if (ret == *len) {
251+
size_t size;
252+
*string = strbuf_detach(&buf, &size);
253+
*len = size;
254+
return (char *)*string;
255+
}
256+
257+
return NULL;
258+
}
259+
241260
int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
242261
{
262+
char *last_branch = substitute_nth_last_branch(&str, &len);
243263
const char **p, *r;
244264
int refs_found = 0;
245265

@@ -259,11 +279,13 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
259279
break;
260280
}
261281
}
282+
free(last_branch);
262283
return refs_found;
263284
}
264285

265286
int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
266287
{
288+
char *last_branch = substitute_nth_last_branch(&str, &len);
267289
const char **p;
268290
int logs_found = 0;
269291

@@ -294,9 +316,12 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
294316
if (!warn_ambiguous_refs)
295317
break;
296318
}
319+
free(last_branch);
297320
return logs_found;
298321
}
299322

323+
static int get_sha1_1(const char *name, int len, unsigned char *sha1);
324+
300325
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
301326
{
302327
static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
@@ -307,10 +332,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
307332
if (len == 40 && !get_sha1_hex(str, sha1))
308333
return 0;
309334

310-
/* basic@{time or number} format to query ref-log */
335+
/* basic@{time or number or -number} format to query ref-log */
311336
reflog_len = at = 0;
312337
if (len && str[len-1] == '}') {
313-
for (at = 0; at < len - 1; at++) {
338+
for (at = len-2; at >= 0; at--) {
314339
if (str[at] == '@' && str[at+1] == '{') {
315340
reflog_len = (len-1) - (at+2);
316341
len = at;
@@ -324,6 +349,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
324349
return -1;
325350

326351
if (!len && reflog_len) {
352+
struct strbuf buf = STRBUF_INIT;
353+
int ret;
354+
/* try the @{-N} syntax for n-th checkout */
355+
ret = interpret_nth_last_branch(str+at, &buf);
356+
if (ret > 0) {
357+
/* substitute this branch name and restart */
358+
return get_sha1_1(buf.buf, buf.len, sha1);
359+
} else if (ret == 0) {
360+
return -1;
361+
}
327362
/* allow "@{...}" to mean the current branch reflog */
328363
refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
329364
} else if (reflog_len)
@@ -379,8 +414,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
379414
return 0;
380415
}
381416

382-
static int get_sha1_1(const char *name, int len, unsigned char *sha1);
383-
384417
static int get_parent(const char *name, int len,
385418
unsigned char *result, int idx)
386419
{
@@ -674,6 +707,92 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
674707
return retval;
675708
}
676709

710+
struct grab_nth_branch_switch_cbdata {
711+
long cnt, alloc;
712+
struct strbuf *buf;
713+
};
714+
715+
static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
716+
const char *email, unsigned long timestamp, int tz,
717+
const char *message, void *cb_data)
718+
{
719+
struct grab_nth_branch_switch_cbdata *cb = cb_data;
720+
const char *match = NULL, *target = NULL;
721+
size_t len;
722+
int nth;
723+
724+
if (!prefixcmp(message, "checkout: moving from ")) {
725+
match = message + strlen("checkout: moving from ");
726+
target = strstr(match, " to ");
727+
}
728+
729+
if (!match || !target)
730+
return 0;
731+
732+
len = target - match;
733+
nth = cb->cnt++ % cb->alloc;
734+
strbuf_reset(&cb->buf[nth]);
735+
strbuf_add(&cb->buf[nth], match, len);
736+
return 0;
737+
}
738+
739+
/*
740+
* This reads "@{-N}" syntax, finds the name of the Nth previous
741+
* branch we were on, and places the name of the branch in the given
742+
* buf and returns the number of characters parsed if successful.
743+
*
744+
* If the input is not of the accepted format, it returns a negative
745+
* number to signal an error.
746+
*
747+
* If the input was ok but there are not N branch switches in the
748+
* reflog, it returns 0.
749+
*/
750+
int interpret_nth_last_branch(const char *name, struct strbuf *buf)
751+
{
752+
long nth;
753+
int i, retval;
754+
struct grab_nth_branch_switch_cbdata cb;
755+
const char *brace;
756+
char *num_end;
757+
758+
if (name[0] != '@' || name[1] != '{' || name[2] != '-')
759+
return -1;
760+
brace = strchr(name, '}');
761+
if (!brace)
762+
return -1;
763+
nth = strtol(name+3, &num_end, 10);
764+
if (num_end != brace)
765+
return -1;
766+
if (nth <= 0)
767+
return -1;
768+
cb.alloc = nth;
769+
cb.buf = xmalloc(nth * sizeof(struct strbuf));
770+
for (i = 0; i < nth; i++)
771+
strbuf_init(&cb.buf[i], 20);
772+
cb.cnt = 0;
773+
retval = 0;
774+
for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
775+
if (cb.cnt < nth) {
776+
cb.cnt = 0;
777+
for (i = 0; i < nth; i++)
778+
strbuf_release(&cb.buf[i]);
779+
for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
780+
}
781+
if (cb.cnt < nth)
782+
goto release_return;
783+
i = cb.cnt % nth;
784+
strbuf_reset(buf);
785+
strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
786+
retval = brace-name+1;
787+
788+
release_return:
789+
for (i = 0; i < nth; i++)
790+
strbuf_release(&cb.buf[i]);
791+
free(cb.buf);
792+
793+
return retval;
794+
}
795+
677796
/*
678797
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
679798
* notably "xyz^" for "parent of xyz"

t/t1505-rev-parse-last.sh

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/bin/sh
2+
3+
test_description='test @{-N} syntax'
4+
5+
. ./test-lib.sh
6+
7+
8+
make_commit () {
9+
echo "$1" > "$1" &&
10+
git add "$1" &&
11+
git commit -m "$1"
12+
}
13+
14+
15+
test_expect_success 'setup' '
16+
17+
make_commit 1 &&
18+
git branch side &&
19+
make_commit 2 &&
20+
make_commit 3 &&
21+
git checkout side &&
22+
make_commit 4 &&
23+
git merge master &&
24+
git checkout master
25+
26+
'
27+
28+
# 1 -- 2 -- 3 master
29+
# \ \
30+
# \ \
31+
# --- 4 --- 5 side
32+
#
33+
# and 'side' should be the last branch
34+
35+
test_rev_equivalent () {
36+
37+
git rev-parse "$1" > expect &&
38+
git rev-parse "$2" > output &&
39+
test_cmp expect output
40+
41+
}
42+
43+
test_expect_success '@{-1} works' '
44+
test_rev_equivalent side @{-1}
45+
'
46+
47+
test_expect_success '@{-1}~2 works' '
48+
test_rev_equivalent side~2 @{-1}~2
49+
'
50+
51+
test_expect_success '@{-1}^2 works' '
52+
test_rev_equivalent side^2 @{-1}^2
53+
'
54+
55+
test_expect_success '@{-1}@{1} works' '
56+
test_rev_equivalent side@{1} @{-1}@{1}
57+
'
58+
59+
test_expect_success '@{-2} works' '
60+
test_rev_equivalent master @{-2}
61+
'
62+
63+
test_expect_success '@{-3} fails' '
64+
test_must_fail git rev-parse @{-3}
65+
'
66+
67+
test_done
68+
69+

0 commit comments

Comments
 (0)