Merge branch 'lb/rebase-i-short-command-names'

With a configuration variable rebase.abbreviateCommands set,
"git rebase -i" produces the todo list with a single-letter
command names.

* lb/rebase-i-short-command-names:
  sequencer.c: drop 'const' from function return type
  t3404: add test case for abbreviated commands
  rebase -i: learn to abbreviate command names
  rebase -i -x: add exec commands via the rebase--helper
  rebase -i: update functions to use a flags parameter
  rebase -i: replace reference to sha1 with oid
  rebase -i: refactor transform_todo_ids
  rebase -i: set commit to null in exec commands
  Documentation: use preferred name for the 'todo list' script
  Documentation: move rebase.* configs to new file
This commit is contained in:
Junio C Hamano 2017-12-27 11:16:21 -08:00
commit 0da2ba4880
8 changed files with 188 additions and 128 deletions

View file

@ -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

View file

@ -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
-------

View file

@ -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.

View file

@ -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);
}

View file

@ -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##* }

View file

@ -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) {
todo_list_release(&todo_list);
return error(_("unable to open '%s' for writing"), 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);
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;
}
fclose(out);
/* 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 0;
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(_("unusable todo list: '%s'"), todo_file);
}
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);
}
i = write_message(buf.buf, buf.len, todo_file, 0);
todo_list_release(&todo_list);
return i;
}
enum check_level {

View file

@ -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);

View file

@ -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 &&