diff --git a/sequencer.c b/sequencer.c index 92204a8c2e..98111e394d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -36,6 +36,7 @@ #include "rebase-interactive.h" #include "reset.h" #include "branch.h" +#include "log-tree.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -193,6 +194,21 @@ struct update_ref_record { struct object_id after; }; +static struct update_ref_record *init_update_ref_record(const char *ref) +{ + struct update_ref_record *rec; + + CALLOC_ARRAY(rec, 1); + + oidcpy(&rec->before, null_oid()); + oidcpy(&rec->after, null_oid()); + + /* This may fail, but that's fine, we will keep the null OID. */ + read_ref(ref, &rec->before); + + return rec; +} + static int git_sequencer_config(const char *k, const char *v, void *cb) { struct replay_opts *opts = cb; @@ -4106,11 +4122,97 @@ leave_merge: return ret; } -static int do_update_ref(struct repository *r, const char *ref_name) +static int write_update_refs_state(struct string_list *refs_to_oids) { + int result = 0; + struct lock_file lock = LOCK_INIT; + FILE *fp = NULL; + struct string_list_item *item; + char *path; + + if (!refs_to_oids->nr) + return 0; + + path = rebase_path_update_refs(the_repository->gitdir); + + if (safe_create_leading_directories(path)) { + result = error(_("unable to create leading directories of %s"), + path); + goto cleanup; + } + + if (hold_lock_file_for_update(&lock, path, 0) < 0) { + result = error(_("another 'rebase' process appears to be running; " + "'%s.lock' already exists"), + path); + goto cleanup; + } + + fp = fdopen_lock_file(&lock, "w"); + if (!fp) { + result = error_errno(_("could not open '%s' for writing"), path); + rollback_lock_file(&lock); + goto cleanup; + } + + for_each_string_list_item(item, refs_to_oids) { + struct update_ref_record *rec = item->util; + fprintf(fp, "%s\n%s\n%s\n", item->string, + oid_to_hex(&rec->before), oid_to_hex(&rec->after)); + } + + result = commit_lock_file(&lock); + +cleanup: + free(path); + return result; +} + +static int do_update_ref(struct repository *r, const char *refname) +{ + struct string_list_item *item; + struct string_list list = STRING_LIST_INIT_DUP; + + if (sequencer_get_update_refs_state(r->gitdir, &list)) + return -1; + + for_each_string_list_item(item, &list) { + if (!strcmp(item->string, refname)) { + struct update_ref_record *rec = item->util; + if (read_ref("HEAD", &rec->after)) + return -1; + break; + } + } + + write_update_refs_state(&list); + string_list_clear(&list, 1); return 0; } +static int do_update_refs(struct repository *r) +{ + int res = 0; + struct string_list_item *item; + struct string_list refs_to_oids = STRING_LIST_INIT_DUP; + struct ref_store *refs = get_main_ref_store(r); + + if ((res = sequencer_get_update_refs_state(r->gitdir, &refs_to_oids))) + return res; + + for_each_string_list_item(item, &refs_to_oids) { + struct update_ref_record *rec = item->util; + + res |= refs_update_ref(refs, "rewritten during rebase", + item->string, + &rec->after, &rec->before, + 0, UPDATE_REFS_MSG_ON_ERR); + } + + string_list_clear(&refs_to_oids, 1); + return res; +} + static int is_final_fixup(struct todo_list *todo_list) { int i = todo_list->current; @@ -4627,6 +4729,9 @@ cleanup_head_ref: strbuf_release(&buf); strbuf_release(&head_ref); + + if (do_update_refs(r)) + return -1; } /* @@ -5711,7 +5816,7 @@ static int add_decorations_to_list(const struct commit *commit, sti = string_list_insert(&ctx->refs_to_oids, decoration->name); - sti->util = oiddup(the_hash_algo->null_oid); + sti->util = init_update_ref_record(decoration->name); } item->offset_in_buf = base_offset; @@ -5731,7 +5836,7 @@ static int add_decorations_to_list(const struct commit *commit, */ static int todo_list_add_update_ref_commands(struct todo_list *todo_list) { - int i; + int i, res; static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP; static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP; static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP; @@ -5767,7 +5872,16 @@ static int todo_list_add_update_ref_commands(struct todo_list *todo_list) } } + res = write_update_refs_state(&ctx.refs_to_oids); + string_list_clear(&ctx.refs_to_oids, 1); + + if (res) { + /* we failed, so clean up the new list. */ + free(ctx.items); + return res; + } + free(todo_list->items); todo_list->items = ctx.items; todo_list->nr = ctx.items_nr; diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh index 8a03f14df8..50815acd3e 100755 --- a/t/t2407-worktree-heads.sh +++ b/t/t2407-worktree-heads.sh @@ -81,25 +81,16 @@ test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (mer grep "cannot force update the branch '\''wt-2'\'' checked out at.*wt-2" err ' -test_expect_success 'refuse to overwrite: worktree in rebase with --update-refs' ' - test_when_finished rm -rf .git/worktrees/wt-3/rebase-merge && +test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase with --update-refs' ' + test_when_finished git -C wt-3 rebase --abort && - mkdir -p .git/worktrees/wt-3/rebase-merge && - touch .git/worktrees/wt-3/rebase-merge/interactive && - - cat >.git/worktrees/wt-3/rebase-merge/update-refs <<-EOF && - refs/heads/fake-3 - $(git rev-parse HEAD~1) - $(git rev-parse HEAD) - refs/heads/fake-4 - $(git rev-parse HEAD) - $(git rev-parse HEAD) - EOF + git branch -f can-be-updated wt-3 && + test_must_fail git -C wt-3 rebase --update-refs conflict-3 && for i in 3 4 do - test_must_fail git branch -f fake-$i HEAD 2>err && - grep "cannot force update the branch '\''fake-$i'\'' checked out at.*wt-3" err || + test_must_fail git branch -f can-be-updated HEAD 2>err && + grep "cannot force update the branch '\''can-be-updated'\'' checked out at.*wt-3" err || return 1 done ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 75c0b90fae..2fffcf5d5f 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1817,6 +1817,23 @@ test_expect_success '--update-refs adds commands with --rebase-merges' ' ) ' +test_expect_success '--update-refs updates refs correctly' ' + git checkout -B update-refs no-conflict-branch && + git branch -f base HEAD~4 && + git branch -f first HEAD~3 && + git branch -f second HEAD~3 && + git branch -f third HEAD~1 && + test_commit extra2 fileX && + git commit --amend --fixup=L && + + git rebase -i --autosquash --update-refs primary && + + test_cmp_rev HEAD~3 refs/heads/first && + test_cmp_rev HEAD~3 refs/heads/second && + test_cmp_rev HEAD~1 refs/heads/third && + test_cmp_rev HEAD refs/heads/no-conflict-branch +' + # This must be the last test in this file test_expect_success '$EDITOR and friends are unchanged' ' test_editor_unchanged