sequencer (rebase -i): implement the 'edit' command

This patch is a straight-forward reimplementation of the `edit` operation of the interactive rebase command. Well, not *quite* straight-forward: when stopping, the `edit` command wants to write the `patch` file (which is not only the patch, but includes the commit message and author information). To that end, this patch requires the earlier work that taught the log-tree machinery to respect the `file` setting of rev_info->diffopt to write to a file stream different than stdout. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Jan 2, 2017 at 16:26 UTC 56dc3ab04bf0f7bb8c73ebbba47469bdf8be8ac4
1 file changed +114 -3
sequencer.c
+114 -3
@@ -17,6 +17,7 @@
17 #include "argv-array.h"
18 #include "quote.h"
19 #include "trailer.h"
20 +#include "log-tree.h"
21
22 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
23
@@ -44,6 +45,20 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
45 * being rebased.
46 */
47 static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script")
48 +/*
49 + * When an "edit" rebase command is being processed, the SHA1 of the
50 + * commit to be edited is recorded in this file. When "git rebase
51 + * --continue" is executed, if there are any staged changes then they
52 + * will be amended to the HEAD commit, but only provided the HEAD
53 + * commit is still the commit to be edited. When any other rebase
54 + * command is processed, this file is deleted.
55 + */
56 +static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend")
57 +/*
58 + * When we stop at a given patch via the "edit" command, this file contains
59 + * the abbreviated commit name of the corresponding patch.
60 + */
61 +static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")
62 /*
63 * The following files are written by git-rebase just after parsing the
64 * command-line (and are only consumed, not modified, by the sequencer).
@@ -616,6 +631,7 @@ enum todo_command {
631 /* commands that handle commits */
632 TODO_PICK = 0,
633 TODO_REVERT,
634 + TODO_EDIT,
635 /* commands that do nothing but are counted for reporting progress */
636 TODO_NOOP
637 };
@@ -623,6 +639,7 @@ enum todo_command {
639 static const char *todo_command_strings[] = {
640 "pick",
641 "revert",
642 + "edit",
643 "noop"
644 };
645
@@ -1302,9 +1319,87 @@ static int save_opts(struct replay_opts *opts)
1319 return res;
1320 }
1321
1322 +static int make_patch(struct commit *commit, struct replay_opts *opts)
1323 +{
1324 + struct strbuf buf = STRBUF_INIT;
1325 + struct rev_info log_tree_opt;
1326 + const char *subject, *p;
1327 + int res = 0;
1328 +
1329 + p = short_commit_name(commit);
1330 + if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0)
1331 + return -1;
1332 +
1333 + strbuf_addf(&buf, "%s/patch", get_dir(opts));
1334 + memset(&log_tree_opt, 0, sizeof(log_tree_opt));
1335 + init_revisions(&log_tree_opt, NULL);
1336 + log_tree_opt.abbrev = 0;
1337 + log_tree_opt.diff = 1;
1338 + log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;
1339 + log_tree_opt.disable_stdin = 1;
1340 + log_tree_opt.no_commit_id = 1;
1341 + log_tree_opt.diffopt.file = fopen(buf.buf, "w");
1342 + log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER;
1343 + if (!log_tree_opt.diffopt.file)
1344 + res |= error_errno(_("could not open '%s'"), buf.buf);
1345 + else {
1346 + res |= log_tree_commit(&log_tree_opt, commit);
1347 + fclose(log_tree_opt.diffopt.file);
1348 + }
1349 + strbuf_reset(&buf);
1350 +
1351 + strbuf_addf(&buf, "%s/message", get_dir(opts));
1352 + if (!file_exists(buf.buf)) {
1353 + const char *commit_buffer = get_commit_buffer(commit, NULL);
1354 + find_commit_subject(commit_buffer, &subject);
1355 + res |= write_message(subject, strlen(subject), buf.buf, 1);
1356 + unuse_commit_buffer(commit, commit_buffer);
1357 + }
1358 + strbuf_release(&buf);
1359 +
1360 + return res;
1361 +}
1362 +
1363 +static int intend_to_amend(void)
1364 +{
1365 + unsigned char head[20];
1366 + char *p;
1367 +
1368 + if (get_sha1("HEAD", head))
1369 + return error(_("cannot read HEAD"));
1370 +
1371 + p = sha1_to_hex(head);
1372 + return write_message(p, strlen(p), rebase_path_amend(), 1);
1373 +}
1374 +
1375 +static int error_with_patch(struct commit *commit,
1376 + const char *subject, int subject_len,
1377 + struct replay_opts *opts, int exit_code, int to_amend)
1378 +{
1379 + if (make_patch(commit, opts))
1380 + return -1;
1381 +
1382 + if (to_amend) {
1383 + if (intend_to_amend())
1384 + return -1;
1385 +
1386 + fprintf(stderr, "You can amend the commit now, with\n"
1387 + "\n"
1388 + " git commit --amend %s\n"
1389 + "\n"
1390 + "Once you are satisfied with your changes, run\n"
1391 + "\n"
1392 + " git rebase --continue\n", gpg_sign_opt_quoted(opts));
1393 + } else if (exit_code)
1394 + fprintf(stderr, "Could not apply %s... %.*s\n",
1395 + short_commit_name(commit), subject_len, subject);
1396 +
1397 + return exit_code;
1398 +}
1399 +
1400 static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
1401 {
1307 - int res;
1402 + int res = 0;
1403
1404 setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
1405 if (opts->allow_ff)
@@ -1317,10 +1412,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
1412 struct todo_item *item = todo_list->items + todo_list->current;
1413 if (save_todo(todo_list, opts))
1414 return -1;
1320 - if (item->command <= TODO_REVERT)
1415 + if (item->command <= TODO_EDIT) {
1416 res = do_pick_commit(item->command, item->commit,
1417 opts);
1323 - else if (!is_noop(item->command))
1418 + if (item->command == TODO_EDIT) {
1419 + struct commit *commit = item->commit;
1420 + if (!res)
1421 + warning(_("stopped at %s... %.*s"),
1422 + short_commit_name(commit),
1423 + item->arg_len, item->arg);
1424 + return error_with_patch(commit,
1425 + item->arg, item->arg_len, opts, res,
1426 + !res);
1427 + }
1428 + } else if (!is_noop(item->command))
1429 return error(_("unknown command %d"), item->command);
1430
1431 todo_list->current++;
@@ -1328,6 +1433,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
1433 return res;
1434 }
1435
1436 + if (is_rebase_i(opts)) {
1437 + /* Stopped in the middle, as planned? */
1438 + if (todo_list->current < todo_list->nr)
1439 + return 0;
1440 + }
1441 +
1442 /*
1443 * Sequence of picks finished successfully; cleanup by
1444 * removing the .git/sequencer directory