Skip to content

Commit 5edd126

Browse files
peffgitster
authored andcommitted
read_ref_at(): special-case ref@{0} for an empty reflog
The previous commit special-cased get_oid_basic()'s handling of ref@{n} for a reflog with n entries. But its special case doesn't work for ref@{0} in an empty reflog, because read_ref_at() dies when it notices the empty reflog! We can make this work by special-casing this in read_ref_at(). It's somewhat gross, for two reasons: 1. We have no reflog entry to describe in the "msg" out-parameter. So we have to leave it uninitialized or make something up. 2. Likewise, we have no oid to put in the "oid" out-parameter. Leaving it untouched is actually the best thing here, as all of the callers will have initialized it with the current ref value via repo_dwim_log(). This is rather subtle, but it is how things worked in 6436a20 (refs: allow @{n} to work with n-sized reflog, 2021-01-07) before we reverted it. The key difference from 6436a20 here is that we'll return "1" to indicate that we _didn't_ find the requested reflog entry. Coupled with the special-casing in get_oid_basic() in the previous commit, that's enough to make looking up ref@{0} work, and we can flip 6436a20's test back to expect_success. It also means that the call in show-branch which segfaulted with 6436a20 (and which is now tested in t3202) remains OK. The caller notices that we could not find any reflog entry, and so it breaks out of its loop, showing nothing. This is different from the current behavior of producing an error, but it's just as reasonable (and is exactly what we'd do if you asked it to walk starting at ref@{1} but there was only 1 entry). Thus nobody should actually look at the reflog entry info we return. But we'll still put in some fake values just to be on the safe side, since this is such a subtle and confusing interface. Likewise, we'll document what's going on in a comment above the function declaration. If this were a function with a lot of callers, the footgun would probably not be worth it. But it has only ever had two callers in its 18-year existence, and it seems unlikely to grow more. So let's hold our noses and let users enjoy the convenience of a simulated ref@{0}. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 755e746 commit 5edd126

File tree

4 files changed

+32
-4
lines changed

4 files changed

+32
-4
lines changed

refs.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,21 @@ int read_ref_at(struct ref_store *refs, const char *refname,
11091109
refs_for_each_reflog_ent_reverse(refs, refname, read_ref_at_ent, &cb);
11101110

11111111
if (!cb.reccnt) {
1112+
if (cnt == 0) {
1113+
/*
1114+
* The caller asked for ref@{0}, and we had no entries.
1115+
* It's a bit subtle, but in practice all callers have
1116+
* prepped the "oid" field with the current value of
1117+
* the ref, which is the most reasonable fallback.
1118+
*
1119+
* We'll put dummy values into the out-parameters (so
1120+
* they're not just uninitialized garbage), and the
1121+
* caller can take our return value as a hint that
1122+
* we did not find any such reflog.
1123+
*/
1124+
set_read_ref_cutoffs(&cb, 0, 0, "empty reflog");
1125+
return 1;
1126+
}
11121127
if (flags & GET_OID_QUIETLY)
11131128
exit(128);
11141129
else

refs.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,20 @@ int refs_create_reflog(struct ref_store *refs, const char *refname,
440440
struct strbuf *err);
441441
int safe_create_reflog(const char *refname, struct strbuf *err);
442442

443-
/** Reads log for the value of ref during at_time. **/
443+
/**
444+
* Reads log for the value of ref during at_time (in which case "cnt" should be
445+
* negative) or the reflog "cnt" entries from the top (in which case "at_time"
446+
* should be 0).
447+
*
448+
* If we found the reflog entry in question, returns 0 (and details of the
449+
* entry can be found in the out-parameters).
450+
*
451+
* If we ran out of reflog entries, the out-parameters are filled with the
452+
* details of the oldest entry we did find, and the function returns 1. Note
453+
* that there is one important special case here! If the reflog was empty
454+
* and the caller asked for the 0-th cnt, we will return "1" but leave the
455+
* "oid" field untouched.
456+
**/
444457
int read_ref_at(struct ref_store *refs,
445458
const char *refname, unsigned int flags,
446459
timestamp_t at_time, int cnt,

t/t1508-at-combinations.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ test_expect_success '@{1} works with only one reflog entry' '
110110
test_cmp_rev newbranch~ newbranch@{1}
111111
'
112112

113-
test_expect_failure '@{0} works with empty reflog' '
113+
test_expect_success '@{0} works with empty reflog' '
114114
git checkout -B newbranch main &&
115115
git reflog expire --expire=now refs/heads/newbranch &&
116116
test_cmp_rev newbranch newbranch@{0}

t/t3202-show-branch.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,8 @@ test_expect_success '--reflog shows reflog entries' '
279279

280280
test_expect_success '--reflog handles missing reflog' '
281281
git reflog expire --expire=now branch &&
282-
test_must_fail git show-branch --reflog branch 2>err &&
283-
grep "log .* is empty" err
282+
git show-branch --reflog branch >actual &&
283+
test_must_be_empty actual
284284
'
285285

286286
test_done

0 commit comments

Comments
 (0)