Skip to content

Commit 9055e40

Browse files
dschogitster
authored andcommitted
sequencer: introduce new commands to reset the revision
In the upcoming commits, we will teach the sequencer to rebase merges. This will be done in a very different way from the unfortunate design of `git rebase --preserve-merges` (which does not allow for reordering commits, or changing the branch topology). The main idea is to introduce new todo list commands, to support labeling the current revision with a given name, resetting the current revision to a previous state, and merging labeled revisions. This idea was developed in Git for Windows' Git garden shears (that are used to maintain Git for Windows' "thicket of branches" on top of upstream Git), and this patch is part of the effort to make it available to a wider audience, as well as to make the entire process more robust (by implementing it in a safe and portable language rather than a Unix shell script). This commit implements the commands to label, and to reset to, given revisions. The syntax is: label <name> reset <name> Internally, the `label <name>` command creates the ref `refs/rewritten/<name>`. This makes it possible to work with the labeled revisions interactively, or in a scripted fashion (e.g. via the todo list command `exec`). These temporary refs are removed upon sequencer_remove_state(), so that even a `git rebase --abort` cleans them up. We disallow '#' as label because that character will be used as separator in the upcoming `merge` command. Later in this patch series, we will mark the `refs/rewritten/` refs as worktree-local, to allow for interactive rebases to be run in parallel in worktrees linked to the same repository. As typos happen, a failed `label` or `reset` command will be rescheduled immediately. As the previous code to reschedule a command is embedded deeply in the pick/fixup/squash code path, we simply duplicate the few lines. This will allow us to extend the new code path easily for the upcoming `merge` command. Signed-off-by: Johannes Schindelin <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent f431d73 commit 9055e40

File tree

2 files changed

+208
-7
lines changed

2 files changed

+208
-7
lines changed

git-rebase--interactive.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ s, squash <commit> = use commit, but meld into previous commit
162162
f, fixup <commit> = like \"squash\", but discard this commit's log message
163163
x, exec <commit> = run command (the rest of the line) using shell
164164
d, drop <commit> = remove commit
165+
l, label <label> = label current HEAD with a name
166+
t, reset <label> = reset HEAD to a label
165167
166168
These lines can be re-ordered; they are executed from top to bottom.
167169
" | git stripspace --comment-lines >>"$todo"

sequencer.c

Lines changed: 206 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include "hashmap.h"
2424
#include "notes-utils.h"
2525
#include "sigchain.h"
26+
#include "unpack-trees.h"
27+
#include "worktree.h"
2628

2729
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
2830

