Skip to content

Commit 3b53182

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. Note that this needs a little change in the original code to perform a reschedule: there is no commit from which to generate a patch here (and we will simply fall through to the regular `return res`). We keep that code path, though, because we will use it for the upcoming `merge` command, too. Signed-off-by: Johannes Schindelin <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 7c4e80e commit 3b53182

File tree

2 files changed

+196
-7
lines changed

2 files changed

+196
-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: 194 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).
@@ -244,18 +253,33 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)
244253

245254
int sequencer_remove_state(struct replay_opts *opts)
246255
{
247-
struct strbuf dir = STRBUF_INIT;
256+
struct strbuf buf = STRBUF_INIT;
248257
int i;
249258

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

256-
strbuf_addstr(&dir, get_dir(opts));
257-
remove_dir_recursively(&dir, 0);
258-
strbuf_release(&dir);
279+
strbuf_reset(&buf);
280+
strbuf_addstr(&buf, get_dir(opts));
281+
remove_dir_recursively(&buf, 0);
282+
strbuf_release(&buf);
259283

260284
return 0;
261285
}
@@ -1279,6 +1303,8 @@ enum todo_command {
12791303
TODO_SQUASH,
12801304
/* commands that do something else than handling a single commit */
12811305
TODO_EXEC,
1306+
TODO_LABEL,
1307+
TODO_RESET,
12821308
/* commands that do nothing but are counted for reporting progress */
12831309
TODO_NOOP,
12841310
TODO_DROP,
@@ -1297,6 +1323,8 @@ static struct {
12971323
{ 'f', "fixup" },
12981324
{ 's', "squash" },
12991325
{ 'x', "exec" },
1326+
{ 'l', "label" },
1327+
{ 't', "reset" },
13001328
{ 0, "noop" },
13011329
{ 'd', "drop" },
13021330
{ 0, NULL }
@@ -1802,7 +1830,8 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
18021830
return error(_("missing arguments for %s"),
18031831
command_to_string(item->command));
18041832

1805-
if (item->command == TODO_EXEC) {
1833+
if (item->command == TODO_EXEC || item->command == TODO_LABEL ||
1834+
item->command == TODO_RESET) {
18061835
item->commit = NULL;
18071836
item->arg = bol;
18081837
item->arg_len = (int)(eol - bol);
@@ -2465,6 +2494,158 @@ static int do_exec(const char *command_line)
24652494
return status;
24662495
}
24672496

2497+
static int safe_append(const char *filename, const char *fmt, ...)
2498+
{
2499+
va_list ap;
2500+
struct lock_file lock = LOCK_INIT;
2501+
int fd = hold_lock_file_for_update(&lock, filename,
2502+
LOCK_REPORT_ON_ERROR);
2503+
struct strbuf buf = STRBUF_INIT;
2504+
2505+
if (fd < 0)
2506+
return -1;
2507+
2508+
if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) {
2509+
error_errno(_("could not read '%s'"), filename);
2510+
rollback_lock_file(&lock);
2511+
return -1;
2512+
}
2513+
strbuf_complete(&buf, '\n');
2514+
va_start(ap, fmt);
2515+
strbuf_vaddf(&buf, fmt, ap);
2516+
va_end(ap);
2517+
2518+
if (write_in_full(fd, buf.buf, buf.len) < 0) {
2519+
error_errno(_("could not write to '%s'"), filename);
2520+
strbuf_release(&buf);
2521+
rollback_lock_file(&lock);
2522+
return -1;
2523+
}
2524+
if (commit_lock_file(&lock) < 0) {
2525+
strbuf_release(&buf);
2526+
rollback_lock_file(&lock);
2527+
return error(_("failed to finalize '%s'"), filename);
2528+
}
2529+
2530+
strbuf_release(&buf);
2531+
return 0;
2532+
}
2533+
2534+
static int do_label(const char *name, int len)
2535+
{
2536+
struct ref_store *refs = get_main_ref_store();
2537+
struct ref_transaction *transaction;
2538+
struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT;
2539+
struct strbuf msg = STRBUF_INIT;
2540+
int ret = 0;
2541+
struct object_id head_oid;
2542+
2543+
if (len == 1 && *name == '#')
2544+
return error("Illegal label name: '%.*s'", len, name);
2545+
2546+
strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
2547+
strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name);
2548+
2549+
transaction = ref_store_transaction_begin(refs, &err);
2550+
if (!transaction) {
2551+
error("%s", err.buf);
2552+
ret = -1;
2553+
} else if (get_oid("HEAD", &head_oid)) {
2554+
error(_("could not read HEAD"));
2555+
ret = -1;
2556+
} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
2557+
NULL, 0, msg.buf, &err) < 0 ||
2558+
ref_transaction_commit(transaction, &err)) {
2559+
error("%s", err.buf);
2560+
ret = -1;
2561+
}
2562+
ref_transaction_free(transaction);
2563+
strbuf_release(&err);
2564+
strbuf_release(&msg);
2565+
2566+
if (!ret)
2567+
ret = safe_append(rebase_path_refs_to_delete(),
2568+
"%s\n", ref_name.buf);
2569+
strbuf_release(&ref_name);
2570+
2571+
return ret;
2572+
}
2573+
2574+
static const char *reflog_message(struct replay_opts *opts,
2575+
const char *sub_action, const char *fmt, ...);
2576+
2577+
static int do_reset(const char *name, int len, struct replay_opts *opts)
2578+
{
2579+
struct strbuf ref_name = STRBUF_INIT;
2580+
struct object_id oid;
2581+
struct lock_file lock = LOCK_INIT;
2582+
struct tree_desc desc;
2583+
struct tree *tree;
2584+
struct unpack_trees_options unpack_tree_opts;
2585+
int ret = 0, i;
2586+
2587+
if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)
2588+
return -1;
2589+
2590+
/* Determine the length of the label */
2591+
for (i = 0; i < len; i++)
2592+
if (isspace(name[i]))
2593+
len = i;
2594+
2595+
strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
2596+
if (get_oid(ref_name.buf, &oid) &&
2597+
get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
2598+
error(_("could not read '%s'"), ref_name.buf);
2599+
rollback_lock_file(&lock);
2600+
strbuf_release(&ref_name);
2601+
return -1;
2602+
}
2603+
2604+
memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
2605+
unpack_tree_opts.head_idx = 1;
2606+
unpack_tree_opts.src_index = &the_index;
2607+
unpack_tree_opts.dst_index = &the_index;
2608+
unpack_tree_opts.fn = oneway_merge;
2609+
unpack_tree_opts.merge = 1;
2610+
unpack_tree_opts.update = 1;
2611+
2612+
if (read_cache_unmerged()) {
2613+
rollback_lock_file(&lock);
2614+
strbuf_release(&ref_name);
2615+
return error_resolve_conflict(_(action_name(opts)));
2616+
}
2617+
2618+
if (!fill_tree_descriptor(&desc, &oid)) {
2619+
error(_("failed to find tree of %s"), oid_to_hex(&oid));
2620+
rollback_lock_file(&lock);
2621+
free((void *)desc.buffer);
2622+
strbuf_release(&ref_name);
2623+
return -1;
2624+
}
2625+
2626+
if (unpack_trees(1, &desc, &unpack_tree_opts)) {
2627+
rollback_lock_file(&lock);
2628+
free((void *)desc.buffer);
2629+
strbuf_release(&ref_name);
2630+
return -1;
2631+
}
2632+
2633+
tree = parse_tree_indirect(&oid);
2634+
prime_cache_tree(&the_index, tree);
2635+
2636+
if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0)
2637+
ret = error(_("could not write index"));
2638+
free((void *)desc.buffer);
2639+
2640+
if (!ret)
2641+
ret = update_ref(reflog_message(opts, "reset", "'%.*s'",
2642+
len, name), "HEAD", &oid,
2643+
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
2644+
2645+
strbuf_release(&ref_name);
2646+
return ret;
2647+
}
2648+
24682649
static int is_final_fixup(struct todo_list *todo_list)
24692650
{
24702651
int i = todo_list->current;
@@ -2610,7 +2791,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
26102791
res = do_pick_commit(item->command, item->commit,
26112792
opts, is_final_fixup(todo_list));
26122793
if (is_rebase_i(opts) && res < 0) {
2613-
/* Reschedule */
2794+
reschedule:
26142795
advise(_(rescheduled_advice),
26152796
get_item_line_length(todo_list,
26162797
todo_list->current),
@@ -2639,7 +2820,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
26392820
intend_to_amend();
26402821
return error_failed_squash(item->commit, opts,
26412822
item->arg_len, item->arg);
2642-
} else if (res && is_rebase_i(opts))
2823+
} else if (res && is_rebase_i(opts) && item->commit)
26432824
return res | error_with_patch(item->commit,
26442825
item->arg, item->arg_len, opts, res,
26452826
item->command == TODO_REWORD);
@@ -2665,6 +2846,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
26652846
/* `current` will be incremented below */
26662847
todo_list->current = -1;
26672848
}
2849+
} else if (item->command == TODO_LABEL) {
2850+
if ((res = do_label(item->arg, item->arg_len)))
2851+
goto reschedule;
2852+
} else if (item->command == TODO_RESET) {
2853+
if ((res = do_reset(item->arg, item->arg_len, opts)))
2854+
goto reschedule;
26682855
} else if (!is_noop(item->command))
26692856
return error(_("unknown command %d"), item->command);
26702857

0 commit comments

Comments
 (0)