Skip to content

Commit 98f85ff

Browse files
committed
reflog: add for_each_reflog_ent_reverse() API
"git checkout -" is a short-hand for "git checkout @{-1}" and the "@{nth}" notation for a negative number is to find nth previous checkout in the reflog of the HEAD to determine the name of the branch the user was on. We would want to find the nth most recent reflog entry that matches "checkout: moving from X to Y" for this. Unfortunately, reflog is implemented as an append-only file, and the API to iterate over its entries, for_each_reflog_ent(), reads the file in order, giving the entries from the oldest to newer. For the purpose of finding nth most recent one, this API forces us to record the last n entries in a rotating buffer and give the result out only after we read everything. To optimize for a common case of finding the nth most recent one for a small value of n, we also have a side API for_each_recent_reflog_ent() that starts reading near the end of the file, but it still has to read the entries in the "wrong" order. The implementation of understanding @{-1} uses this interface. This all becomes unnecessary if we add an API to let us iterate over reflog entries in the reverse order, from the newest to older. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 7ae07c1 commit 98f85ff

File tree

3 files changed

+105
-47
lines changed

3 files changed

+105
-47
lines changed

refs.c

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2318,40 +2318,110 @@ static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *c
23182318
return fn(osha1, nsha1, sb->buf + 82, timestamp, tz, message, cb_data);
23192319
}
23202320

2321-
int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long ofs, void *cb_data)
2321+
static char *find_beginning_of_line(char *bob, char *scan)
2322+
{
2323+
while (bob < scan && *(--scan) != '\n')
2324+
; /* keep scanning backwards */
2325+
/*
2326+
* Return either beginning of the buffer, or LF at the end of
2327+
* the previous line.
2328+
*/
2329+
return scan;
2330+
}
2331+
2332+
int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data)
23222333
{
2323-
FILE *logfp;
23242334
struct strbuf sb = STRBUF_INIT;
2325-
int ret = 0;
2335+
FILE *logfp;
2336+
long pos;
2337+
int ret = 0, at_tail = 1;
23262338

23272339
logfp = fopen(git_path("logs/%s", refname), "r");
23282340
if (!logfp)
23292341
return -1;
23302342

2331-
if (ofs) {
2332-
struct stat statbuf;
2333-
if (fstat(fileno(logfp), &statbuf) ||
2334-
statbuf.st_size < ofs ||
2335-
fseek(logfp, -ofs, SEEK_END) ||
2336-
strbuf_getwholeline(&sb, logfp, '\n')) {
2337-
fclose(logfp);
2338-
strbuf_release(&sb);
2339-
return -1;
2343+
/* Jump to the end */
2344+
if (fseek(logfp, 0, SEEK_END) < 0)
2345+
return error("cannot seek back reflog for %s: %s",
2346+
refname, strerror(errno));
2347+
pos = ftell(logfp);
2348+
while (!ret && 0 < pos) {
2349+
int cnt;
2350+
size_t nread;
2351+
char buf[BUFSIZ];
2352+
char *endp, *scanp;
2353+
2354+
/* Fill next block from the end */
2355+
cnt = (sizeof(buf) < pos) ? sizeof(buf) : pos;
2356+
if (fseek(logfp, pos - cnt, SEEK_SET))
2357+
return error("cannot seek back reflog for %s: %s",
2358+
refname, strerror(errno));
2359+
nread = fread(buf, cnt, 1, logfp);
2360+
if (nread < 0)
2361+
return error("cannot read %d bytes from reflog for %s: %s",
2362+
cnt, refname, strerror(errno));
2363+
pos -= cnt;
2364+
2365+
scanp = endp = buf + cnt;
2366+
if (at_tail && scanp[-1] == '\n')
2367+
/* Looking at the final LF at the end of the file */
2368+
scanp--;
2369+
at_tail = 0;
2370+
2371+
while (buf < scanp) {
2372+
/*
2373+
* terminating LF of the previous line, or the beginning
2374+
* of the buffer.
2375+
*/
2376+
char *bp;
2377+
2378+
bp = find_beginning_of_line(buf, scanp);
2379+
2380+
if (*bp != '\n') {
2381+
strbuf_splice(&sb, 0, 0, buf, endp - buf);
2382+
if (pos)
2383+
break; /* need to fill another block */
2384+
scanp = buf - 1; /* leave loop */
2385+
} else {
2386+
/*
2387+
* (bp + 1) thru endp is the beginning of the
2388+
* current line we have in sb
2389+
*/
2390+
strbuf_splice(&sb, 0, 0, bp + 1, endp - (bp + 1));
2391+
scanp = bp;
2392+
endp = bp + 1;
2393+
}
2394+
ret = show_one_reflog_ent(&sb, fn, cb_data);
2395+
strbuf_reset(&sb);
2396+
if (ret)
2397+
break;
23402398
}
2341-
}
23422399

2343-
while (!ret && !strbuf_getwholeline(&sb, logfp, '\n'))
2400+
}
2401+
if (!ret && sb.len)
23442402
ret = show_one_reflog_ent(&sb, fn, cb_data);
2403+
23452404
fclose(logfp);
23462405
strbuf_release(&sb);
23472406
return ret;
23482407
}
23492408

23502409
int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data)
23512410
{
2352-
return for_each_recent_reflog_ent(refname, fn, 0, cb_data);
2353-
}
2411+
FILE *logfp;
2412+
struct strbuf sb = STRBUF_INIT;
2413+
int ret = 0;
2414+
2415+
logfp = fopen(git_path("logs/%s", refname), "r");
2416+
if (!logfp)
2417+
return -1;
23542418

