mirror of
https://github.com/git/git.git
synced 2024-11-27 12:03:55 +08:00
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:
commit
0da2ba4880
@ -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
|
||||
|
@ -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
|
||||
-------
|
||||
|
52
Documentation/rebase-config.txt
Normal file
52
Documentation/rebase-config.txt
Normal 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.
|
@ -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);
|
||||
}
|
||||
|
@ -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##* }
|
||||
|
122
sequencer.c
122
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) {
|
||||
todo_list_release(&todo_list);
|
||||
return error(_("unable to open '%s' for writing"), todo_file);
|
||||
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;
|
||||
}
|
||||
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 ?
|
||||
/* 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(_("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);
|
||||
int len;
|
||||
|
||||
p += strspn(p, " \t"); /* left-trim command */
|
||||
len = strcspn(p, " \t"); /* length of command */
|
||||
strbuf_addf(&buf, " %s", oid);
|
||||
}
|
||||
/* add all the rest */
|
||||
strbuf_addf(&buf, " %.*s\n", item->arg_len, item->arg);
|
||||
}
|
||||
|
||||
fprintf(out, "%.*s %s %.*s\n",
|
||||
len, p, id, 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 {
|
||||
|
10
sequencer.h
10
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);
|
||||
|
@ -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 &&
|
||||
|
Loading…
Reference in New Issue
Block a user