diff --git a/object-name.c b/object-name.c index 3a2ef5d680..511f09bc0f 100644 --- a/object-name.c +++ b/object-name.c @@ -1034,6 +1034,15 @@ static int get_oid_basic(struct repository *r, const char *str, int len, len, str, show_date(co_time, co_tz, DATE_MODE(RFC2822))); } + } else if (nth == co_cnt && !is_null_oid(oid)) { + /* + * We were asked for the Nth reflog (counting + * from 0), but there were only N entries. + * read_ref_at() will have returned "1" to tell + * us it did not find an entry, but it did + * still fill in the oid with the "old" value, + * which we can use. + */ } else { if (flags & GET_OID_QUIETLY) { exit(128); diff --git a/refs.c b/refs.c index f9261267f0..c6d412877d 100644 --- a/refs.c +++ b/refs.c @@ -1039,55 +1039,40 @@ static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid, const char *message, void *cb_data) { struct read_ref_at_cb *cb = cb_data; - int reached_count; cb->tz = tz; cb->date = timestamp; - /* - * It is not possible for cb->cnt == 0 on the first iteration because - * that special case is handled in read_ref_at(). - */ - if (cb->cnt > 0) - cb->cnt--; - reached_count = cb->cnt == 0 && !is_null_oid(ooid); - if (timestamp <= cb->at_time || reached_count) { + if (timestamp <= cb->at_time || cb->cnt == 0) { set_read_ref_cutoffs(cb, timestamp, tz, message); /* * we have not yet updated cb->[n|o]oid so they still * hold the values for the previous record. */ - if (!is_null_oid(&cb->ooid) && !oideq(&cb->ooid, noid)) - warning(_("log for ref %s has gap after %s"), + if (!is_null_oid(&cb->ooid)) { + oidcpy(cb->oid, noid); + if (!oideq(&cb->ooid, noid)) + warning(_("log for ref %s has gap after %s"), cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); - if (reached_count) - oidcpy(cb->oid, ooid); - else if (!is_null_oid(&cb->ooid) || cb->date == cb->at_time) + } + else if (cb->date == cb->at_time) oidcpy(cb->oid, noid); else if (!oideq(noid, cb->oid)) warning(_("log for ref %s unexpectedly ended on %s"), cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); + cb->reccnt++; + oidcpy(&cb->ooid, ooid); + oidcpy(&cb->noid, noid); cb->found_it = 1; + return 1; } cb->reccnt++; oidcpy(&cb->ooid, ooid); oidcpy(&cb->noid, noid); - return cb->found_it; -} - -static int read_ref_at_ent_newest(struct object_id *ooid UNUSED, - struct object_id *noid, - const char *email UNUSED, - timestamp_t timestamp, int tz, - const char *message, void *cb_data) -{ - struct read_ref_at_cb *cb = cb_data; - - set_read_ref_cutoffs(cb, timestamp, tz, message); - oidcpy(cb->oid, noid); - /* We just want the first entry */ - return 1; + if (cb->cnt > 0) + cb->cnt--; + return 0; } static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid, @@ -1099,7 +1084,7 @@ static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid set_read_ref_cutoffs(cb, timestamp, tz, message); oidcpy(cb->oid, ooid); - if (is_null_oid(cb->oid)) + if (cb->at_time && is_null_oid(cb->oid)) oidcpy(cb->oid, noid); /* We just want the first entry */ return 1; @@ -1122,14 +1107,24 @@ int read_ref_at(struct ref_store *refs, const char *refname, cb.cutoff_cnt = cutoff_cnt; cb.oid = oid; - if (cb.cnt == 0) { - refs_for_each_reflog_ent_reverse(refs, refname, read_ref_at_ent_newest, &cb); - return 0; - } - refs_for_each_reflog_ent_reverse(refs, refname, read_ref_at_ent, &cb); if (!cb.reccnt) { + if (cnt == 0) { + /* + * The caller asked for ref@{0}, and we had no entries. + * It's a bit subtle, but in practice all callers have + * prepped the "oid" field with the current value of + * the ref, which is the most reasonable fallback. + * + * We'll put dummy values into the out-parameters (so + * they're not just uninitialized garbage), and the + * caller can take our return value as a hint that + * we did not find any such reflog. + */ + set_read_ref_cutoffs(&cb, 0, 0, "empty reflog"); + return 1; + } if (flags & GET_OID_QUIETLY) exit(128); else diff --git a/refs.h b/refs.h index 895579aeb7..0bd3fab468 100644 --- a/refs.h +++ b/refs.h @@ -440,7 +440,20 @@ int refs_create_reflog(struct ref_store *refs, const char *refname, struct strbuf *err); int safe_create_reflog(const char *refname, struct strbuf *err); -/** Reads log for the value of ref during at_time. **/ +/** + * Reads log for the value of ref during at_time (in which case "cnt" should be + * negative) or the reflog "cnt" entries from the top (in which case "at_time" + * should be 0). + * + * If we found the reflog entry in question, returns 0 (and details of the + * entry can be found in the out-parameters). + * + * If we ran out of reflog entries, the out-parameters are filled with the + * details of the oldest entry we did find, and the function returns 1. Note + * that there is one important special case here! If the reflog was empty + * and the caller asked for the 0-th cnt, we will return "1" but leave the + * "oid" field untouched. + **/ int read_ref_at(struct ref_store *refs, const char *refname, unsigned int flags, timestamp_t at_time, int cnt, diff --git a/t/t3202-show-branch.sh b/t/t3202-show-branch.sh index 6a98b2df76..a1139f79e2 100755 --- a/t/t3202-show-branch.sh +++ b/t/t3202-show-branch.sh @@ -4,9 +4,6 @@ test_description='test show-branch' . ./test-lib.sh -# arbitrary reference time: 2009-08-30 19:20:00 -GIT_TEST_DATE_NOW=1251660000; export GIT_TEST_DATE_NOW - test_expect_success 'error descriptions on empty repository' ' current=$(git branch --show-current) && cat >expect <<-EOF && @@ -187,18 +184,6 @@ test_expect_success 'show branch --merge-base with N arguments' ' test_cmp expect actual ' -test_expect_success 'show branch --reflog=2' ' - sed "s/^> //" >expect <<-\EOF && - > ! [refs/heads/branch10@{0}] (4 years, 5 months ago) commit: branch10 - > ! [refs/heads/branch10@{1}] (4 years, 5 months ago) commit: branch10 - > -- - > + [refs/heads/branch10@{0}] branch10 - > ++ [refs/heads/branch10@{1}] initial - EOF - git show-branch --reflog=2 >actual && - test_cmp actual expect -' - # incompatible options while read combo do @@ -264,4 +249,38 @@ test_expect_success 'error descriptions on orphan branch' ' test_branch_op_in_wt -c new-branch ' +test_expect_success 'setup reflogs' ' + test_commit base && + git checkout -b branch && + test_commit one && + git reset --hard HEAD^ && + test_commit two && + test_commit three +' + +test_expect_success '--reflog shows reflog entries' ' + cat >expect <<-\EOF && + ! [branch@{0}] (0 seconds ago) commit: three + ! [branch@{1}] (60 seconds ago) commit: two + ! [branch@{2}] (2 minutes ago) reset: moving to HEAD^ + ! [branch@{3}] (2 minutes ago) commit: one + ---- + + [branch@{0}] three + ++ [branch@{1}] two + + [branch@{3}] one + ++++ [branch@{2}] base + EOF + # the output always contains relative timestamps; use + # a known time to get deterministic results + GIT_TEST_DATE_NOW=$test_tick \ + git show-branch --reflog branch >actual && + test_cmp expect actual +' + +test_expect_success '--reflog handles missing reflog' ' + git reflog expire --expire=now branch && + git show-branch --reflog branch >actual && + test_must_be_empty actual +' + test_done