@@ -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;
@@ -4081,11 +4097,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;
@@ -4601,6 +4703,9 @@ cleanup_head_ref:
strbuf_release(&buf);
strbuf_release(&head_ref);
+
+ if (do_update_refs(r))
+ return -1;
}
/*
@@ -5676,7 +5781,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;
@@ -5696,7 +5801,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;
@@ -5732,7 +5837,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;
@@ -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 &&
-
- mkdir -p .git/worktrees/wt-3/rebase-merge &&
- touch .git/worktrees/wt-3/rebase-merge/interactive &&
+test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase with --update-refs' '
+ test_when_finished git -C wt-3 rebase --abort &&
- 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
'
@@ -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