Merge branch 'ds/rebase-update-ref'
"git rebase -i" learns to update branches whose tip appear in the rebased range with "--update-refs" option. source: <pull.1247.v5.git.1658255624.gitgitgadget@gmail.com> * ds/rebase-update-ref: sequencer: notify user of --update-refs activity sequencer: ignore HEAD ref under --update-refs rebase: add rebase.updateRefs config option sequencer: rewrite update-refs as user edits todo list rebase: update refs from 'update-ref' commands rebase: add --update-refs option sequencer: add update-ref command sequencer: define array with enum values rebase-interactive: update 'merge' description branch: consider refs under 'update-refs' t2407: test branches currently using apply backend t2407: test bisect and rebase as black-boxes
This commit is contained in:
commit
3d8e3dc4fc
@ -21,6 +21,9 @@ rebase.autoStash::
|
||||
`--autostash` options of linkgit:git-rebase[1].
|
||||
Defaults to false.
|
||||
|
||||
rebase.updateRefs::
|
||||
If set to true enable `--update-refs` option by default.
|
||||
|
||||
rebase.missingCommitsCheck::
|
||||
If set to "warn", git rebase -i will print a warning if some
|
||||
commits are removed (e.g. a line was deleted), however the
|
||||
|
@ -612,6 +612,15 @@ provided. Otherwise an explicit `--no-reschedule-failed-exec` at the
|
||||
start would be overridden by the presence of
|
||||
`rebase.rescheduleFailedExec=true` configuration.
|
||||
|
||||
--update-refs::
|
||||
--no-update-refs::
|
||||
Automatically force-update any branches that point to commits that
|
||||
are being rebased. Any branches that are checked out in a worktree
|
||||
are not updated in this way.
|
||||
+
|
||||
If the configuration variable `rebase.updateRefs` is set, then this option
|
||||
can be used to override and disable this setting.
|
||||
|
||||
INCOMPATIBLE OPTIONS
|
||||
--------------------
|
||||
|
||||
@ -635,6 +644,7 @@ are incompatible with the following options:
|
||||
* --empty=
|
||||
* --reapply-cherry-picks
|
||||
* --edit-todo
|
||||
* --update-refs
|
||||
* --root when used in combination with --onto
|
||||
|
||||
In addition, the following pairs of options are incompatible:
|
||||
|
13
branch.c
13
branch.c
@ -388,6 +388,7 @@ static void prepare_checked_out_branches(void)
|
||||
char *old;
|
||||
struct wt_status_state state = { 0 };
|
||||
struct worktree *wt = worktrees[i++];
|
||||
struct string_list update_refs = STRING_LIST_INIT_DUP;
|
||||
|
||||
if (wt->is_bare)
|
||||
continue;
|
||||
@ -423,6 +424,18 @@ static void prepare_checked_out_branches(void)
|
||||
strbuf_release(&ref);
|
||||
}
|
||||
wt_status_state_free_buffers(&state);
|
||||
|
||||
if (!sequencer_get_update_refs_state(get_worktree_git_dir(wt),
|
||||
&update_refs)) {
|
||||
struct string_list_item *item;
|
||||
for_each_string_list_item(item, &update_refs) {
|
||||
old = strmap_put(¤t_checked_out_branches,
|
||||
item->string,
|
||||
xstrdup(wt->path));
|
||||
free(old);
|
||||
}
|
||||
string_list_clear(&update_refs, 1);
|
||||
}
|
||||
}
|
||||
|
||||
free_worktrees(worktrees);
|
||||
|
@ -102,6 +102,7 @@ struct rebase_options {
|
||||
int reschedule_failed_exec;
|
||||
int reapply_cherry_picks;
|
||||
int fork_point;
|
||||
int update_refs;
|
||||
};
|
||||
|
||||
#define REBASE_OPTIONS_INIT { \
|
||||
@ -298,6 +299,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
|
||||
ret = complete_action(the_repository, &replay, flags,
|
||||
shortrevisions, opts->onto_name, opts->onto,
|
||||
&opts->orig_head, &commands, opts->autosquash,
|
||||
opts->update_refs,
|
||||
&todo_list);
|
||||
}
|
||||
|
||||
@ -800,6 +802,11 @@ static int rebase_config(const char *var, const char *value, void *data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(var, "rebase.updaterefs")) {
|
||||
opts->update_refs = git_config_bool(var, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(var, "rebase.reschedulefailedexec")) {
|
||||
opts->reschedule_failed_exec = git_config_bool(var, value);
|
||||
return 0;
|
||||
@ -1124,6 +1131,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
||||
OPT_BOOL(0, "autosquash", &options.autosquash,
|
||||
N_("move commits that begin with "
|
||||
"squash!/fixup! under -i")),
|
||||
OPT_BOOL(0, "update-refs", &options.update_refs,
|
||||
N_("update branches that point to commits "
|
||||
"that are being rebased")),
|
||||
{ OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"),
|
||||
N_("GPG-sign commits"),
|
||||
PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
|
||||
|
@ -54,9 +54,12 @@ void append_todo_help(int command_count,
|
||||
"l, label <label> = label current HEAD with a name\n"
|
||||
"t, reset <label> = reset HEAD to a label\n"
|
||||
"m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n"
|
||||
". create a merge commit using the original merge commit's\n"
|
||||
". message (or the oneline, if no original merge commit was\n"
|
||||
". specified); use -c <commit> to reword the commit message\n"
|
||||
" create a merge commit using the original merge commit's\n"
|
||||
" message (or the oneline, if no original merge commit was\n"
|
||||
" specified); use -c <commit> to reword the commit message\n"
|
||||
"u, update-ref <ref> = track a placeholder for the <ref> to be updated\n"
|
||||
" to this position in the new commits. The <ref> is\n"
|
||||
" updated at the end of the rebase\n"
|
||||
"\n"
|
||||
"These lines can be re-ordered; they are executed from top to bottom.\n");
|
||||
unsigned edit_todo = !(shortrevisions && shortonto);
|
||||
@ -143,6 +146,12 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,
|
||||
return -4;
|
||||
}
|
||||
|
||||
/*
|
||||
* See if branches need to be added or removed from the update-refs
|
||||
* file based on the new todo list.
|
||||
*/
|
||||
todo_list_filter_update_refs(r, new_todo);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
474
sequencer.c
474
sequencer.c
@ -35,6 +35,8 @@
|
||||
#include "commit-reach.h"
|
||||
#include "rebase-interactive.h"
|
||||
#include "reset.h"
|
||||
#include "branch.h"
|
||||
#include "log-tree.h"
|
||||
|
||||
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
|
||||
|
||||
@ -147,6 +149,20 @@ static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto")
|
||||
*/
|
||||
static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
|
||||
|
||||
/*
|
||||
* The update-refs file stores a list of refs that will be updated at the end
|
||||
* of the rebase sequence. The 'update-ref <ref>' commands in the todo file
|
||||
* update the OIDs for the refs in this file, but the refs are not updated
|
||||
* until the end of the rebase sequence.
|
||||
*
|
||||
* rebase_path_update_refs() returns the path to this file for a given
|
||||
* worktree directory. For the current worktree, pass the_repository->gitdir.
|
||||
*/
|
||||
static char *rebase_path_update_refs(const char *wt_git_dir)
|
||||
{
|
||||
return xstrfmt("%s/rebase-merge/update-refs", wt_git_dir);
|
||||
}
|
||||
|
||||
/*
|
||||
* The following files are written by git-rebase just after parsing the
|
||||
* command-line.
|
||||
@ -169,6 +185,30 @@ static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, "rebase-merge/no-res
|
||||
static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
|
||||
static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
|
||||
|
||||
/**
|
||||
* A 'struct update_refs_record' represents a value in the update-refs
|
||||
* list. We use a string_list to map refs to these (before, after) pairs.
|
||||
*/
|
||||
struct update_ref_record {
|
||||
struct object_id before;
|
||||
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;
|
||||
@ -1689,20 +1729,21 @@ static struct {
|
||||
char c;
|
||||
const char *str;
|
||||
} todo_command_info[] = {
|
||||
{ 'p', "pick" },
|
||||
{ 0, "revert" },
|
||||
{ 'e', "edit" },
|
||||
{ 'r', "reword" },
|
||||
{ 'f', "fixup" },
|
||||
{ 's', "squash" },
|
||||
{ 'x', "exec" },
|
||||
{ 'b', "break" },
|
||||
{ 'l', "label" },
|
||||
{ 't', "reset" },
|
||||
{ 'm', "merge" },
|
||||
{ 0, "noop" },
|
||||
{ 'd', "drop" },
|
||||
{ 0, NULL }
|
||||
[TODO_PICK] = { 'p', "pick" },
|
||||
[TODO_REVERT] = { 0, "revert" },
|
||||
[TODO_EDIT] = { 'e', "edit" },
|
||||
[TODO_REWORD] = { 'r', "reword" },
|
||||
[TODO_FIXUP] = { 'f', "fixup" },
|
||||
[TODO_SQUASH] = { 's', "squash" },
|
||||
[TODO_EXEC] = { 'x', "exec" },
|
||||
[TODO_BREAK] = { 'b', "break" },
|
||||
[TODO_LABEL] = { 'l', "label" },
|
||||
[TODO_RESET] = { 't', "reset" },
|
||||
[TODO_MERGE] = { 'm', "merge" },
|
||||
[TODO_UPDATE_REF] = { 'u', "update-ref" },
|
||||
[TODO_NOOP] = { 0, "noop" },
|
||||
[TODO_DROP] = { 'd', "drop" },
|
||||
[TODO_COMMENT] = { 0, NULL },
|
||||
};
|
||||
|
||||
static const char *command_to_string(const enum todo_command command)
|
||||
@ -2481,7 +2522,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
|
||||
command_to_string(item->command));
|
||||
|
||||
if (item->command == TODO_EXEC || item->command == TODO_LABEL ||
|
||||
item->command == TODO_RESET) {
|
||||
item->command == TODO_RESET || item->command == TODO_UPDATE_REF) {
|
||||
item->commit = NULL;
|
||||
item->arg_offset = bol - buf;
|
||||
item->arg_len = (int)(eol - bol);
|
||||
@ -4081,6 +4122,221 @@ leave_merge:
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the update-refs file for the current rebase, then remove the
|
||||
* refs that do not appear in the todo_list (and have not had updated
|
||||
* values stored) and add refs that are in the todo_list but not
|
||||
* represented in the update-refs file.
|
||||
*
|
||||
* If there are changes to the update-refs list, then write the new state
|
||||
* to disk.
|
||||
*/
|
||||
void todo_list_filter_update_refs(struct repository *r,
|
||||
struct todo_list *todo_list)
|
||||
{
|
||||
int i;
|
||||
int updated = 0;
|
||||
struct string_list update_refs = STRING_LIST_INIT_DUP;
|
||||
|
||||
sequencer_get_update_refs_state(r->gitdir, &update_refs);
|
||||
|
||||
/*
|
||||
* For each item in the update_refs list, if it has no updated
|
||||
* value and does not appear in the todo_list, then remove it
|
||||
* from the update_refs list.
|
||||
*/
|
||||
for (i = 0; i < update_refs.nr; i++) {
|
||||
int j;
|
||||
int found = 0;
|
||||
const char *ref = update_refs.items[i].string;
|
||||
size_t reflen = strlen(ref);
|
||||
struct update_ref_record *rec = update_refs.items[i].util;
|
||||
|
||||
/* OID already stored as updated. */
|
||||
if (!is_null_oid(&rec->after))
|
||||
continue;
|
||||
|
||||
for (j = 0; !found && j < todo_list->total_nr; j++) {
|
||||
struct todo_item *item = &todo_list->items[j];
|
||||
const char *arg = todo_list->buf.buf + item->arg_offset;
|
||||
|
||||
if (item->command != TODO_UPDATE_REF)
|
||||
continue;
|
||||
|
||||
if (item->arg_len != reflen ||
|
||||
strncmp(arg, ref, reflen))
|
||||
continue;
|
||||
|
||||
found = 1;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
free(update_refs.items[i].string);
|
||||
free(update_refs.items[i].util);
|
||||
|
||||
update_refs.nr--;
|
||||
MOVE_ARRAY(update_refs.items + i, update_refs.items + i + 1, update_refs.nr - i);
|
||||
|
||||
updated = 1;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* For each todo_item, check if its ref is in the update_refs list.
|
||||
* If not, then add it as an un-updated ref.
|
||||
*/
|
||||
for (i = 0; i < todo_list->total_nr; i++) {
|
||||
struct todo_item *item = &todo_list->items[i];
|
||||
const char *arg = todo_list->buf.buf + item->arg_offset;
|
||||
int j, found = 0;
|
||||
|
||||
if (item->command != TODO_UPDATE_REF)
|
||||
continue;
|
||||
|
||||
for (j = 0; !found && j < update_refs.nr; j++) {
|
||||
const char *ref = update_refs.items[j].string;
|
||||
|
||||
found = strlen(ref) == item->arg_len &&
|
||||
!strncmp(ref, arg, item->arg_len);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
struct string_list_item *inserted;
|
||||
struct strbuf argref = STRBUF_INIT;
|
||||
|
||||
strbuf_add(&argref, arg, item->arg_len);
|
||||
inserted = string_list_insert(&update_refs, argref.buf);
|
||||
inserted->util = init_update_ref_record(argref.buf);
|
||||
strbuf_release(&argref);
|
||||
updated = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
write_update_refs_state(&update_refs);
|
||||
string_list_clear(&update_refs, 1);
|
||||
}
|
||||
|
||||
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 quiet)
|
||||
{
|
||||
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);
|
||||
struct strbuf update_msg = STRBUF_INIT;
|
||||
struct strbuf error_msg = STRBUF_INIT;
|
||||
|
||||
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;
|
||||
int loop_res;
|
||||
|
||||
loop_res = refs_update_ref(refs, "rewritten during rebase",
|
||||
item->string,
|
||||
&rec->after, &rec->before,
|
||||
0, UPDATE_REFS_MSG_ON_ERR);
|
||||
res |= loop_res;
|
||||
|
||||
if (quiet)
|
||||
continue;
|
||||
|
||||
if (loop_res)
|
||||
strbuf_addf(&error_msg, "\t%s\n", item->string);
|
||||
else
|
||||
strbuf_addf(&update_msg, "\t%s\n", item->string);
|
||||
}
|
||||
|
||||
if (!quiet &&
|
||||
(update_msg.len || error_msg.len)) {
|
||||
fprintf(stderr,
|
||||
_("Updated the following refs with %s:\n%s"),
|
||||
"--update-refs",
|
||||
update_msg.buf);
|
||||
|
||||
if (res)
|
||||
fprintf(stderr,
|
||||
_("Failed to update the following refs with %s:\n%s"),
|
||||
"--update-refs",
|
||||
error_msg.buf);
|
||||
}
|
||||
|
||||
string_list_clear(&refs_to_oids, 1);
|
||||
strbuf_release(&update_msg);
|
||||
strbuf_release(&error_msg);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int is_final_fixup(struct todo_list *todo_list)
|
||||
{
|
||||
int i = todo_list->current;
|
||||
@ -4456,6 +4712,12 @@ static int pick_commits(struct repository *r,
|
||||
return error_with_patch(r, item->commit,
|
||||
arg, item->arg_len,
|
||||
opts, res, 0);
|
||||
} else if (item->command == TODO_UPDATE_REF) {
|
||||
struct strbuf ref = STRBUF_INIT;
|
||||
strbuf_add(&ref, arg, item->arg_len);
|
||||
if ((res = do_update_ref(r, ref.buf)))
|
||||
reschedule = 1;
|
||||
strbuf_release(&ref);
|
||||
} else if (!is_noop(item->command))
|
||||
return error(_("unknown command %d"), item->command);
|
||||
|
||||
@ -4591,6 +4853,9 @@ cleanup_head_ref:
|
||||
|
||||
strbuf_release(&buf);
|
||||
strbuf_release(&head_ref);
|
||||
|
||||
if (do_update_refs(r, opts->quiet))
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -5638,10 +5903,135 @@ static int skip_unnecessary_picks(struct repository *r,
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct todo_add_branch_context {
|
||||
struct todo_item *items;
|
||||
size_t items_nr;
|
||||
size_t items_alloc;
|
||||
struct strbuf *buf;
|
||||
struct commit *commit;
|
||||
struct string_list refs_to_oids;
|
||||
};
|
||||
|
||||
static int add_decorations_to_list(const struct commit *commit,
|
||||
struct todo_add_branch_context *ctx)
|
||||
{
|
||||
const struct name_decoration *decoration = get_name_decoration(&commit->object);
|
||||
const char *head_ref = resolve_ref_unsafe("HEAD",
|
||||
RESOLVE_REF_READING,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
while (decoration) {
|
||||
struct todo_item *item;
|
||||
const char *path;
|
||||
size_t base_offset = ctx->buf->len;
|
||||
|
||||
/*
|
||||
* If the branch is the current HEAD, then it will be
|
||||
* updated by the default rebase behavior.
|
||||
*/
|
||||
if (head_ref && !strcmp(head_ref, decoration->name)) {
|
||||
decoration = decoration->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
ALLOC_GROW(ctx->items,
|
||||
ctx->items_nr + 1,
|
||||
ctx->items_alloc);
|
||||
item = &ctx->items[ctx->items_nr];
|
||||
memset(item, 0, sizeof(*item));
|
||||
|
||||
/* If the branch is checked out, then leave a comment instead. */
|
||||
if ((path = branch_checked_out(decoration->name))) {
|
||||
item->command = TODO_COMMENT;
|
||||
strbuf_addf(ctx->buf, "# Ref %s checked out at '%s'\n",
|
||||
decoration->name, path);
|
||||
} else {
|
||||
struct string_list_item *sti;
|
||||
item->command = TODO_UPDATE_REF;
|
||||
strbuf_addf(ctx->buf, "%s\n", decoration->name);
|
||||
|
||||
sti = string_list_insert(&ctx->refs_to_oids,
|
||||
decoration->name);
|
||||
sti->util = init_update_ref_record(decoration->name);
|
||||
}
|
||||
|
||||
item->offset_in_buf = base_offset;
|
||||
item->arg_offset = base_offset;
|
||||
item->arg_len = ctx->buf->len - base_offset;
|
||||
ctx->items_nr++;
|
||||
|
||||
decoration = decoration->next;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* For each 'pick' command, find out if the commit has a decoration in
|
||||
* refs/heads/. If so, then add a 'label for-update-refs/' command.
|
||||
*/
|
||||
static int todo_list_add_update_ref_commands(struct todo_list *todo_list)
|
||||
{
|
||||
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;
|
||||
struct decoration_filter decoration_filter = {
|
||||
.include_ref_pattern = &decorate_refs_include,
|
||||
.exclude_ref_pattern = &decorate_refs_exclude,
|
||||
.exclude_ref_config_pattern = &decorate_refs_exclude_config,
|
||||
};
|
||||
struct todo_add_branch_context ctx = {
|
||||
.buf = &todo_list->buf,
|
||||
.refs_to_oids = STRING_LIST_INIT_DUP,
|
||||
};
|
||||
|
||||
ctx.items_alloc = 2 * todo_list->nr + 1;
|
||||
ALLOC_ARRAY(ctx.items, ctx.items_alloc);
|
||||
|
||||
string_list_append(&decorate_refs_include, "refs/heads/");
|
||||
load_ref_decorations(&decoration_filter, 0);
|
||||
|
||||
for (i = 0; i < todo_list->nr; ) {
|
||||
struct todo_item *item = &todo_list->items[i];
|
||||
|
||||
/* insert ith item into new list */
|
||||
ALLOC_GROW(ctx.items,
|
||||
ctx.items_nr + 1,
|
||||
ctx.items_alloc);
|
||||
|
||||
ctx.items[ctx.items_nr++] = todo_list->items[i++];
|
||||
|
||||
if (item->commit) {
|
||||
ctx.commit = item->commit;
|
||||
add_decorations_to_list(item->commit, &ctx);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
todo_list->alloc = ctx.items_alloc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,
|
||||
const char *shortrevisions, const char *onto_name,
|
||||
struct commit *onto, const struct object_id *orig_head,
|
||||
struct string_list *commands, unsigned autosquash,
|
||||
unsigned update_refs,
|
||||
struct todo_list *todo_list)
|
||||
{
|
||||
char shortonto[GIT_MAX_HEXSZ + 1];
|
||||
@ -5660,6 +6050,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
|
||||
item->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0;
|
||||
}
|
||||
|
||||
if (update_refs && todo_list_add_update_ref_commands(todo_list))
|
||||
return -1;
|
||||
|
||||
if (autosquash && todo_list_rearrange_squash(todo_list))
|
||||
return -1;
|
||||
|
||||
@ -5936,3 +6329,54 @@ int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sequencer_get_update_refs_state(const char *wt_dir,
|
||||
struct string_list *refs)
|
||||
{
|
||||
int result = 0;
|
||||
FILE *fp = NULL;
|
||||
struct strbuf ref = STRBUF_INIT;
|
||||
struct strbuf hash = STRBUF_INIT;
|
||||
struct update_ref_record *rec = NULL;
|
||||
|
||||
char *path = rebase_path_update_refs(wt_dir);
|
||||
|
||||
fp = fopen(path, "r");
|
||||
if (!fp)
|
||||
goto cleanup;
|
||||
|
||||
while (strbuf_getline(&ref, fp) != EOF) {
|
||||
struct string_list_item *item;
|
||||
|
||||
CALLOC_ARRAY(rec, 1);
|
||||
|
||||
if (strbuf_getline(&hash, fp) == EOF ||
|
||||
get_oid_hex(hash.buf, &rec->before)) {
|
||||
warning(_("update-refs file at '%s' is invalid"),
|
||||
path);
|
||||
result = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (strbuf_getline(&hash, fp) == EOF ||
|
||||
get_oid_hex(hash.buf, &rec->after)) {
|
||||
warning(_("update-refs file at '%s' is invalid"),
|
||||
path);
|
||||
result = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
item = string_list_insert(refs, ref.buf);
|
||||
item->util = rec;
|
||||
rec = NULL;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
free(path);
|
||||
free(rec);
|
||||
strbuf_release(&ref);
|
||||
strbuf_release(&hash);
|
||||
return result;
|
||||
}
|
||||
|
23
sequencer.h
23
sequencer.h
@ -96,6 +96,7 @@ enum todo_command {
|
||||
TODO_LABEL,
|
||||
TODO_RESET,
|
||||
TODO_MERGE,
|
||||
TODO_UPDATE_REF,
|
||||
/* commands that do nothing but are counted for reporting progress */
|
||||
TODO_NOOP,
|
||||
TODO_DROP,
|
||||
@ -132,6 +133,18 @@ void todo_list_release(struct todo_list *todo_list);
|
||||
const char *todo_item_get_arg(struct todo_list *todo_list,
|
||||
struct todo_item *item);
|
||||
|
||||
/*
|
||||
* Parse the update-refs file for the current rebase, then remove the
|
||||
* refs that do not appear in the todo_list (and have not had updated
|
||||
* values stored) and add refs that are in the todo_list but not
|
||||
* represented in the update-refs file.
|
||||
*
|
||||
* If there are changes to the update-refs list, then write the new state
|
||||
* to disk.
|
||||
*/
|
||||
void todo_list_filter_update_refs(struct repository *r,
|
||||
struct todo_list *todo_list);
|
||||
|
||||
/* Call this to setup defaults before parsing command line options */
|
||||
void sequencer_init_config(struct replay_opts *opts);
|
||||
int sequencer_pick_revisions(struct repository *repo,
|
||||
@ -167,6 +180,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
|
||||
const char *shortrevisions, const char *onto_name,
|
||||
struct commit *onto, const struct object_id *orig_head,
|
||||
struct string_list *commands, unsigned autosquash,
|
||||
unsigned update_refs,
|
||||
struct todo_list *todo_list);
|
||||
int todo_list_rearrange_squash(struct todo_list *todo_list);
|
||||
|
||||
@ -233,4 +247,13 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose);
|
||||
int sequencer_get_last_command(struct repository* r,
|
||||
enum replay_action *action);
|
||||
int sequencer_determine_whence(struct repository *r, enum commit_whence *whence);
|
||||
|
||||
/**
|
||||
* Append the set of ref-OID pairs that are currently stored for the 'git
|
||||
* rebase --update-refs' feature if such a rebase is currently happening.
|
||||
*
|
||||
* Localized to a worktree's git dir.
|
||||
*/
|
||||
int sequencer_get_update_refs_state(const char *wt_dir, struct string_list *refs);
|
||||
|
||||
#endif /* SEQUENCER_H */
|
||||
|
@ -207,3 +207,18 @@ check_reworded_commits () {
|
||||
>reword-log &&
|
||||
test_cmp reword-expected reword-log
|
||||
}
|
||||
|
||||
# usage: set_replace_editor <file>
|
||||
#
|
||||
# Replace the todo file with the exact contents of the given file.
|
||||
set_replace_editor () {
|
||||
cat >script <<-\EOF &&
|
||||
cat FILENAME >"$1"
|
||||
|
||||
echo 'rebase -i script after editing:'
|
||||
cat "$1"
|
||||
EOF
|
||||
|
||||
sed -e "s/FILENAME/$1/g" <script | write_script fake-editor.sh &&
|
||||
test_set_editor "$(pwd)/fake-editor.sh"
|
||||
}
|
||||
|
@ -7,13 +7,18 @@ TEST_PASSES_SANITIZE_LEAK=true
|
||||
|
||||
test_expect_success 'setup' '
|
||||
test_commit init &&
|
||||
git branch -f fake-1 &&
|
||||
git branch -f fake-2 &&
|
||||
|
||||
for i in 1 2 3 4
|
||||
do
|
||||
git checkout -b conflict-$i &&
|
||||
echo "not I" >$i.t &&
|
||||
git add $i.t &&
|
||||
git commit -m "will conflict" &&
|
||||
|
||||
git checkout - &&
|
||||
test_commit $i &&
|
||||
git branch wt-$i &&
|
||||
git branch fake-$i &&
|
||||
git worktree add wt-$i wt-$i || return 1
|
||||
done &&
|
||||
|
||||
@ -44,26 +49,50 @@ test_expect_success 'refuse to overwrite: checked out in worktree' '
|
||||
done
|
||||
'
|
||||
|
||||
test_expect_success 'refuse to overwrite: worktree in bisect' '
|
||||
test_when_finished rm -rf .git/worktrees/wt-*/BISECT_* &&
|
||||
test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in bisect' '
|
||||
test_when_finished git -C wt-4 bisect reset &&
|
||||
|
||||
touch .git/worktrees/wt-4/BISECT_LOG &&
|
||||
echo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START &&
|
||||
# Set up a bisect so HEAD no longer points to wt-4.
|
||||
git -C wt-4 bisect start &&
|
||||
git -C wt-4 bisect bad wt-4 &&
|
||||
git -C wt-4 bisect good wt-1 &&
|
||||
|
||||
test_must_fail git branch -f fake-2 HEAD 2>err &&
|
||||
grep "cannot force update the branch '\''fake-2'\'' checked out at.*wt-4" err
|
||||
test_must_fail git branch -f wt-4 HEAD 2>err &&
|
||||
grep "cannot force update the branch '\''wt-4'\'' checked out at.*wt-4" err
|
||||
'
|
||||
|
||||
test_expect_success 'refuse to overwrite: worktree in rebase' '
|
||||
test_when_finished rm -rf .git/worktrees/wt-*/rebase-merge &&
|
||||
test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (apply)' '
|
||||
test_when_finished git -C wt-2 rebase --abort &&
|
||||
|
||||
mkdir -p .git/worktrees/wt-3/rebase-merge &&
|
||||
touch .git/worktrees/wt-3/rebase-merge/interactive &&
|
||||
echo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name &&
|
||||
echo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto &&
|
||||
# This will fail part-way through due to a conflict.
|
||||
test_must_fail git -C wt-2 rebase --apply conflict-2 &&
|
||||
|
||||
test_must_fail git branch -f fake-1 HEAD 2>err &&
|
||||
grep "cannot force update the branch '\''fake-1'\'' checked out at.*wt-3" err
|
||||
test_must_fail git branch -f wt-2 HEAD 2>err &&
|
||||
grep "cannot force update the branch '\''wt-2'\'' checked out at.*wt-2" err
|
||||
'
|
||||
|
||||
test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase (merge)' '
|
||||
test_when_finished git -C wt-2 rebase --abort &&
|
||||
|
||||
# This will fail part-way through due to a conflict.
|
||||
test_must_fail git -C wt-2 rebase conflict-2 &&
|
||||
|
||||
test_must_fail git branch -f wt-2 HEAD 2>err &&
|
||||
grep "cannot force update the branch '\''wt-2'\'' checked out at.*wt-2" err
|
||||
'
|
||||
|
||||
test_expect_success !SANITIZE_LEAK 'refuse to overwrite: worktree in rebase with --update-refs' '
|
||||
test_when_finished git -C wt-3 rebase --abort &&
|
||||
|
||||
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 can-be-updated HEAD 2>err &&
|
||||
grep "cannot force update the branch '\''can-be-updated'\'' checked out at.*wt-3" err ||
|
||||
return 1
|
||||
done
|
||||
'
|
||||
|
||||
test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' '
|
||||
@ -77,24 +106,24 @@ test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' '
|
||||
'
|
||||
|
||||
test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in bisect' '
|
||||
test_when_finished rm -rf .git/worktrees/wt-*/BISECT_* &&
|
||||
test_when_finished git -C wt-4 bisect reset &&
|
||||
|
||||
touch .git/worktrees/wt-4/BISECT_LOG &&
|
||||
echo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START &&
|
||||
# Set up a bisect so HEAD no longer points to wt-4.
|
||||
git -C wt-4 bisect start &&
|
||||
git -C wt-4 bisect bad wt-4 &&
|
||||
git -C wt-4 bisect good wt-1 &&
|
||||
|
||||
test_must_fail git fetch server +refs/heads/fake-2:refs/heads/fake-2 2>err &&
|
||||
test_must_fail git fetch server +refs/heads/wt-4:refs/heads/wt-4 2>err &&
|
||||
grep "refusing to fetch into branch" err
|
||||
'
|
||||
|
||||
test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in rebase' '
|
||||
test_when_finished rm -rf .git/worktrees/wt-*/rebase-merge &&
|
||||
test_when_finished git -C wt-3 rebase --abort &&
|
||||
|
||||
mkdir -p .git/worktrees/wt-4/rebase-merge &&
|
||||
touch .git/worktrees/wt-4/rebase-merge/interactive &&
|
||||
echo refs/heads/fake-1 >.git/worktrees/wt-4/rebase-merge/head-name &&
|
||||
echo refs/heads/fake-2 >.git/worktrees/wt-4/rebase-merge/onto &&
|
||||
# This will fail part-way through due to a conflict.
|
||||
test_must_fail git -C wt-3 rebase conflict-3 &&
|
||||
|
||||
test_must_fail git fetch server +refs/heads/fake-1:refs/heads/fake-1 2>err &&
|
||||
test_must_fail git fetch server +refs/heads/wt-3:refs/heads/wt-3 2>err &&
|
||||
grep "refusing to fetch into branch" err
|
||||
'
|
||||
|
||||
@ -126,4 +155,26 @@ test_expect_success 'refuse to overwrite when in error states' '
|
||||
done
|
||||
'
|
||||
|
||||
. "$TEST_DIRECTORY"/lib-rebase.sh
|
||||
|
||||
test_expect_success !SANITIZE_LEAK 'refuse to overwrite during rebase with --update-refs' '
|
||||
git commit --fixup HEAD~2 --allow-empty &&
|
||||
(
|
||||
set_cat_todo_editor &&
|
||||
test_must_fail git rebase -i --update-refs HEAD~3 >todo &&
|
||||
! grep "update-refs" todo
|
||||
) &&
|
||||
git branch -f allow-update HEAD~2 &&
|
||||
(
|
||||
set_cat_todo_editor &&
|
||||
test_must_fail git rebase -i --update-refs HEAD~3 >todo &&
|
||||
grep "update-ref refs/heads/allow-update" todo
|
||||
)
|
||||
'
|
||||
|
||||
# This must be the last test in this file
|
||||
test_expect_success '$EDITOR and friends are unchanged' '
|
||||
test_editor_unchanged
|
||||
'
|
||||
|
||||
test_done
|
||||
|
@ -1743,6 +1743,279 @@ test_expect_success 'ORIG_HEAD is updated correctly' '
|
||||
test_cmp_rev ORIG_HEAD test-orig-head@{1}
|
||||
'
|
||||
|
||||
test_expect_success '--update-refs adds label and update-ref commands' '
|
||||
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 &&
|
||||
git commit --allow-empty --fixup=third &&
|
||||
git branch -f is-not-reordered &&
|
||||
git commit --allow-empty --fixup=HEAD~4 &&
|
||||
git branch -f shared-tip &&
|
||||
(
|
||||
set_cat_todo_editor &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
pick $(git log -1 --format=%h J) J
|
||||
fixup $(git log -1 --format=%h update-refs) fixup! J # empty
|
||||
update-ref refs/heads/second
|
||||
update-ref refs/heads/first
|
||||
pick $(git log -1 --format=%h K) K
|
||||
pick $(git log -1 --format=%h L) L
|
||||
fixup $(git log -1 --format=%h is-not-reordered) fixup! L # empty
|
||||
update-ref refs/heads/third
|
||||
pick $(git log -1 --format=%h M) M
|
||||
update-ref refs/heads/no-conflict-branch
|
||||
update-ref refs/heads/is-not-reordered
|
||||
update-ref refs/heads/shared-tip
|
||||
EOF
|
||||
|
||||
test_must_fail git rebase -i --autosquash --update-refs primary >todo &&
|
||||
test_cmp expect todo &&
|
||||
|
||||
test_must_fail git -c rebase.autosquash=true \
|
||||
-c rebase.updaterefs=true \
|
||||
rebase -i primary >todo &&
|
||||
|
||||
test_cmp expect todo
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '--update-refs adds commands with --rebase-merges' '
|
||||
git checkout -b update-refs-with-merge 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 &&
|
||||
git merge -m merge branch2 &&
|
||||
git branch -f merge-branch &&
|
||||
git commit --fixup=third --allow-empty &&
|
||||
(
|
||||
set_cat_todo_editor &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
label onto
|
||||
reset onto
|
||||
pick $(git log -1 --format=%h branch2~1) F
|
||||
pick $(git log -1 --format=%h branch2) I
|
||||
update-ref refs/heads/branch2
|
||||
label merge
|
||||
reset onto
|
||||
pick $(git log -1 --format=%h refs/heads/second) J
|
||||
update-ref refs/heads/second
|
||||
update-ref refs/heads/first
|
||||
pick $(git log -1 --format=%h refs/heads/third~1) K
|
||||
pick $(git log -1 --format=%h refs/heads/third) L
|
||||
fixup $(git log -1 --format=%h update-refs-with-merge) fixup! L # empty
|
||||
update-ref refs/heads/third
|
||||
pick $(git log -1 --format=%h HEAD~2) M
|
||||
update-ref refs/heads/no-conflict-branch
|
||||
merge -C $(git log -1 --format=%h HEAD~1) merge # merge
|
||||
update-ref refs/heads/merge-branch
|
||||
EOF
|
||||
|
||||
test_must_fail git rebase -i --autosquash \
|
||||
--rebase-merges=rebase-cousins \
|
||||
--update-refs primary >todo &&
|
||||
|
||||
test_cmp expect todo &&
|
||||
|
||||
test_must_fail git -c rebase.autosquash=true \
|
||||
-c rebase.updaterefs=true \
|
||||
rebase -i \
|
||||
--rebase-merges=rebase-cousins \
|
||||
primary >todo &&
|
||||
|
||||
test_cmp expect todo
|
||||
)
|
||||
'
|
||||
|
||||
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 2>err &&
|
||||
|
||||
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 &&
|
||||
|
||||
cat >expect <<-\EOF &&
|
||||
Successfully rebased and updated refs/heads/update-refs.
|
||||
Updated the following refs with --update-refs:
|
||||
refs/heads/first
|
||||
refs/heads/no-conflict-branch
|
||||
refs/heads/second
|
||||
refs/heads/third
|
||||
EOF
|
||||
|
||||
# Clear "Rebasing (X/Y)" progress lines and drop leading tabs.
|
||||
sed -e "s/Rebasing.*Successfully/Successfully/g" -e "s/^\t//g" \
|
||||
<err >err.trimmed &&
|
||||
test_cmp expect err.trimmed
|
||||
'
|
||||
|
||||
test_expect_success 'respect user edits to update-ref steps' '
|
||||
git checkout -B update-refs-break 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 &&
|
||||
git branch -f unseen base &&
|
||||
|
||||
# First, we will add breaks to the expected todo file
|
||||
cat >fake-todo-1 <<-EOF &&
|
||||
pick $(git rev-parse HEAD~3)
|
||||
break
|
||||
update-ref refs/heads/second
|
||||
update-ref refs/heads/first
|
||||
|
||||
pick $(git rev-parse HEAD~2)
|
||||
pick $(git rev-parse HEAD~1)
|
||||
update-ref refs/heads/third
|
||||
|
||||
pick $(git rev-parse HEAD)
|
||||
update-ref refs/heads/no-conflict-branch
|
||||
EOF
|
||||
|
||||
# Second, we will drop some update-refs commands (and move one)
|
||||
cat >fake-todo-2 <<-EOF &&
|
||||
update-ref refs/heads/second
|
||||
|
||||
pick $(git rev-parse HEAD~2)
|
||||
update-ref refs/heads/third
|
||||
pick $(git rev-parse HEAD~1)
|
||||
break
|
||||
|
||||
pick $(git rev-parse HEAD)
|
||||
EOF
|
||||
|
||||
# Third, we will:
|
||||
# * insert a new one (new-branch),
|
||||
# * re-add an old one (first), and
|
||||
# * add a second instance of a previously-stored one (second)
|
||||
cat >fake-todo-3 <<-EOF &&
|
||||
update-ref refs/heads/unseen
|
||||
update-ref refs/heads/new-branch
|
||||
pick $(git rev-parse HEAD)
|
||||
update-ref refs/heads/first
|
||||
update-ref refs/heads/second
|
||||
EOF
|
||||
|
||||
(
|
||||
set_replace_editor fake-todo-1 &&
|
||||
git rebase -i --update-refs primary &&
|
||||
|
||||
# These branches are currently locked.
|
||||
for b in first second third no-conflict-branch
|
||||
do
|
||||
test_must_fail git branch -f $b base || return 1
|
||||
done &&
|
||||
|
||||
set_replace_editor fake-todo-2 &&
|
||||
git rebase --edit-todo &&
|
||||
|
||||
# These branches are currently locked.
|
||||
for b in second third
|
||||
do
|
||||
test_must_fail git branch -f $b base || return 1
|
||||
done &&
|
||||
|
||||
# These branches are currently unlocked for checkout.
|
||||
for b in first no-conflict-branch
|
||||
do
|
||||
git worktree add wt-$b $b &&
|
||||
git worktree remove wt-$b || return 1
|
||||
done &&
|
||||
|
||||
git rebase --continue &&
|
||||
|
||||
set_replace_editor fake-todo-3 &&
|
||||
git rebase --edit-todo &&
|
||||
|
||||
# These branches are currently locked.
|
||||
for b in second third first unseen
|
||||
do
|
||||
test_must_fail git branch -f $b base || return 1
|
||||
done &&
|
||||
|
||||
# These branches are currently unlocked for checkout.
|
||||
for b in no-conflict-branch
|
||||
do
|
||||
git worktree add wt-$b $b &&
|
||||
git worktree remove wt-$b || return 1
|
||||
done &&
|
||||
|
||||
git rebase --continue
|
||||
) &&
|
||||
|
||||
test_cmp_rev HEAD~2 refs/heads/third &&
|
||||
test_cmp_rev HEAD~1 refs/heads/unseen &&
|
||||
test_cmp_rev HEAD~1 refs/heads/new-branch &&
|
||||
test_cmp_rev HEAD refs/heads/first &&
|
||||
test_cmp_rev HEAD refs/heads/second &&
|
||||
test_cmp_rev HEAD refs/heads/no-conflict-branch
|
||||
'
|
||||
|
||||
test_expect_success '--update-refs: check failed ref update' '
|
||||
git checkout -B update-refs-error no-conflict-branch &&
|
||||
git branch -f base HEAD~4 &&
|
||||
git branch -f first HEAD~3 &&
|
||||
git branch -f second HEAD~2 &&
|
||||
git branch -f third HEAD~1 &&
|
||||
|
||||
cat >fake-todo <<-EOF &&
|
||||
pick $(git rev-parse HEAD~3)
|
||||
break
|
||||
update-ref refs/heads/first
|
||||
|
||||
pick $(git rev-parse HEAD~2)
|
||||
update-ref refs/heads/second
|
||||
|
||||
pick $(git rev-parse HEAD~1)
|
||||
update-ref refs/heads/third
|
||||
|
||||
pick $(git rev-parse HEAD)
|
||||
update-ref refs/heads/no-conflict-branch
|
||||
EOF
|
||||
|
||||
(
|
||||
set_replace_editor fake-todo &&
|
||||
git rebase -i --update-refs base
|
||||
) &&
|
||||
|
||||
# At this point, the values of first, second, and third are
|
||||
# recorded in the update-refs file. We will force-update the
|
||||
# "second" ref, but "git branch -f" will not work because of
|
||||
# the lock in the update-refs file.
|
||||
git rev-parse third >.git/refs/heads/second &&
|
||||
|
||||
test_must_fail git rebase --continue 2>err &&
|
||||
grep "update_ref failed for ref '\''refs/heads/second'\''" err &&
|
||||
|
||||
cat >expect <<-\EOF &&
|
||||
Updated the following refs with --update-refs:
|
||||
refs/heads/first
|
||||
refs/heads/no-conflict-branch
|
||||
refs/heads/third
|
||||
Failed to update the following refs with --update-refs:
|
||||
refs/heads/second
|
||||
EOF
|
||||
|
||||
# Clear "Rebasing (X/Y)" progress lines and drop leading tabs.
|
||||
tail -n 6 err >err.last &&
|
||||
sed -e "s/Rebasing.*Successfully/Successfully/g" -e "s/^\t//g" \
|
||||
<err.last >err.trimmed &&
|
||||
test_cmp expect err.trimmed
|
||||
'
|
||||
|
||||
# This must be the last test in this file
|
||||
test_expect_success '$EDITOR and friends are unchanged' '
|
||||
test_editor_unchanged
|
||||
|
Loading…
Reference in New Issue
Block a user