Skip to content

Commit 39f7331

Browse files
committed
Merge branch 'pk/rebase-in-c-3-acts'
Rewrite "git rebase" in C. * pk/rebase-in-c-3-acts: builtin rebase: stop if `git am` is in progress builtin rebase: actions require a rebase in progress builtin rebase: support --edit-todo and --show-current-patch builtin rebase: support --quit builtin rebase: support --abort builtin rebase: support --skip builtin rebase: support --continue
2 parents e0720a3 + 0eabf4b commit 39f7331

File tree

3 files changed

+201
-4
lines changed

3 files changed

+201
-4
lines changed

builtin/rebase.c

Lines changed: 189 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "wt-status.h"
2323
#include "revision.h"
2424
#include "commit-reach.h"
25+
#include "rerere.h"
2526

2627
static char const * const builtin_rebase_usage[] = {
2728
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
@@ -92,6 +93,7 @@ struct rebase_options {
9293
REBASE_INTERACTIVE_EXPLICIT = 1<<4,
9394
} flags;
9495
struct strbuf git_am_opt;
96+
const char *action;
9597
};
9698

9799
static int is_interactive(struct rebase_options *opts)
@@ -116,6 +118,62 @@ static const char *state_dir_path(const char *filename, struct rebase_options *o
116118
return path.buf;
117119
}
118120

121+
/* Read one file, then strip line endings */
122+
static int read_one(const char *path, struct strbuf *buf)
123+
{
124+
if (strbuf_read_file(buf, path, 0) < 0)
125+
return error_errno(_("could not read '%s'"), path);
126+
strbuf_trim_trailing_newline(buf);
127+
return 0;
128+
}
129+
130+
/* Initialize the rebase options from the state directory. */
131+
static int read_basic_state(struct rebase_options *opts)
132+
{
133+
struct strbuf head_name = STRBUF_INIT;
134+
struct strbuf buf = STRBUF_INIT;
135+
struct object_id oid;
136+
137+
if (read_one(state_dir_path("head-name", opts), &head_name) ||
138+
read_one(state_dir_path("onto", opts), &buf))
139+
return -1;
140+
opts->head_name = starts_with(head_name.buf, "refs/") ?
141+
xstrdup(head_name.buf) : NULL;
142+
strbuf_release(&head_name);
143+
if (get_oid(buf.buf, &oid))
144+
return error(_("could not get 'onto': '%s'"), buf.buf);
145+
opts->onto = lookup_commit_or_die(&oid, buf.buf);
146+
147+
/*
148+
* We always write to orig-head, but interactive rebase used to write to
149+
* head. Fall back to reading from head to cover for the case that the
150+
* user upgraded git with an ongoing interactive rebase.
151+
*/
152+
strbuf_reset(&buf);
153+
if (file_exists(state_dir_path("orig-head", opts))) {
154+
if (read_one(state_dir_path("orig-head", opts), &buf))
155+
return -1;
156+
} else if (read_one(state_dir_path("head", opts), &buf))
157+
return -1;
158+
if (get_oid(buf.buf, &opts->orig_head))
159+
return error(_("invalid orig-head: '%s'"), buf.buf);
160+
161+
strbuf_reset(&buf);
162+
if (read_one(state_dir_path("quiet", opts), &buf))
163+
return -1;
164+
if (buf.len)
165+
opts->flags &= ~REBASE_NO_QUIET;
166+
else
167+
opts->flags |= REBASE_NO_QUIET;
168+
169+
if (file_exists(state_dir_path("verbose", opts)))
170+
opts->flags |= REBASE_VERBOSE;
171+
172+
strbuf_release(&buf);
173+
174+
return 0;
175+
}
176+
119177
static int finish_rebase(struct rebase_options *opts)
120178
{
121179
struct strbuf dir = STRBUF_INIT;
@@ -169,12 +227,13 @@ static int run_specific_rebase(struct rebase_options *opts)
169227
add_var(&script_snippet, "state_dir", opts->state_dir);
170228

171229
add_var(&script_snippet, "upstream_name", opts->upstream_name);
172-
add_var(&script_snippet, "upstream",
173-
oid_to_hex(&opts->upstream->object.oid));
230+
add_var(&script_snippet, "upstream", opts->upstream ?
231+
oid_to_hex(&opts->upstream->object.oid) : NULL);
174232
add_var(&script_snippet, "head_name",
175233
opts->head_name ? opts->head_name : "detached HEAD");
176234
add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head));
177-
add_var(&script_snippet, "onto", oid_to_hex(&opts->onto->object.oid));
235+
add_var(&script_snippet, "onto", opts->onto ?
236+
oid_to_hex(&opts->onto->object.oid) : NULL);
178237
add_var(&script_snippet, "onto_name", opts->onto_name);
179238
add_var(&script_snippet, "revisions", opts->revisions);
180239
add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
@@ -190,6 +249,7 @@ static int run_specific_rebase(struct rebase_options *opts)
190249
opts->flags & REBASE_FORCE ? "t" : "");
191250
if (opts->switch_to)
192251
add_var(&script_snippet, "switch_to", opts->switch_to);
252+
add_var(&script_snippet, "action", opts->action ? opts->action : "");
193253

