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.