2419+
while (!ret && !strbuf_getwholeline(&sb, logfp, '\n'))
2420+
ret = show_one_reflog_ent(&sb, fn, cb_data);
2421+
fclose(logfp);
2422+
strbuf_release(&sb);
2423+
return ret;
2424+
}
23552425
/*
23562426
* Call fn for each reflog in the namespace indicated by name. name
23572427
* must be empty or end with '/'. Name will be used as a scratch

refs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,
103103
/* iterate over reflog entries */
104104
typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
105105
int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
106-
int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long, void *cb_data);
106+
int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data);
107107

108108
/*
109109
* Calls the specified function for each reflog file until it returns nonzero,

sha1_name.c

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -856,8 +856,8 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
856856
}
857857

858858
struct grab_nth_branch_switch_cbdata {
859-
long cnt, alloc;
860-
struct strbuf *buf;
859+
int remaining;
860+
struct strbuf buf;
861861
};
862862

863863
static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
@@ -867,7 +867,6 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
867867
struct grab_nth_branch_switch_cbdata *cb = cb_data;
868868
const char *match = NULL, *target = NULL;
869869
size_t len;
870-
int nth;
871870

872871
if (!prefixcmp(message, "checkout: moving from ")) {
873872
match = message + strlen("checkout: moving from ");
@@ -876,11 +875,12 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
876875

877876
if (!match || !target)
878877
return 0;
879-
880-
len = target - match;
881-
nth = cb->cnt++ % cb->alloc;
882-
strbuf_reset(&cb->buf[nth]);
883-
strbuf_add(&cb->buf[nth], match, len);
878+
if (--(cb->remaining) == 0) {
879+
len = target - match;
880+
strbuf_reset(&cb->buf);
881+
strbuf_add(&cb->buf, match, len);
882+
return 1; /* we are done */
883+
}
884884
return 0;
885885
}
886886

@@ -891,7 +891,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
891891
static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
892892
{
893893
long nth;
894-
int i, retval;
894+
int retval;
895895
struct grab_nth_branch_switch_cbdata cb;
896896
const char *brace;
897897
char *num_end;
@@ -901,34 +901,22 @@ static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
901901
brace = strchr(name, '}');
902902
if (!brace)
903903
return -1;
904-
nth = strtol(name+3, &num_end, 10);
904+
nth = strtol(name + 3, &num_end, 10);
905905
if (num_end != brace)
906906
return -1;
907907
if (nth <= 0)
908908
return -1;
909-
cb.alloc = nth;
910-
cb.buf = xmalloc(nth * sizeof(struct strbuf));
911-
for (i = 0; i < nth; i++)
912-
strbuf_init(&cb.buf[i], 20);
913-
cb.cnt = 0;
909+
cb.remaining = nth;
910+
strbuf_init(&cb.buf, 20);
911+
914912
retval = 0;
915-
for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
916-
if (cb.cnt < nth) {
917-
cb.cnt = 0;
918-
for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
913+
if (0 < for_each_reflog_ent_reverse("HEAD", grab_nth_branch_switch, &cb)) {
914+
strbuf_reset(buf);
915+
strbuf_add(buf, cb.buf.buf, cb.buf.len);
916+
retval = brace - name + 1;
919917
}
920-
if (cb.cnt < nth)
921-
goto release_return;
922-
i = cb.cnt % nth;
923-
strbuf_reset(buf);
924-
strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
925-
retval = brace-name+1;
926-
927-
release_return:
928-
for (i = 0; i < nth; i++)
929-
strbuf_release(&cb.buf[i]);
930-
free(cb.buf);
931918

919+
strbuf_release(&cb.buf);
932920
return retval;
933921
}
934922

0 commit comments

Comments
 (0)