194254
switch (opts->type) {
195255
case REBASE_AM:
@@ -401,12 +461,21 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
401461
.git_am_opt = STRBUF_INIT,
402462
};
403463
const char *branch_name;
404-
int ret, flags, in_progress = 0;
464+
int ret, flags, total_argc, in_progress = 0;
405465
int ok_to_skip_pre_rebase = 0;
406466
struct strbuf msg = STRBUF_INIT;
407467
struct strbuf revisions = STRBUF_INIT;
408468
struct strbuf buf = STRBUF_INIT;
409469
struct object_id merge_base;
470+
enum {
471+
NO_ACTION,
472+
ACTION_CONTINUE,
473+
ACTION_SKIP,
474+
ACTION_ABORT,
475+
ACTION_QUIT,
476+
ACTION_EDIT_TODO,
477+
ACTION_SHOW_CURRENT_PATCH,
478+
} action = NO_ACTION;
410479
struct option builtin_rebase_options[] = {
411480
OPT_STRING(0, "onto", &options.onto_name,
412481
N_("revision"),
@@ -428,6 +497,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
428497
OPT_BIT(0, "no-ff", &options.flags,
429498
N_("cherry-pick all commits, even if unchanged"),
430499
REBASE_FORCE),
500+
OPT_CMDMODE(0, "continue", &action, N_("continue"),
501+
ACTION_CONTINUE),
502+
OPT_CMDMODE(0, "skip", &action,
503+
N_("skip current patch and continue"), ACTION_SKIP),
504+
OPT_CMDMODE(0, "abort", &action,
505+
N_("abort and check out the original branch"),
506+
ACTION_ABORT),
507+
OPT_CMDMODE(0, "quit", &action,
508+
N_("abort but keep HEAD where it is"), ACTION_QUIT),
509+
OPT_CMDMODE(0, "edit-todo", &action, N_("edit the todo list "
510+
"during an interactive rebase"), ACTION_EDIT_TODO),
511+
OPT_CMDMODE(0, "show-current-patch", &action,
512+
N_("show the patch file being applied or merged"),
513+
ACTION_SHOW_CURRENT_PATCH),
431514
OPT_END(),
432515
};
433516

@@ -457,6 +540,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
457540

458541
git_config(rebase_config, &options);
459542

543+
strbuf_reset(&buf);
544+
strbuf_addf(&buf, "%s/applying", apply_dir());
545+
if(file_exists(buf.buf))
546+
die(_("It looks like 'git am' is in progress. Cannot rebase."));
547+
460548
if (is_directory(apply_dir())) {
461549
options.type = REBASE_AM;
462550
options.state_dir = apply_dir();
@@ -481,14 +569,110 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
481569
if (options.type != REBASE_UNSPECIFIED)
482570
in_progress = 1;
483571

572+
total_argc = argc;
484573
argc = parse_options(argc, argv, prefix,
485574
builtin_rebase_options,
486575
builtin_rebase_usage, 0);
487576

577+
if (action != NO_ACTION && total_argc != 2) {
578+
usage_with_options(builtin_rebase_usage,
579+
builtin_rebase_options);
580+
}
581+
488582
if (argc > 2)
489583
usage_with_options(builtin_rebase_usage,
490584
builtin_rebase_options);
491585

586+
if (action != NO_ACTION && !in_progress)
587+
die(_("No rebase in progress?"));
588+
589+
if (action == ACTION_EDIT_TODO && !is_interactive(&options))
590+
die(_("The --edit-todo action can only be used during "
591+
"interactive rebase."));
592+
593+
switch (action) {
594+
case ACTION_CONTINUE: {
595+
struct object_id head;
596+
struct lock_file lock_file = LOCK_INIT;
597+
int fd;
598+
599+
options.action = "continue";
600+
601+
/* Sanity check */
602+
if (get_oid("HEAD", &head))
603+
die(_("Cannot read HEAD"));
604+
605+
fd = hold_locked_index(&lock_file, 0);
606+
if (read_index(the_repository->index) < 0)
607+
die(_("could not read index"));
608+
refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL,
609+
NULL);
610+
if (0 <= fd)
611+
update_index_if_able(the_repository->index,
612+
&lock_file);
613+
rollback_lock_file(&lock_file);
614+
615+
if (has_unstaged_changes(1)) {
616+
puts(_("You must edit all merge conflicts and then\n"
617+
"mark them as resolved using git add"));
618+
exit(1);
619+
}
620+
if (read_basic_state(&options))
621+
exit(1);
622+
goto run_rebase;
623+
}
624+
case ACTION_SKIP: {
625+
struct string_list merge_rr = STRING_LIST_INIT_DUP;
626+
627+
options.action = "skip";
628+
629+
rerere_clear(&merge_rr);
630+
string_list_clear(&merge_rr, 1);
631+
632+
if (reset_head(NULL, "reset", NULL, 0) < 0)
633+
die(_("could not discard worktree changes"));
634+
if (read_basic_state(&options))
635+
exit(1);
636+
goto run_rebase;
637+
}
638+
case ACTION_ABORT: {
639+
struct string_list merge_rr = STRING_LIST_INIT_DUP;
640+
options.action = "abort";
641+
642+
rerere_clear(&merge_rr);
643+
string_list_clear(&merge_rr, 1);
644+
645+
if (read_basic_state(&options))
646+
exit(1);
647+
if (reset_head(&options.orig_head, "reset",
648+
options.head_name, 0) < 0)
649+
die(_("could not move back to %s"),
650+
oid_to_hex(&options.orig_head));
651+
ret = finish_rebase(&options);
652+
goto cleanup;
653+
}
654+
case ACTION_QUIT: {
655+
strbuf_reset(&buf);
656+
strbuf_addstr(&buf, options.state_dir);
657+
ret = !!remove_dir_recursively(&buf, 0);
658+
if (ret)
659+
die(_("could not remove '%s'"), options.state_dir);
660+
goto cleanup;
661+
}
662+
case ACTION_EDIT_TODO:
663+
options.action = "edit-todo";
664+
options.dont_finish_rebase = 1;
665+
goto run_rebase;
666+
case ACTION_SHOW_CURRENT_PATCH:
667+
options.action = "show-current-patch";
668+
options.dont_finish_rebase = 1;
669+
goto run_rebase;
670+
case NO_ACTION:
671+
break;
672+
default:
673+
BUG("action: %d", action);
674+
}
675+
492676
/* Make sure no rebase is in progress */
493677
if (in_progress) {
494678
const char *last_slash = strrchr(options.state_dir, '/');
@@ -720,6 +904,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
720904

721905
options.revisions = revisions.buf;
722906

907+
run_rebase:
723908
ret = !!run_specific_rebase(&options);
724909

725910
cleanup:

strbuf.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,15 @@ void strbuf_trim_trailing_dir_sep(struct strbuf *sb)
120120
sb->buf[sb->len] = '\0';
121121
}
122122

123+
void strbuf_trim_trailing_newline(struct strbuf *sb)
124+
{
125+
if (sb->len > 0 && sb->buf[sb->len - 1] == '\n') {
126+
if (--sb->len > 0 && sb->buf[sb->len - 1] == '\r')
127+
--sb->len;
128+
sb->buf[sb->len] = '\0';
129+
}
130+
}
131+
123132
void strbuf_ltrim(struct strbuf *sb)
124133
{
125134
char *b = sb->buf;

strbuf.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ void strbuf_ltrim(struct strbuf *sb);
190190
/* Strip trailing directory separators */
191191
void strbuf_trim_trailing_dir_sep(struct strbuf *sb);
192192

193+
/* Strip trailing LF or CR/LF */
194+
void strbuf_trim_trailing_newline(struct strbuf *sb);
195+
193196
/**
194197
* Replace the contents of the strbuf with a reencoded form. Returns -1
195198
* on error, 0 on success.

0 commit comments

Comments
 (0)