aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/config.txt31
-rw-r--r--Documentation/git-rebase.txt19
-rw-r--r--Documentation/rebase-config.txt52
-rw-r--r--builtin/rebase--helper.c29
-rw-r--r--git-rebase--interactive.sh23
-rw-r--r--sequencer.c126
-rw-r--r--sequencer.h10
-rwxr-xr-xt/t3404-rebase-interactive.sh22
8 files changed, 186 insertions, 126 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt
index b18c0f97f..7814fb904 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -2736,36 +2736,7 @@ push.recurseSubmodules::
is retained. You may override this configuration at time of push by
specifying '--recurse-submodules=check|on-demand|no'.
-rebase.stat::
- Whether to show a diffstat of what changed upstream since the last
- rebase. False by default.
-
-rebase.autoSquash::
- If set to true enable `--autosquash` option by default.
-
-rebase.autoStash::
- When set to true, automatically create a temporary stash entry
- before the operation begins, and apply it after the operation
- ends. This means that you can run rebase on a dirty worktree.
- However, use with care: the final stash application after a
- successful rebase might result in non-trivial conflicts.
- Defaults to false.
-
-rebase.missingCommitsCheck::
- If set to "warn", git rebase -i will print a warning if some
- commits are removed (e.g. a line was deleted), however the
- rebase will still proceed. If set to "error", it will print
- the previous warning and stop the rebase, 'git rebase
- --edit-todo' can then be used to correct the error. If set to
- "ignore", no checking is done.
- To drop a commit without warning or error, use the `drop`
- command in the todo-list.
- Defaults to "ignore".
-
-rebase.instructionFormat::
- A format string, as specified in linkgit:git-log[1], to be used for
- the instruction list during an interactive rebase. The format will automatically
- have the long commit hash prepended to the format.
+include::rebase-config.txt[]
receive.advertiseAtomic::
By default, git-receive-pack will advertise the atomic push
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 3cedfb0fd..8a861c1e0 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -203,24 +203,7 @@ Alternatively, you can undo the 'git rebase' with
CONFIGURATION
-------------
-rebase.stat::
- Whether to show a diffstat of what changed upstream since the last
- rebase. False by default.
-
-rebase.autoSquash::
- If set to true enable `--autosquash` option by default.
-
-rebase.autoStash::
- If set to true enable `--autostash` option by default.
-
-rebase.missingCommitsCheck::
- If set to "warn", print warnings about removed commits in
- interactive mode. If set to "error", print the warnings and
- stop the rebase. If set to "ignore", no checking is
- done. "ignore" by default.
-
-rebase.instructionFormat::
- Custom commit list format to use during an `--interactive` rebase.
+include::rebase-config.txt[]
OPTIONS
-------
diff --git a/Documentation/rebase-config.txt b/Documentation/rebase-config.txt
new file mode 100644
index 000000000..42e1ba757
--- /dev/null
+++ b/Documentation/rebase-config.txt
@@ -0,0 +1,52 @@
+rebase.stat::
+ Whether to show a diffstat of what changed upstream since the last
+ rebase. False by default.
+
+rebase.autoSquash::
+ If set to true enable `--autosquash` option by default.
+
+rebase.autoStash::
+ When set to true, automatically create a temporary stash entry
+ before the operation begins, and apply it after the operation
+ ends. This means that you can run rebase on a dirty worktree.
+ However, use with care: the final stash application after a
+ successful rebase might result in non-trivial conflicts.
+ This option can be overridden by the `--no-autostash` and
+ `--autostash` options of linkgit:git-rebase[1].
+ Defaults to false.
+
+rebase.missingCommitsCheck::
+ If set to "warn", git rebase -i will print a warning if some
+ commits are removed (e.g. a line was deleted), however the
+ rebase will still proceed. If set to "error", it will print
+ the previous warning and stop the rebase, 'git rebase
+ --edit-todo' can then be used to correct the error. If set to
+ "ignore", no checking is done.
+ To drop a commit without warning or error, use the `drop`
+ command in the todo list.
+ Defaults to "ignore".
+
+rebase.instructionFormat::
+ A format string, as specified in linkgit:git-log[1], to be used for the
+ todo list during an interactive rebase. The format will
+ automatically have the long commit hash prepended to the format.
+
+rebase.abbreviateCommands::
+ If set to true, `git rebase` will use abbreviated command names in the
+ todo list resulting in something like this:
++
+-------------------------------------------
+ p deadbee The oneline of the commit
+ p fa1afe1 The oneline of the next commit
+ ...
+-------------------------------------------
++
+instead of:
++
+-------------------------------------------
+ pick deadbee The oneline of the commit
+ pick fa1afe1 The oneline of the next commit
+ ...
+-------------------------------------------
++
+Defaults to false.
diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c
index f8519363a..7daee544b 100644
--- a/builtin/rebase--helper.c
+++ b/builtin/rebase--helper.c
@@ -12,10 +12,12 @@ static const char * const builtin_rebase_helper_usage[] = {
int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
{
struct replay_opts opts = REPLAY_OPTS_INIT;
- int keep_empty = 0;
+ unsigned flags = 0, keep_empty = 0;
+ int abbreviate_commands = 0;
enum {
- CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S,
- CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH
+ CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
+ CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
+ ADD_EXEC
} command = 0;
struct option options[] = {
OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
@@ -27,19 +29,22 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
OPT_CMDMODE(0, "make-script", &command,
N_("make rebase script"), MAKE_SCRIPT),
OPT_CMDMODE(0, "shorten-ids", &command,
- N_("shorten SHA-1s in the todo list"), SHORTEN_SHA1S),
+ N_("shorten commit ids in the todo list"), SHORTEN_OIDS),
OPT_CMDMODE(0, "expand-ids", &command,
- N_("expand SHA-1s in the todo list"), EXPAND_SHA1S),
+ N_("expand commit ids in the todo list"), EXPAND_OIDS),
OPT_CMDMODE(0, "check-todo-list", &command,
N_("check the todo list"), CHECK_TODO_LIST),
OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
OPT_CMDMODE(0, "rearrange-squash", &command,
N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
+ OPT_CMDMODE(0, "add-exec-commands", &command,
+ N_("insert exec commands in todo list"), ADD_EXEC),
OPT_END()
};
git_config(git_default_config, NULL);
+ git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
opts.action = REPLAY_INTERACTIVE_REBASE;
opts.allow_ff = 1;
@@ -48,21 +53,25 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, NULL, options,
builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0);
+ flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
+ flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
+ flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
+
if (command == CONTINUE && argc == 1)
return !!sequencer_continue(&opts);
if (command == ABORT && argc == 1)
return !!sequencer_remove_state(&opts);
if (command == MAKE_SCRIPT && argc > 1)
- return !!sequencer_make_script(keep_empty, stdout, argc, argv);
- if (command == SHORTEN_SHA1S && argc == 1)
- return !!transform_todo_ids(1);
- if (command == EXPAND_SHA1S && argc == 1)
- return !!transform_todo_ids(0);
+ return !!sequencer_make_script(stdout, argc, argv, flags);
+ if ((command == SHORTEN_OIDS || command == EXPAND_OIDS) && argc == 1)
+ return !!transform_todos(flags);
if (command == CHECK_TODO_LIST && argc == 1)
return !!check_todo_list();
if (command == SKIP_UNNECESSARY_PICKS && argc == 1)
return !!skip_unnecessary_picks();
if (command == REARRANGE_SQUASH && argc == 1)
return !!rearrange_squash();
+ if (command == ADD_EXEC && argc == 2)
+ return !!sequencer_add_exec_commands(argv[1]);
usage_with_options(builtin_rebase_helper_usage, options);
}
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 437815669..e3f5a0abf 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -722,27 +722,6 @@ collapse_todo_ids() {
git rebase--helper --shorten-ids
}
-# Add commands after a pick or after a squash/fixup series
-# in the todo list.
-add_exec_commands () {
- {
- first=t
- while read -r insn rest
- do
- case $insn in
- pick)
- test -n "$first" ||
- printf "%s" "$cmd"
- ;;
- esac
- printf "%s %s\n" "$insn" "$rest"
- first=
- done
- printf "%s" "$cmd"
- } <"$1" >"$1.new" &&
- mv "$1.new" "$1"
-}
-
# Switch to the branch in $into and notify it in the reflog
checkout_onto () {
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
@@ -982,7 +961,7 @@ fi
test -s "$todo" || echo noop >> "$todo"
test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
-test -n "$cmd" && add_exec_commands "$todo"
+test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd"
todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
todocount=${todocount##* }
diff --git a/sequencer.c b/sequencer.c
index c8cb20c1e..894d12ad7 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -797,6 +797,13 @@ static const char *command_to_string(const enum todo_command command)
die("Unknown command: %d", command);
}
+static char command_to_char(const enum todo_command command)
+{
+ if (command < TODO_COMMENT && todo_command_info[command].c)
+ return todo_command_info[command].c;
+ return comment_line_char;
+}
+
static int is_noop(const enum todo_command command)
{
return TODO_NOOP <= command;
@@ -1270,6 +1277,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
bol += padding;
if (item->command == TODO_EXEC) {
+ item->commit = NULL;
item->arg = bol;
item->arg_len = (int)(eol - bol);
return 0;
@@ -2445,14 +2453,16 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
strbuf_release(&sob);
}
-int sequencer_make_script(int keep_empty, FILE *out,
- int argc, const char **argv)
+int sequencer_make_script(FILE *out, int argc, const char **argv,
+ unsigned flags)
{
char *format = NULL;
struct pretty_print_context pp = {0};
struct strbuf buf = STRBUF_INIT;
struct rev_info revs;
struct commit *commit;
+ int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
+ const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
init_revisions(&revs, NULL);
revs.verbose_header = 1;
@@ -2485,7 +2495,8 @@ int sequencer_make_script(int keep_empty, FILE *out,
strbuf_reset(&buf);
if (!keep_empty && is_original_commit_empty(commit))
strbuf_addf(&buf, "%c ", comment_line_char);
- strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid));
+ strbuf_addf(&buf, "%s %s ", insn,
+ oid_to_hex(&commit->object.oid));
pretty_print_commit(&pp, commit, &buf);
strbuf_addch(&buf, '\n');
fputs(buf.buf, out);
@@ -2494,61 +2505,90 @@ int sequencer_make_script(int keep_empty, FILE *out,
return 0;
}
-
-int transform_todo_ids(int shorten_ids)
+/*
+ * Add commands after pick and (series of) squash/fixup commands
+ * in the todo list.
+ */
+int sequencer_add_exec_commands(const char *commands)
{
const char *todo_file = rebase_path_todo();
struct todo_list todo_list = TODO_LIST_INIT;
- int fd, res, i;
- FILE *out;
+ struct todo_item *item;
+ struct strbuf *buf = &todo_list.buf;
+ size_t offset = 0, commands_len = strlen(commands);
+ int i, first;
- strbuf_reset(&todo_list.buf);
- fd = open(todo_file, O_RDONLY);
- if (fd < 0)
- return error_errno(_("could not open '%s'"), todo_file);
- if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
- close(fd);
+ if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
return error(_("could not read '%s'."), todo_file);
- }
- close(fd);
- res = parse_insn_buffer(todo_list.buf.buf, &todo_list);
- if (res) {
+ if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) {
todo_list_release(&todo_list);
return error(_("unusable todo list: '%s'"), todo_file);
}
- out = fopen(todo_file, "w");
- if (!out) {
+ first = 1;
+ /* insert <commands> before every pick except the first one */
+ for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) {
+ if (item->command == TODO_PICK && !first) {
+ strbuf_insert(buf, item->offset_in_buf + offset,
+ commands, commands_len);
+ offset += commands_len;
+ }
+ first = 0;
+ }
+
+ /* append final <commands> */
+ strbuf_add(buf, commands, commands_len);
+
+ i = write_message(buf->buf, buf->len, todo_file, 0);
+ todo_list_release(&todo_list);
+ return i;
+}
+
+int transform_todos(unsigned flags)
+{
+ const char *todo_file = rebase_path_todo();
+ struct todo_list todo_list = TODO_LIST_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ struct todo_item *item;
+ int i;
+
+ if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
+ return error(_("could not read '%s'."), todo_file);
+
+ if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) {
todo_list_release(&todo_list);
- return error(_("unable to open '%s' for writing"), todo_file);
+ return error(_("unusable todo list: '%s'"), todo_file);
}
- for (i = 0; i < todo_list.nr; i++) {
- struct todo_item *item = todo_list.items + i;
- int bol = item->offset_in_buf;
- const char *p = todo_list.buf.buf + bol;
- int eol = i + 1 < todo_list.nr ?
- todo_list.items[i + 1].offset_in_buf :
- todo_list.buf.len;
-
- if (item->command >= TODO_EXEC && item->command != TODO_DROP)
- fwrite(p, eol - bol, 1, out);
- else {
- const char *id = shorten_ids ?
- short_commit_name(item->commit) :
- oid_to_hex(&item->commit->object.oid);
- int len;
-
- p += strspn(p, " \t"); /* left-trim command */
- len = strcspn(p, " \t"); /* length of command */
-
- fprintf(out, "%.*s %s %.*s\n",
- len, p, id, item->arg_len, item->arg);
+
+ for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) {
+ /* if the item is not a command write it and continue */
+ if (item->command >= TODO_COMMENT) {
+ strbuf_addf(&buf, "%.*s\n", item->arg_len, item->arg);
+ continue;
}
+
+ /* add command to the buffer */
+ if (flags & TODO_LIST_ABBREVIATE_CMDS)
+ strbuf_addch(&buf, command_to_char(item->command));
+ else
+ strbuf_addstr(&buf, command_to_string(item->command));
+
+ /* add commit id */
+ if (item->commit) {
+ const char *oid = flags & TODO_LIST_SHORTEN_IDS ?
+ short_commit_name(item->commit) :
+ oid_to_hex(&item->commit->object.oid);
+
+ strbuf_addf(&buf, " %s", oid);
+ }
+ /* add all the rest */
+ strbuf_addf(&buf, " %.*s\n", item->arg_len, item->arg);
}
- fclose(out);
+
+ i = write_message(buf.buf, buf.len, todo_file, 0);
todo_list_release(&todo_list);
- return 0;
+ return i;
}
enum check_level {
diff --git a/sequencer.h b/sequencer.h
index 6f3d3df82..81f6d7d39 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -45,10 +45,14 @@ int sequencer_continue(struct replay_opts *opts);
int sequencer_rollback(struct replay_opts *opts);
int sequencer_remove_state(struct replay_opts *opts);
-int sequencer_make_script(int keep_empty, FILE *out,
- int argc, const char **argv);
+#define TODO_LIST_KEEP_EMPTY (1U << 0)
+#define TODO_LIST_SHORTEN_IDS (1U << 1)
+#define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
+int sequencer_make_script(FILE *out, int argc, const char **argv,
+ unsigned flags);
-int transform_todo_ids(int shorten_ids);
+int sequencer_add_exec_commands(const char *command);
+int transform_todos(unsigned flags);
int check_todo_list(void);
int skip_unnecessary_picks(void);
int rearrange_squash(void);
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 6a82d1ed8..481a35009 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1260,6 +1260,28 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '
test B = $(git cat-file commit HEAD^ | sed -ne \$p)
'
+test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '
+ rebase_setup_and_clean abbrevcmd &&
+ test_commit "first" file1.txt "first line" first &&
+ test_commit "second" file1.txt "another line" second &&
+ test_commit "fixup! first" file2.txt "first line again" first_fixup &&
+ test_commit "squash! second" file1.txt "another line here" second_squash &&
+ cat >expected <<-EOF &&
+ p $(git rev-list --abbrev-commit -1 first) first
+ f $(git rev-list --abbrev-commit -1 first_fixup) fixup! first
+ x git show HEAD
+ p $(git rev-list --abbrev-commit -1 second) second
+ s $(git rev-list --abbrev-commit -1 second_squash) squash! second
+ x git show HEAD
+ EOF
+ git checkout abbrevcmd &&
+ set_cat_todo_editor &&
+ test_config rebase.abbreviateCommands true &&
+ test_must_fail git rebase -i --exec "git show HEAD" \
+ --autosquash master >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'static check of bad command' '
rebase_setup_and_clean bad-cmd &&
set_fake_editor &&