Skip to content

Commit bc7f5db

Browse files
phillipwoodgitster
authored andcommitted
prune: mark rebase autostash and orig-head as reachable
Rebase records the oid of HEAD before rebasing and the commit created by "--autostash" in files in the rebase state directory. This means that the autostash commit is never reachable from any ref or reflog and when rebasing a detached HEAD the original HEAD can become unreachable if the user expires HEAD's the reflog while the rebase is running. Fix this by reading the relevant files when marking reachable commits. Note that it is possible for the commit recorded in .git/rebase-merge/amend to be unreachable but pruning that object does not affect the operation of "git rebase --continue" as we're only interested in the object id, not in the object itself. Reported-by: Orgad Shaneh <[email protected]> Signed-off-by: Phillip Wood <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 3526e67 commit bc7f5db

File tree

3 files changed

+76
-1
lines changed

3 files changed

+76
-1
lines changed

reachable.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "pack-mtimes.h"
1818
#include "config.h"
1919
#include "run-command.h"
20+
#include "sequencer.h"
2021

2122
struct connectivity_progress {
2223
struct progress *progress;
@@ -30,6 +31,52 @@ static void update_progress(struct connectivity_progress *cp)
3031
display_progress(cp->progress, cp->count);
3132
}
3233

34+
static void add_one_file(const char *path, struct rev_info *revs)
35+
{
36+
struct strbuf buf = STRBUF_INIT;
37+
struct object_id oid;
38+
struct object *object;
39+
40+
if (!read_oneliner(&buf, path, READ_ONELINER_SKIP_IF_EMPTY)) {
41+
strbuf_release(&buf);
42+
return;
43+
}
44+
strbuf_trim(&buf);
45+
if (!get_oid_hex(buf.buf, &oid)) {
46+
object = parse_object_or_die(&oid, buf.buf);
47+
add_pending_object(revs, object, "");
48+
}
49+
strbuf_release(&buf);
50+
}
51+
52+
/* Mark objects recorded in rebase state files as reachable. */
53+
static void add_rebase_files(struct rev_info *revs)
54+
{
55+
struct strbuf buf = STRBUF_INIT;
56+
size_t len;
57+
const char *path[] = {
58+
"rebase-apply/autostash",
59+
"rebase-apply/orig-head",
60+
"rebase-merge/autostash",
61+
"rebase-merge/orig-head",
62+
};
63+
struct worktree **worktrees = get_worktrees();
64+
65+
for (struct worktree **wt = worktrees; *wt; wt++) {
66+
strbuf_reset(&buf);
67+
strbuf_addstr(&buf, get_worktree_git_dir(*wt));
68+
strbuf_complete(&buf, '/');
69+
len = buf.len;
70+
for (size_t i = 0; i < ARRAY_SIZE(path); i++) {
71+
strbuf_setlen(&buf, len);
72+
strbuf_addstr(&buf, path[i]);
73+
add_one_file(buf.buf, revs);
74+
}
75+
}
76+
strbuf_release(&buf);
77+
free_worktrees(worktrees);
78+
}
79+
3380
static int add_one_ref(const char *path, const struct object_id *oid,
3481
int flag, void *cb_data)
3582
{
@@ -322,6 +369,9 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog,
322369
head_ref(add_one_ref, revs);
323370
other_head_refs(add_one_ref, revs);
324371

372+
/* rebase autostash and orig-head */
373+
add_rebase_files(revs);
374+
325375
/* Add all reflog info */
326376
if (mark_reflog)
327377
add_reflogs_to_pending(revs, 0);

t/t3407-rebase-abort.sh

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,24 @@ testrebase() {
4040
test_path_is_missing "$state_dir"
4141
'
4242

43+
test_expect_success "pre rebase$type head is marked as reachable" '
44+
# Clean up the state from the previous one
45+
git checkout -f --detach pre-rebase &&
46+
test_tick &&
47+
git commit --amend --only -m "reworded" &&
48+
orig_head=$(git rev-parse HEAD) &&
49+
test_must_fail git rebase$type main &&
50+
# Stop ORIG_HEAD marking $state_dir/orig-head as reachable
51+
git update-ref -d ORIG_HEAD &&
52+
git reflog expire --expire="$GIT_COMMITTER_DATE" --all &&
53+
git prune --expire=now &&
54+
git rebase --abort &&
55+
test_cmp_rev $orig_head HEAD
56+
'
57+
4358
test_expect_success "rebase$type --abort after --skip" '
4459
# Clean up the state from the previous one
45-
git reset --hard pre-rebase &&
60+
git checkout -B to-rebase pre-rebase &&
4661
test_must_fail git rebase$type main &&
4762
test_path_is_dir "$state_dir" &&
4863
test_must_fail git rebase --skip &&

t/t3420-rebase-autostash.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,14 @@ test_expect_success 'never change active branch' '
333333
test_cmp_rev not-the-feature-branch unrelated-onto-branch
334334
'
335335

336+
test_expect_success 'autostash commit is marked as reachable' '
337+
echo changed >file0 &&
338+
git rebase --autostash --exec "git prune --expire=now" \
339+
feature-branch^ feature-branch &&
340+
# git rebase succeeds if the stash cannot be applied so we need to check
341+
# the contents of file0
342+
echo changed >expect &&
343+
test_cmp expect file0
344+
'
345+
336346
test_done

0 commit comments

Comments
 (0)