builtin rebase: support --continue

This commit adds the option `--continue` which is used to resume rebase after merge conflicts. The code tries to stay as close to the equivalent shell scripts found in `git-legacy-rebase.sh` as possible. When continuing a rebase, the state variables are read from state_dir. Some of the state variables are not actually stored there, such as `upstream`. The shell script version simply does not set them, but for consistency, we unset them in the builtin version. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Pratik Karki committed Aug 8, 2018 at 20:51 UTC f95736288a3a8d0168af3fc05dc4251edf0d0b47
3 files changed +123 -4
builtin/rebase.c
+111 -4
@@ -91,6 +91,7 @@ struct rebase_options {
91 REBASE_INTERACTIVE_EXPLICIT = 1<<4,
92 } flags;
93 struct strbuf git_am_opt;
94 + const char *action;
95 };
96
97 static int is_interactive(struct rebase_options *opts)
@@ -115,6 +116,62 @@ static const char *state_dir_path(const char *filename, struct rebase_options *o
116 return path.buf;
117 }
118
119 +/* Read one file, then strip line endings */
120 +static int read_one(const char *path, struct strbuf *buf)
121 +{
122 + if (strbuf_read_file(buf, path, 0) < 0)
123 + return error_errno(_("could not read '%s'"), path);
124 + strbuf_trim_trailing_newline(buf);
125 + return 0;
126 +}
127 +
128 +/* Initialize the rebase options from the state directory. */
129 +static int read_basic_state(struct rebase_options *opts)
130 +{
131 + struct strbuf head_name = STRBUF_INIT;
132 + struct strbuf buf = STRBUF_INIT;
133 + struct object_id oid;
134 +
135 + if (read_one(state_dir_path("head-name", opts), &head_name) ||
136 + read_one(state_dir_path("onto", opts), &buf))
137 + return -1;
138 + opts->head_name = starts_with(head_name.buf, "refs/") ?
139 + xstrdup(head_name.buf) : NULL;
140 + strbuf_release(&head_name);
141 + if (get_oid(buf.buf, &oid))
142 + return error(_("could not get 'onto': '%s'"), buf.buf);
143 + opts->onto = lookup_commit_or_die(&oid, buf.buf);
144 +
145 + /*
146 + * We always write to orig-head, but interactive rebase used to write to
147 + * head. Fall back to reading from head to cover for the case that the
148 + * user upgraded git with an ongoing interactive rebase.
149 + */
150 + strbuf_reset(&buf);
151 + if (file_exists(state_dir_path("orig-head", opts))) {
152 + if (read_one(state_dir_path("orig-head", opts), &buf))
153 + return -1;
154 + } else if (read_one(state_dir_path("head", opts), &buf))
155 + return -1;
156 + if (get_oid(buf.buf, &opts->orig_head))
157 + return error(_("invalid orig-head: '%s'"), buf.buf);
158 +
159 + strbuf_reset(&buf);
160 + if (read_one(state_dir_path("quiet", opts), &buf))
161 + return -1;
162 + if (buf.len)
163 + opts->flags &= ~REBASE_NO_QUIET;
164 + else
165 + opts->flags |= REBASE_NO_QUIET;
166 +
167 + if (file_exists(state_dir_path("verbose", opts)))
168 + opts->flags |= REBASE_VERBOSE;
169 +
170 + strbuf_release(&buf);
171 +
172 + return 0;
173 +}
174 +
175 static int finish_rebase(struct rebase_options *opts)
176 {
177 struct strbuf dir = STRBUF_INIT;
@@ -168,12 +225,13 @@ static int run_specific_rebase(struct rebase_options *opts)
225 add_var(&script_snippet, "state_dir", opts->state_dir);
226
227 add_var(&script_snippet, "upstream_name", opts->upstream_name);
171 - add_var(&script_snippet, "upstream",
172 - oid_to_hex(&opts->upstream->object.oid));
228 + add_var(&script_snippet, "upstream", opts->upstream ?
229 + oid_to_hex(&opts->upstream->object.oid) : NULL);
230 add_var(&script_snippet, "head_name",
231 opts->head_name ? opts->head_name : "detached HEAD");
232 add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head));
176 - add_var(&script_snippet, "onto", oid_to_hex(&opts->onto->object.oid));
233 + add_var(&script_snippet, "onto", opts->onto ?
234 + oid_to_hex(&opts->onto->object.oid) : NULL);
235 add_var(&script_snippet, "onto_name", opts->onto_name);
236 add_var(&script_snippet, "revisions", opts->revisions);
237 add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
@@ -189,6 +247,7 @@ static int run_specific_rebase(struct rebase_options *opts)
247 opts->flags & REBASE_FORCE ? "t" : "");
248 if (opts->switch_to)
249 add_var(&script_snippet, "switch_to", opts->switch_to);
250 + add_var(&script_snippet, "action", opts->action ? opts->action : "");
251
252 switch (opts->type) {
253 case REBASE_AM:
@@ -400,12 +459,16 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
459 .git_am_opt = STRBUF_INIT,
460 };
461 const char *branch_name;
403 - int ret, flags, in_progress = 0;
462 + int ret, flags, total_argc, in_progress = 0;
463 int ok_to_skip_pre_rebase = 0;
464 struct strbuf msg = STRBUF_INIT;
465 struct strbuf revisions = STRBUF_INIT;
466 struct strbuf buf = STRBUF_INIT;
467 struct object_id merge_base;
468 + enum {
469 + NO_ACTION,
470 + ACTION_CONTINUE,
471 + } action = NO_ACTION;
472 struct option builtin_rebase_options[] = {
473 OPT_STRING(0, "onto", &options.onto_name,
474 N_("revision"),
@@ -427,6 +490,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
490 OPT_BIT(0, "no-ff", &options.flags,
491 N_("cherry-pick all commits, even if unchanged"),
492 REBASE_FORCE),
493 + OPT_CMDMODE(0, "continue", &action, N_("continue"),
494 + ACTION_CONTINUE),
495 OPT_END(),
496 };
497
@@ -480,14 +545,55 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
545 if (options.type != REBASE_UNSPECIFIED)
546 in_progress = 1;
547
548 + total_argc = argc;
549 argc = parse_options(argc, argv, prefix,
550 builtin_rebase_options,
551 builtin_rebase_usage, 0);
552
553 + if (action != NO_ACTION && total_argc != 2) {
554 + usage_with_options(builtin_rebase_usage,
555 + builtin_rebase_options);
556 + }
557 +
558 if (argc > 2)
559 usage_with_options(builtin_rebase_usage,
560 builtin_rebase_options);
561
562 + switch (action) {
563 + case ACTION_CONTINUE: {
564 + struct object_id head;
565 + struct lock_file lock_file = LOCK_INIT;
566 + int fd;
567 +
568 + options.action = "continue";
569 +
570 + /* Sanity check */
571 + if (get_oid("HEAD", &head))
572 + die(_("Cannot read HEAD"));
573 +
574 + fd = hold_locked_index(&lock_file, 0);
575 + if (read_index(the_repository->index) < 0)
576 + die(_("could not read index"));
577 + refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL,
578 + NULL);
579 + if (0 <= fd)
580 + update_index_if_able(the_repository->index,
581 + &lock_file);
582 + rollback_lock_file(&lock_file);
583 +
584 + if (has_unstaged_changes(1)) {
585 + puts(_("You must edit all merge conflicts and then\n"
586 + "mark them as resolved using git add"));
587 + exit(1);
588 + }
589 + if (read_basic_state(&options))
590 + exit(1);
591 + goto run_rebase;
592 + }
593 + default:
594 + die("TODO");
595 + }
596 +
597 /* Make sure no rebase is in progress */
598 if (in_progress) {
599 const char *last_slash = strrchr(options.state_dir, '/');
@@ -719,6 +825,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
825
826 options.revisions = revisions.buf;
827
828 +run_rebase:
829 ret = !!run_specific_rebase(&options);
830
831 cleanup:
strbuf.c
+9
@@ -120,6 +120,15 @@ void strbuf_trim_trailing_dir_sep(struct strbuf *sb)
120 sb->buf[sb->len] = '\0';
121 }
122
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 +
132 void strbuf_ltrim(struct strbuf *sb)
133 {
134 char *b = sb->buf;
strbuf.h
+3
@@ -190,6 +190,9 @@ extern void strbuf_ltrim(struct strbuf *);
190 /* Strip trailing directory separators */
191 extern void strbuf_trim_trailing_dir_sep(struct strbuf *);
192
193 +/* Strip trailing LF or CR/LF */
194 +extern void strbuf_trim_trailing_newline(struct strbuf *sb);
195 +
196 /**
197 * Replace the contents of the strbuf with a reencoded form. Returns -1
198 * on error, 0 on success.