@@ -120,6 +122,13 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")
120122
static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list")
121123
static GIT_PATH_FUNC(rebase_path_rewritten_pending,
122124
"rebase-merge/rewritten-pending")
125+
126+
/*
127+
* The path of the file listing refs that need to be deleted after the rebase
128+
* finishes. This is used by the `label` command to record the need for cleanup.
129+
*/
130+
static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
131+
123132
/*
124133
* The following files are written by git-rebase just after parsing the
125134
* command-line (and are only consumed, not modified, by the sequencer).
@@ -245,18 +254,34 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)
245254

246255
int sequencer_remove_state(struct replay_opts *opts)
247256
{
248-
struct strbuf dir = STRBUF_INIT;
257+
struct strbuf buf = STRBUF_INIT;
249258
int i;
250259

260+
if (is_rebase_i(opts) &&
261+
strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {
262+
char *p = buf.buf;
263+
while (*p) {
264+
char *eol = strchr(p, '\n');
265+
if (eol)
266+
*eol = '\0';
267+
if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0)
268+
warning(_("could not delete '%s'"), p);
269+
if (!eol)
270+
break;
271+
p = eol + 1;
272+
}
273+
}
274+
251275
free(opts->gpg_sign);
252276
free(opts->strategy);
253277
for (i = 0; i < opts->xopts_nr; i++)
254278
free(opts->xopts[i]);
255279
free(opts->xopts);
256280

257-
strbuf_addstr(&dir, get_dir(opts));
258-
remove_dir_recursively(&dir, 0);
259-
strbuf_release(&dir);
281+
strbuf_reset(&buf);
282+
strbuf_addstr(&buf, get_dir(opts));
283+
remove_dir_recursively(&buf, 0);
284+
strbuf_release(&buf);
260285

261286
return 0;
262287
}
@@ -1280,6 +1305,8 @@ enum todo_command {
12801305
TODO_SQUASH,
12811306
/* commands that do something else than handling a single commit */
12821307
TODO_EXEC,
1308+
TODO_LABEL,
1309+
TODO_RESET,
12831310
/* commands that do nothing but are counted for reporting progress */
12841311
TODO_NOOP,
12851312
TODO_DROP,
@@ -1298,6 +1325,8 @@ static struct {
12981325
{ 'f', "fixup" },
12991326
{ 's', "squash" },
13001327
{ 'x', "exec" },
1328+
{ 'l', "label" },
1329+
{ 't', "reset" },
13011330
{ 0, "noop" },
13021331
{ 'd', "drop" },
13031332
{ 0, NULL }
@@ -1803,7 +1832,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
18031832
return error(_("missing arguments for %s"),
18041833
command_to_string(item->command));
18051834

1806-
if (item->command == TODO_EXEC) {
1835+
if (item->command == TODO_EXEC || item->command == TODO_LABEL ||
1836+
item->command == TODO_RESET) {
18071837
item->commit = NULL;
18081838
item->arg = bol;
18091839
item->arg_len = (int)(eol - bol);
@@ -2471,6 +2501,159 @@ static int do_exec(const char *command_line)
24712501
return status;
24722502
}
24732503

2504+
static int safe_append(const char *filename, const char *fmt, ...)
2505+
{
2506+
va_list ap;
2507+
struct lock_file lock = LOCK_INIT;
2508+
int fd = hold_lock_file_for_update(&lock, filename,
2509+
LOCK_REPORT_ON_ERROR);
2510+
struct strbuf buf = STRBUF_INIT;
2511+
2512+
if (fd < 0)
2513+
return -1;
2514+
2515+
if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {
2516+
error_errno(_("could not read '%s'"), filename);
2517+
rollback_lock_file(&lock);
2518+
return -1;
2519+
}
2520+
strbuf_complete(&buf, '\n');
2521+
va_start(ap, fmt);
2522+
strbuf_vaddf(&buf, fmt, ap);
2523+
va_end(ap);
2524+
2525+
if (write_in_full(fd, buf.buf, buf.len) < 0) {
2526+
error_errno(_("could not write to '%s'"), filename);
2527+
strbuf_release(&buf);
2528+
rollback_lock_file(&lock);
2529+
return -1;
2530+
}
2531+
if (commit_lock_file(&lock) < 0) {
2532+
strbuf_release(&buf);
2533+
rollback_lock_file(&lock);
2534+
return error(_("failed to finalize '%s'"), filename);
2535+
}
2536+
2537+
strbuf_release(&buf);
2538+
return 0;
2539+
}
2540+
2541+
static int do_label(const char *name, int len)
2542+
{
2543+
struct ref_store *refs = get_main_ref_store();
2544+
struct ref_transaction *transaction;
2545+
struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;
2546+
struct strbuf msg = STRBUF_INIT;
2547+
int ret = 0;
2548+
struct object_id head_oid;
2549+
2550+
if (len == 1 && *name == '#')
2551+
return error("Illegal label name: '%.*s'", len, name);
2552+
2553+
strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
2554+
strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name);
2555+
2556+
transaction = ref_store_transaction_begin(refs, &err);
2557+
if (!transaction) {
2558+
error("%s", err.buf);
2559+
ret = -1;
2560+
} else if (get_oid("HEAD", &head_oid)) {
2561+
error(_("could not read HEAD"));
2562+
ret = -1;
2563+
} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
2564+
NULL, 0, msg.buf, &err) < 0 ||
2565+
ref_transaction_commit(transaction, &err)) {
2566+
error("%s", err.buf);
2567+
ret = -1;
2568+
}
2569+
ref_transaction_free(transaction);
2570+
strbuf_release(&err);
2571+
strbuf_release(&msg);
2572+
2573+
if (!ret)
2574+
ret = safe_append(rebase_path_refs_to_delete(),
2575+
"%s\n", ref_name.buf);
2576+
strbuf_release(&ref_name);
2577+
2578+
return ret;
2579+
}
2580+
2581+
static const char *reflog_message(struct replay_opts *opts,
2582+
const char *sub_action, const char *fmt, ...);
2583+
2584+
static int do_reset(const char *name, int len, struct replay_opts *opts)
2585+
{
2586+
struct strbuf ref_name = STRBUF_INIT;
2587+
struct object_id oid;
2588+
struct lock_file lock = LOCK_INIT;
2589+
struct tree_desc desc;
2590+
struct tree *tree;
2591+
struct unpack_trees_options unpack_tree_opts;
2592+
int ret = 0, i;
2593+
2594+
if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)
2595+
return -1;
2596+
2597+
/* Determine the length of the label */
2598+
for (i = 0; i < len; i++)
2599+
if (isspace(name[i]))
2600+
len = i;
2601+
2602+
strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
2603+
if (get_oid(ref_name.buf, &oid) &&
2604+
get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
2605+
error(_("could not read '%s'"), ref_name.buf);
2606+
rollback_lock_file(&lock);
2607+
strbuf_release(&ref_name);
2608+
return -1;
2609+
}
2610+
2611+
memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
2612+
setup_unpack_trees_porcelain(&unpack_tree_opts, "reset");
2613+
unpack_tree_opts.head_idx = 1;
2614+
unpack_tree_opts.src_index = &the_index;
2615+
unpack_tree_opts.dst_index = &the_index;
2616+
unpack_tree_opts.fn = oneway_merge;
2617+
unpack_tree_opts.merge = 1;
2618+
unpack_tree_opts.update = 1;
2619+
2620+
if (read_cache_unmerged()) {
2621+
rollback_lock_file(&lock);
2622+
strbuf_release(&ref_name);
2623+
return error_resolve_conflict(_(action_name(opts)));
2624+
}
2625+
2626+
if (!fill_tree_descriptor(&desc, &oid)) {
2627+
error(_("failed to find tree of %s"), oid_to_hex(&oid));
2628+
rollback_lock_file(&lock);
2629+
free((void *)desc.buffer);
2630+
strbuf_release(&ref_name);
2631+
return -1;
2632+
}
2633+
2634+
if (unpack_trees(1, &desc, &unpack_tree_opts)) {
2635+
rollback_lock_file(&lock);
2636+
free((void *)desc.buffer);
2637+
strbuf_release(&ref_name);
2638+
return -1;
2639+
}
2640+
2641+
tree = parse_tree_indirect(&oid);
2642+
prime_cache_tree(&the_index, tree);
2643+
2644+
if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)
2645+
ret = error(_("could not write index"));
2646+
free((void *)desc.buffer);
2647+
2648+
if (!ret)
2649+
ret = update_ref(reflog_message(opts, "reset", "'%.*s'",
2650+
len, name), "HEAD", &oid,
2651+
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
2652+
2653+
strbuf_release(&ref_name);
2654+
return ret;
2655+
}
2656+
24742657
static int is_final_fixup(struct todo_list *todo_list)
24752658
{
24762659
int i = todo_list->current;
@@ -2574,7 +2757,7 @@ N_("Could not execute the todo command\n"
25742757

25752758
static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
25762759
{
2577-
int res = 0;
2760+
int res = 0, reschedule = 0;
25782761

25792762
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
25802763
if (opts->allow_ff)
@@ -2645,7 +2828,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
26452828
intend_to_amend();
26462829
return error_failed_squash(item->commit, opts,
26472830
item->arg_len, item->arg);
2648-
} else if (res && is_rebase_i(opts))
2831+
} else if (res && is_rebase_i(opts) && item->commit)
26492832
return res | error_with_patch(item->commit,
26502833
item->arg, item->arg_len, opts, res,
26512834
item->command == TODO_REWORD);
@@ -2671,9 +2854,25 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
26712854
/* `current` will be incremented below */
26722855
todo_list->current = -1;
26732856
}
2857+
} else if (item->command == TODO_LABEL) {
2858+
if ((res = do_label(item->arg, item->arg_len)))
2859+
reschedule = 1;
2860+
} else if (item->command == TODO_RESET) {
2861+
if ((res = do_reset(item->arg, item->arg_len, opts)))
2862+
reschedule = 1;
26742863
} else if (!is_noop(item->command))
26752864
return error(_("unknown command %d"), item->command);
26762865

2866+
if (reschedule) {
2867+
advise(_(rescheduled_advice),
2868+
get_item_line_length(todo_list,
2869+
todo_list->current),
2870+
get_item_line(todo_list, todo_list->current));
2871+
todo_list->current--;
2872+
if (save_todo(todo_list, opts))
2873+
return -1;
2874+
}
2875+
26772876
todo_list->current++;
26782877
if (res)
26792878
return res;

0 commit comments

Comments
 (0)