Skip to content

Commit aa72e73

Browse files
peffgitster
authored andcommitted
Revert "refs: allow @{n} to work with n-sized reflog"
This reverts commit 6436a20. The idea of that commit is that if read_ref_at() is counting back to the Nth reflog but the reflog is short by one entry (e.g., because it was pruned), we can find the oid of the missing entry by looking at the "before" oid value of the entry that comes after it (whereas before, we looked at the "after" value of each entry and complained that we couldn't find the one from before the truncation). This works fine for resolving the oid of ref@{n}, as it is used by get_oid_basic(), which does not look at any other aspect of the reflog we found (e.g., its timestamp or message). But there's another caller of read_ref_at(): in show-branch we use it to walk over the reflog, and we do care about the reflog entry. And so that commit broke "show-branch --reflog"; it shows the reflog message for ref@{0} as ref@{1}, ref@{1} as ref@{2}, and so on. For example, in the new test in t3202 we produce: ! [branch@{0}] (0 seconds ago) commit: three ! [branch@{1}] (0 seconds ago) commit: three ! [branch@{2}] (60 seconds ago) commit: two ! [branch@{3}] (2 minutes ago) reset: moving to HEAD^ instead of the correct: ! [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 But there's another bug, too: because it is looking at the "old" value of the reflog after the one we're interested in, it has to special-case ref@{0} (since there isn't anything after it). That's why it doesn't show the offset bug in the output above. But this special-case code fails to handle the situation where the reflog is empty or missing; it returns success even though the reflog message out-parameter has been left uninitialized. You can't trigger this through get_oid_basic(), but "show-branch --reflog" will pretty reliably segfault as it tries to access the garbage pointer. Fixing the segfault would be pretty easy. But the off-by-one problem is inherent in this approach. So let's start by reverting the commit to give us a clean slate to work with. This isn't a pure revert; all of the code changes are reverted, but for the tests: 1. We'll flip the cases in t1508 to expect_failure; making these work was the goal of 6436a20, and we'll want to use them for our replacement approach. 2. There's a test in t3202 for "show-branch --reflog", but it expects the broken output! It was added by f246349 (show-branch: show reflog message, 2021-12-02) which was fixing another bug, and I think the author simply didn't notice that the second line showed the wrong reflog. Rather than fixing that test, let's replace it with one that is more thorough (while still covering the reflog message fix from that commit). We'll use a longer reflog, which lets us see more entries (thus making the "off by one" pattern much more clear). And we'll use a more recent timestamp for "now" so that our relative dates have more resolution. That lets us see that the reflog dates are correct (whereas when you are 4 years away, two entries that are 60 seconds apart will have the same "4 years ago" relative date). Because we're adjusting the repository state, I've moved this new test to the end of the script, leaving the other tests undisturbed. We'll also add a new test which covers the missing reflog case; previously it segfaulted, but now it reports the empty reflog). Reported-by: Yasushi SHOJI <[email protected]> Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 3c2a3fd commit aa72e73

File tree

3 files changed

+50
-51
lines changed

3 files changed

+50
-51
lines changed

refs.c

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,55 +1038,40 @@ static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid,
10381038
const char *message, void *cb_data)
10391039
{
10401040
struct read_ref_at_cb *cb = cb_data;
1041-
int reached_count;
10421041

10431042
cb->tz = tz;
10441043
cb->date = timestamp;
10451044

1046-
/*
1047-
* It is not possible for cb->cnt == 0 on the first iteration because
1048-
* that special case is handled in read_ref_at().
1049-
*/
1050-
if (cb->cnt > 0)
1051-
cb->cnt--;
1052-
reached_count = cb->cnt == 0 && !is_null_oid(ooid);
1053-
if (timestamp <= cb->at_time || reached_count) {
1045+
if (timestamp <= cb->at_time || cb->cnt == 0) {
10541046
set_read_ref_cutoffs(cb, timestamp, tz, message);
10551047
/*
10561048
* we have not yet updated cb->[n|o]oid so they still
10571049
* hold the values for the previous record.
10581050
*/
1059-
if (!is_null_oid(&cb->ooid) && !oideq(&cb->ooid, noid))
1060-
warning(_("log for ref %s has gap after %s"),
1051+
if (!is_null_oid(&cb->ooid)) {
1052+
oidcpy(cb->oid, noid);
1053+
if (!oideq(&cb->ooid, noid))
1054+
warning(_("log for ref %s has gap after %s"),
10611055
cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
1062-
if (reached_count)
1063-
oidcpy(cb->oid, ooid);
1064-
else if (!is_null_oid(&cb->ooid) || cb->date == cb->at_time)
1056+
}
1057+
else if (cb->date == cb->at_time)
10651058
oidcpy(cb->oid, noid);
10661059
else if (!oideq(noid, cb->oid))
10671060
warning(_("log for ref %s unexpectedly ended on %s"),
10681061
cb->refname, show_date(cb->date, cb->tz,
10691062
DATE_MODE(RFC2822)));
1063+
cb->reccnt++;
1064+
oidcpy(&cb->ooid, ooid);
1065+
oidcpy(&cb->noid, noid);
10701066
cb->found_it = 1;
1067+
return 1;
10711068
}
10721069
cb->reccnt++;
10731070
oidcpy(&cb->ooid, ooid);
10741071
oidcpy(&cb->noid, noid);
1075-
return cb->found_it;
1076-
}
1077-
1078-
static int read_ref_at_ent_newest(struct object_id *ooid UNUSED,
1079-
struct object_id *noid,
1080-
const char *email UNUSED,
1081-
timestamp_t timestamp, int tz,
1082-
const char *message, void *cb_data)
1083-
{
1084-
struct read_ref_at_cb *cb = cb_data;
1085-
1086-
set_read_ref_cutoffs(cb, timestamp, tz, message);
1087-
oidcpy(cb->oid, noid);
1088-
/* We just want the first entry */
1089-
return 1;
1072+
if (cb->cnt > 0)
1073+
cb->cnt--;
1074+
return 0;
10901075
}
10911076

10921077
static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid,
@@ -1121,11 +1106,6 @@ int read_ref_at(struct ref_store *refs, const char *refname,
11211106
cb.cutoff_cnt = cutoff_cnt;
11221107
cb.oid = oid;
11231108

1124-
if (cb.cnt == 0) {
1125-
refs_for_each_reflog_ent_reverse(refs, refname, read_ref_at_ent_newest, &cb);
1126-
return 0;
1127-
}
1128-
11291109
refs_for_each_reflog_ent_reverse(refs, refname, read_ref_at_ent, &cb);
11301110

11311111
if (!cb.reccnt) {

t/t1508-at-combinations.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,14 @@ test_expect_success 'create path with @' '
103103
check "@:normal" blob content
104104
check "@:fun@ny" blob content
105105

106-
test_expect_success '@{1} works with only one reflog entry' '
106+
test_expect_failure '@{1} works with only one reflog entry' '
107107
git checkout -B newbranch main &&
108108
git reflog expire --expire=now refs/heads/newbranch &&
109109
git commit --allow-empty -m "first after expiration" &&
110110
test_cmp_rev newbranch~ newbranch@{1}
111111
'
112112

113-
test_expect_success '@{0} works with empty reflog' '
113+
test_expect_failure '@{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: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ test_description='test show-branch'
44

55
. ./test-lib.sh
66

7-
# arbitrary reference time: 2009-08-30 19:20:00
8-
GIT_TEST_DATE_NOW=1251660000; export GIT_TEST_DATE_NOW
9-
107
test_expect_success 'error descriptions on empty repository' '
118
current=$(git branch --show-current) &&
129
cat >expect <<-EOF &&
@@ -187,18 +184,6 @@ test_expect_success 'show branch --merge-base with N arguments' '
187184
test_cmp expect actual
188185
'
189186

190-
test_expect_success 'show branch --reflog=2' '
191-
sed "s/^> //" >expect <<-\EOF &&
192-
> ! [refs/heads/branch10@{0}] (4 years, 5 months ago) commit: branch10
193-
> ! [refs/heads/branch10@{1}] (4 years, 5 months ago) commit: branch10
194-
> --
195-
> + [refs/heads/branch10@{0}] branch10
196-
> ++ [refs/heads/branch10@{1}] initial
197-
EOF
198-
git show-branch --reflog=2 >actual &&
199-
test_cmp actual expect
200-
'
201-
202187
# incompatible options
203188
while read combo
204189
do
@@ -264,4 +249,38 @@ test_expect_success 'error descriptions on orphan branch' '
264249
test_branch_op_in_wt -c new-branch
265250
'
266251

252+
test_expect_success 'setup reflogs' '
253+
test_commit base &&
254+
git checkout -b branch &&
255+
test_commit one &&
256+
git reset --hard HEAD^ &&
257+
test_commit two &&
258+
test_commit three
259+
'
260+
261+
test_expect_success '--reflog shows reflog entries' '
262+
cat >expect <<-\EOF &&
263+
! [branch@{0}] (0 seconds ago) commit: three
264+
! [branch@{1}] (60 seconds ago) commit: two
265+
! [branch@{2}] (2 minutes ago) reset: moving to HEAD^
266+
! [branch@{3}] (2 minutes ago) commit: one
267+
----
268+
+ [branch@{0}] three
269+
++ [branch@{1}] two
270+
+ [branch@{3}] one
271+
++++ [branch@{2}] base
272+
EOF
273+
# the output always contains relative timestamps; use
274+
# a known time to get deterministic results
275+
GIT_TEST_DATE_NOW=$test_tick \
276+
git show-branch --reflog branch >actual &&
277+
test_cmp expect actual
278+
'
279+
280+
test_expect_success '--reflog handles missing reflog' '
281+
git reflog expire --expire=now branch &&
282+
test_must_fail git show-branch --reflog branch 2>err &&
283+
grep "log .* is empty" err
284+
'
285+
267286
test_done

0 commit comments

Comments
 (0)