mirror of
https://github.com/git/git.git
synced 2026-01-17 22:26:32 +00:00
Merge 'builtin-stash-rebase-v3'
To avoid having to play tricks as in earlier rounds, we bit the sour apple and rebased the `builtin-stash-rebase-v3` branch thicket onto the commit starting Git for Windows' merging-rebase. (The merging-rebase pulls in the previous branch thicket via a "fake merge", i.e. a merge commit that does not actually apply any changes from the merged commit history. This has the unfortunate side effect of confusing `merge` into thinking that any branch that was merged into an earlier round does not need to be merged again.) Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -78,6 +78,9 @@
|
||||
/git-init-db
|
||||
/git-interpret-trailers
|
||||
/git-instaweb
|
||||
/git-legacy-rebase
|
||||
/git-legacy-rebase--interactive
|
||||
/git-legacy-stash
|
||||
/git-log
|
||||
/git-ls-files
|
||||
/git-ls-remote
|
||||
@@ -117,7 +120,7 @@
|
||||
/git-read-tree
|
||||
/git-rebase
|
||||
/git-rebase--am
|
||||
/git-rebase--helper
|
||||
/git-rebase--common
|
||||
/git-rebase--interactive
|
||||
/git-rebase--merge
|
||||
/git-rebase--preserve-merges
|
||||
|
||||
@@ -9,7 +9,7 @@ SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git stash' list [<options>]
|
||||
'git stash' show [<stash>]
|
||||
'git stash' show [<options>] [<stash>]
|
||||
'git stash' drop [-q|--quiet] [<stash>]
|
||||
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
|
||||
'git stash' branch <branchname> [<stash>]
|
||||
@@ -106,7 +106,7 @@ stash@{1}: On master: 9cc0589... Add git-stash
|
||||
The command takes options applicable to the 'git log'
|
||||
command to control what is shown and how. See linkgit:git-log[1].
|
||||
|
||||
show [<stash>]::
|
||||
show [<options>] [<stash>]::
|
||||
|
||||
Show the changes recorded in the stash entry as a diff between the
|
||||
stashed contents and the commit back when the stash entry was first
|
||||
|
||||
13
Makefile
13
Makefile
@@ -614,17 +614,18 @@ SCRIPT_SH += git-merge-one-file.sh
|
||||
SCRIPT_SH += git-merge-resolve.sh
|
||||
SCRIPT_SH += git-mergetool.sh
|
||||
SCRIPT_SH += git-quiltimport.sh
|
||||
SCRIPT_SH += git-rebase.sh
|
||||
SCRIPT_SH += git-legacy-rebase.sh
|
||||
SCRIPT_SH += git-legacy-stash.sh
|
||||
SCRIPT_SH += git-remote-testgit.sh
|
||||
SCRIPT_SH += git-request-pull.sh
|
||||
SCRIPT_SH += git-stash.sh
|
||||
SCRIPT_SH += git-submodule.sh
|
||||
SCRIPT_SH += git-web--browse.sh
|
||||
|
||||
SCRIPT_LIB += git-legacy-rebase--interactive
|
||||
SCRIPT_LIB += git-mergetool--lib
|
||||
SCRIPT_LIB += git-parse-remote
|
||||
SCRIPT_LIB += git-rebase--am
|
||||
SCRIPT_LIB += git-rebase--interactive
|
||||
SCRIPT_LIB += git-rebase--common
|
||||
SCRIPT_LIB += git-rebase--preserve-merges
|
||||
SCRIPT_LIB += git-rebase--merge
|
||||
SCRIPT_LIB += git-sh-setup
|
||||
@@ -937,6 +938,7 @@ LIB_OBJS += quote.o
|
||||
LIB_OBJS += range-diff.o
|
||||
LIB_OBJS += reachable.o
|
||||
LIB_OBJS += read-cache.o
|
||||
LIB_OBJS += rebase-interactive.o
|
||||
LIB_OBJS += reflog-walk.o
|
||||
LIB_OBJS += refs.o
|
||||
LIB_OBJS += refs/files-backend.o
|
||||
@@ -1075,7 +1077,8 @@ BUILTIN_OBJS += builtin/pull.o
|
||||
BUILTIN_OBJS += builtin/push.o
|
||||
BUILTIN_OBJS += builtin/range-diff.o
|
||||
BUILTIN_OBJS += builtin/read-tree.o
|
||||
BUILTIN_OBJS += builtin/rebase--helper.o
|
||||
BUILTIN_OBJS += builtin/rebase.o
|
||||
BUILTIN_OBJS += builtin/rebase--interactive.o
|
||||
BUILTIN_OBJS += builtin/receive-pack.o
|
||||
BUILTIN_OBJS += builtin/reflog.o
|
||||
BUILTIN_OBJS += builtin/remote.o
|
||||
@@ -1095,6 +1098,7 @@ BUILTIN_OBJS += builtin/shortlog.o
|
||||
BUILTIN_OBJS += builtin/show-branch.o
|
||||
BUILTIN_OBJS += builtin/show-index.o
|
||||
BUILTIN_OBJS += builtin/show-ref.o
|
||||
BUILTIN_OBJS += builtin/stash.o
|
||||
BUILTIN_OBJS += builtin/stripspace.o
|
||||
BUILTIN_OBJS += builtin/submodule--helper.o
|
||||
BUILTIN_OBJS += builtin/symbolic-ref.o
|
||||
@@ -2414,7 +2418,6 @@ XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \
|
||||
LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H)
|
||||
LOCALIZED_SH = $(SCRIPT_SH)
|
||||
LOCALIZED_SH += git-parse-remote.sh
|
||||
LOCALIZED_SH += git-rebase--interactive.sh
|
||||
LOCALIZED_SH += git-rebase--preserve-merges.sh
|
||||
LOCALIZED_SH += git-sh-setup.sh
|
||||
LOCALIZED_PERL = $(SCRIPT_PERL)
|
||||
|
||||
@@ -203,7 +203,8 @@ extern int cmd_pull(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_push(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_range_diff(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_rebase(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_rebase__interactive(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_reflog(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_remote(int argc, const char **argv, const char *prefix);
|
||||
@@ -223,6 +224,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_show_index(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_status(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_stash(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
|
||||
|
||||
@@ -110,54 +110,12 @@ static int handle_is_ancestor(int argc, const char **argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct rev_collect {
|
||||
struct commit **commit;
|
||||
int nr;
|
||||
int alloc;
|
||||
unsigned int initial : 1;
|
||||
};
|
||||
|
||||
static void add_one_commit(struct object_id *oid, struct rev_collect *revs)
|
||||
{
|
||||
struct commit *commit;
|
||||
|
||||
if (is_null_oid(oid))
|
||||
return;
|
||||
|
||||
commit = lookup_commit(the_repository, oid);
|
||||
if (!commit ||
|
||||
(commit->object.flags & TMP_MARK) ||
|
||||
parse_commit(commit))
|
||||
return;
|
||||
|
||||
ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc);
|
||||
revs->commit[revs->nr++] = commit;
|
||||
commit->object.flags |= TMP_MARK;
|
||||
}
|
||||
|
||||
static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid,
|
||||
const char *ident, timestamp_t timestamp,
|
||||
int tz, const char *message, void *cbdata)
|
||||
{
|
||||
struct rev_collect *revs = cbdata;
|
||||
|
||||
if (revs->initial) {
|
||||
revs->initial = 0;
|
||||
add_one_commit(ooid, revs);
|
||||
}
|
||||
add_one_commit(noid, revs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_fork_point(int argc, const char **argv)
|
||||
{
|
||||
struct object_id oid;
|
||||
char *refname;
|
||||
struct commit *derived, *fork_point;
|
||||
const char *commitname;
|
||||
struct rev_collect revs;
|
||||
struct commit *derived;
|
||||
struct commit_list *bases;
|
||||
int i, ret = 0;
|
||||
|
||||
switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) {
|
||||
case 0:
|
||||
@@ -173,41 +131,14 @@ static int handle_fork_point(int argc, const char **argv)
|
||||
die("Not a valid object name: '%s'", commitname);
|
||||
|
||||
derived = lookup_commit_reference(the_repository, &oid);
|
||||
memset(&revs, 0, sizeof(revs));
|
||||
revs.initial = 1;
|
||||
for_each_reflog_ent(refname, collect_one_reflog_ent, &revs);
|
||||
|
||||
if (!revs.nr && !get_oid(refname, &oid))
|
||||
add_one_commit(&oid, &revs);
|
||||
fork_point = get_fork_point(refname, derived);
|
||||
|
||||
for (i = 0; i < revs.nr; i++)
|
||||
revs.commit[i]->object.flags &= ~TMP_MARK;
|
||||
if (!fork_point)
|
||||
return 1;
|
||||
|
||||
bases = get_merge_bases_many_dirty(derived, revs.nr, revs.commit);
|
||||
|
||||
/*
|
||||
* There should be one and only one merge base, when we found
|
||||
* a common ancestor among reflog entries.
|
||||
*/
|
||||
if (!bases || bases->next) {
|
||||
ret = 1;
|
||||
goto cleanup_return;
|
||||
}
|
||||
|
||||
/* And the found one must be one of the reflog entries */
|
||||
for (i = 0; i < revs.nr; i++)
|
||||
if (&bases->item->object == &revs.commit[i]->object)
|
||||
break; /* found */
|
||||
if (revs.nr <= i) {
|
||||
ret = 1; /* not found */
|
||||
goto cleanup_return;
|
||||
}
|
||||
|
||||
printf("%s\n", oid_to_hex(&bases->item->object.oid));
|
||||
|
||||
cleanup_return:
|
||||
free_commit_list(bases);
|
||||
return ret;
|
||||
printf("%s\n", oid_to_hex(&fork_point->object.oid));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_merge_base(int argc, const char **argv, const char *prefix)
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
#include "builtin.h"
|
||||
#include "cache.h"
|
||||
#include "config.h"
|
||||
#include "parse-options.h"
|
||||
#include "sequencer.h"
|
||||
|
||||
static const char * const builtin_rebase_helper_usage[] = {
|
||||
N_("git rebase--helper [<options>]"),
|
||||
NULL
|
||||
};
|
||||
|
||||
int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct replay_opts opts = REPLAY_OPTS_INIT;
|
||||
unsigned flags = 0, keep_empty = 0, rebase_merges = 0;
|
||||
int abbreviate_commands = 0, rebase_cousins = -1;
|
||||
enum {
|
||||
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")),
|
||||
OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")),
|
||||
OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
|
||||
N_("allow commits with empty messages")),
|
||||
OPT_BOOL(0, "rebase-merges", &rebase_merges, N_("rebase merge commits")),
|
||||
OPT_BOOL(0, "rebase-cousins", &rebase_cousins,
|
||||
N_("keep original branch points of cousins")),
|
||||
OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
|
||||
CONTINUE),
|
||||
OPT_CMDMODE(0, "abort", &command, N_("abort rebase"),
|
||||
ABORT),
|
||||
OPT_CMDMODE(0, "make-script", &command,
|
||||
N_("make rebase script"), MAKE_SCRIPT),
|
||||
OPT_CMDMODE(0, "shorten-ids", &command,
|
||||
N_("shorten commit ids in the todo list"), SHORTEN_OIDS),
|
||||
OPT_CMDMODE(0, "expand-ids", &command,
|
||||
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()
|
||||
};
|
||||
|
||||
sequencer_init_config(&opts);
|
||||
git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
|
||||
|
||||
opts.action = REPLAY_INTERACTIVE_REBASE;
|
||||
opts.allow_ff = 1;
|
||||
opts.allow_empty = 1;
|
||||
|
||||
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 |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
|
||||
flags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
|
||||
flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
|
||||
|
||||
if (rebase_cousins >= 0 && !rebase_merges)
|
||||
warning(_("--[no-]rebase-cousins has no effect without "
|
||||
"--rebase-merges"));
|
||||
|
||||
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(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);
|
||||
}
|
||||
287
builtin/rebase--interactive.c
Normal file
287
builtin/rebase--interactive.c
Normal file
@@ -0,0 +1,287 @@
|
||||
#include "builtin.h"
|
||||
#include "cache.h"
|
||||
#include "config.h"
|
||||
#include "parse-options.h"
|
||||
#include "sequencer.h"
|
||||
#include "rebase-interactive.h"
|
||||
#include "argv-array.h"
|
||||
#include "refs.h"
|
||||
#include "rerere.h"
|
||||
#include "run-command.h"
|
||||
|
||||
static GIT_PATH_FUNC(path_state_dir, "rebase-merge/")
|
||||
static GIT_PATH_FUNC(path_squash_onto, "rebase-merge/squash-onto")
|
||||
static GIT_PATH_FUNC(path_interactive, "rebase-merge/interactive")
|
||||
|
||||
static int get_revision_ranges(const char *upstream, const char *onto,
|
||||
const char **head_hash,
|
||||
char **revisions, char **shortrevisions)
|
||||
{
|
||||
const char *base_rev = upstream ? upstream : onto, *shorthead;
|
||||
struct object_id orig_head;
|
||||
|
||||
if (get_oid("HEAD", &orig_head))
|
||||
return error(_("no HEAD?"));
|
||||
|
||||
*head_hash = find_unique_abbrev(&orig_head, GIT_MAX_HEXSZ);
|
||||
*revisions = xstrfmt("%s...%s", base_rev, *head_hash);
|
||||
|
||||
shorthead = find_unique_abbrev(&orig_head, DEFAULT_ABBREV);
|
||||
|
||||
if (upstream) {
|
||||
const char *shortrev;
|
||||
struct object_id rev_oid;
|
||||
|
||||
get_oid(base_rev, &rev_oid);
|
||||
shortrev = find_unique_abbrev(&rev_oid, DEFAULT_ABBREV);
|
||||
|
||||
*shortrevisions = xstrfmt("%s..%s", shortrev, shorthead);
|
||||
} else
|
||||
*shortrevisions = xstrdup(shorthead);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int init_basic_state(struct replay_opts *opts, const char *head_name,
|
||||
const char *onto, const char *orig_head)
|
||||
{
|
||||
FILE *interactive;
|
||||
|
||||
if (!is_directory(path_state_dir()) && mkdir_in_gitdir(path_state_dir()))
|
||||
return error_errno(_("could not create temporary %s"), path_state_dir());
|
||||
|
||||
delete_reflog("REBASE_HEAD");
|
||||
|
||||
interactive = fopen(path_interactive(), "w");
|
||||
if (!interactive)
|
||||
return error_errno(_("could not mark as interactive"));
|
||||
fclose(interactive);
|
||||
|
||||
return write_basic_state(opts, head_name, onto, orig_head);
|
||||
}
|
||||
|
||||
static int do_interactive_rebase(struct replay_opts *opts, unsigned flags,
|
||||
const char *switch_to, const char *upstream,
|
||||
const char *onto, const char *onto_name,
|
||||
const char *squash_onto, const char *head_name,
|
||||
const char *restrict_revision, char *raw_strategies,
|
||||
const char *cmd, unsigned autosquash)
|
||||
{
|
||||
int ret;
|
||||
const char *head_hash = NULL;
|
||||
char *revisions = NULL, *shortrevisions = NULL;
|
||||
struct argv_array make_script_args = ARGV_ARRAY_INIT;
|
||||
FILE *todo_list;
|
||||
|
||||
if (prepare_branch_to_be_rebased(opts, switch_to))
|
||||
return -1;
|
||||
|
||||
if (get_revision_ranges(upstream, onto, &head_hash,
|
||||
&revisions, &shortrevisions))
|
||||
return -1;
|
||||
|
||||
if (raw_strategies)
|
||||
parse_strategy_opts(opts, raw_strategies);
|
||||
|
||||
if (init_basic_state(opts, head_name, onto, head_hash)) {
|
||||
free(revisions);
|
||||
free(shortrevisions);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!upstream && squash_onto)
|
||||
write_file(path_squash_onto(), "%s\n", squash_onto);
|
||||
|
||||
todo_list = fopen(rebase_path_todo(), "w");
|
||||
if (!todo_list) {
|
||||
free(revisions);
|
||||
free(shortrevisions);
|
||||
|
||||
return error_errno(_("could not open %s"), rebase_path_todo());
|
||||
}
|
||||
|
||||
argv_array_pushl(&make_script_args, "", revisions, NULL);
|
||||
if (restrict_revision)
|
||||
argv_array_push(&make_script_args, restrict_revision);
|
||||
|
||||
ret = sequencer_make_script(todo_list,
|
||||
make_script_args.argc, make_script_args.argv,
|
||||
flags);
|
||||
fclose(todo_list);
|
||||
|
||||
if (ret)
|
||||
error(_("could not generate todo list"));
|
||||
else {
|
||||
discard_cache();
|
||||
ret = complete_action(opts, flags, shortrevisions, onto_name, onto,
|
||||
head_hash, cmd, autosquash);
|
||||
}
|
||||
|
||||
free(revisions);
|
||||
free(shortrevisions);
|
||||
argv_array_clear(&make_script_args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const char * const builtin_rebase_interactive_usage[] = {
|
||||
N_("git rebase--interactive [<options>]"),
|
||||
NULL
|
||||
};
|
||||
|
||||
int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct replay_opts opts = REPLAY_OPTS_INIT;
|
||||
unsigned flags = 0, keep_empty = 0, rebase_merges = 0, autosquash = 0;
|
||||
int abbreviate_commands = 0, rebase_cousins = -1, ret = 0;
|
||||
const char *onto = NULL, *onto_name = NULL, *restrict_revision = NULL,
|
||||
*squash_onto = NULL, *upstream = NULL, *head_name = NULL,
|
||||
*switch_to = NULL, *cmd = NULL;
|
||||
char *raw_strategies = NULL;
|
||||
enum {
|
||||
NONE = 0, CONTINUE, SKIP, EDIT_TODO, SHOW_CURRENT_PATCH,
|
||||
SHORTEN_OIDS, EXPAND_OIDS, CHECK_TODO_LIST, REARRANGE_SQUASH, ADD_EXEC,
|
||||
MAKE_SCRIPT, SKIP_UNNECESSARY_PICKS,
|
||||
} command = 0;
|
||||
struct option options[] = {
|
||||
OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
|
||||
OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")),
|
||||
OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
|
||||
N_("allow commits with empty messages")),
|
||||
OPT_BOOL(0, "rebase-merges", &rebase_merges, N_("rebase merge commits")),
|
||||
OPT_BOOL(0, "rebase-cousins", &rebase_cousins,
|
||||
N_("keep original branch points of cousins")),
|
||||
OPT_BOOL(0, "autosquash", &autosquash,
|
||||
N_("move commits that begin with squash!/fixup!")),
|
||||
OPT_BOOL(0, "signoff", &opts.signoff, N_("sign commits")),
|
||||
OPT__VERBOSE(&opts.verbose, N_("be verbose")),
|
||||
OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
|
||||
CONTINUE),
|
||||
OPT_CMDMODE(0, "skip", &command, N_("skip commit"), SKIP),
|
||||
OPT_CMDMODE(0, "edit-todo", &command, N_("edit the todo list"),
|
||||
EDIT_TODO),
|
||||
OPT_CMDMODE(0, "show-current-patch", &command, N_("show the current patch"),
|
||||
SHOW_CURRENT_PATCH),
|
||||
OPT_CMDMODE(0, "shorten-ids", &command,
|
||||
N_("shorten commit ids in the todo list"), SHORTEN_OIDS),
|
||||
OPT_CMDMODE(0, "expand-ids", &command,
|
||||
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, "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_STRING(0, "onto", &onto, N_("onto"), N_("onto")),
|
||||
OPT_STRING(0, "restrict-revision", &restrict_revision,
|
||||
N_("restrict-revision"), N_("restrict revision")),
|
||||
OPT_STRING(0, "squash-onto", &squash_onto, N_("squash-onto"),
|
||||
N_("squash onto")),
|
||||
OPT_STRING(0, "upstream", &upstream, N_("upstream"),
|
||||
N_("the upstream commit")),
|
||||
OPT_STRING(0, "head-name", &head_name, N_("head-name"), N_("head name")),
|
||||
{ OPTION_STRING, 'S', "gpg-sign", &opts.gpg_sign, N_("key-id"),
|
||||
N_("GPG-sign commits"),
|
||||
PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
|
||||
OPT_STRING(0, "strategy", &opts.strategy, N_("strategy"),
|
||||
N_("rebase strategy")),
|
||||
OPT_STRING(0, "strategy-opts", &raw_strategies, N_("strategy-opts"),
|
||||
N_("strategy options")),
|
||||
OPT_STRING(0, "switch-to", &switch_to, N_("switch-to"),
|
||||
N_("the branch or commit to checkout")),
|
||||
OPT_STRING(0, "onto-name", &onto_name, N_("onto-name"), N_("onto name")),
|
||||
OPT_STRING(0, "cmd", &cmd, N_("cmd"), N_("the command to run")),
|
||||
OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_auto),
|
||||
OPT_CMDMODE(0, "make-script", &command,
|
||||
N_("make rebase script"), MAKE_SCRIPT),
|
||||
OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
|
||||
N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
sequencer_init_config(&opts);
|
||||
git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
|
||||
|
||||
opts.action = REPLAY_INTERACTIVE_REBASE;
|
||||
opts.allow_ff = 1;
|
||||
opts.allow_empty = 1;
|
||||
|
||||
if (argc == 1)
|
||||
usage_with_options(builtin_rebase_interactive_usage, options);
|
||||
|
||||
argc = parse_options(argc, argv, NULL, options,
|
||||
builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0);
|
||||
|
||||
opts.gpg_sign = xstrdup_or_null(opts.gpg_sign);
|
||||
|
||||
flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
|
||||
flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
|
||||
flags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
|
||||
flags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
|
||||
flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
|
||||
|
||||
if (rebase_cousins >= 0 && !rebase_merges)
|
||||
warning(_("--[no-]rebase-cousins has no effect without "
|
||||
"--rebase-merges"));
|
||||
|
||||
switch (command) {
|
||||
case NONE:
|
||||
if (!onto && !upstream)
|
||||
die(_("a base commit must be provided with --upstream or --onto"));
|
||||
|
||||
ret = do_interactive_rebase(&opts, flags, switch_to, upstream, onto,
|
||||
onto_name, squash_onto, head_name, restrict_revision,
|
||||
raw_strategies, cmd, autosquash);
|
||||
break;
|
||||
case SKIP: {
|
||||
struct string_list merge_rr = STRING_LIST_INIT_DUP;
|
||||
|
||||
rerere_clear(&merge_rr);
|
||||
/* fallthrough */
|
||||
case CONTINUE:
|
||||
ret = sequencer_continue(&opts);
|
||||
break;
|
||||
}
|
||||
case EDIT_TODO:
|
||||
ret = edit_todo_list(flags);
|
||||
break;
|
||||
case SHOW_CURRENT_PATCH: {
|
||||
struct child_process cmd = CHILD_PROCESS_INIT;
|
||||
|
||||
cmd.git_cmd = 1;
|
||||
argv_array_pushl(&cmd.args, "show", "REBASE_HEAD", "--", NULL);
|
||||
ret = run_command(&cmd);
|
||||
|
||||
break;
|
||||
}
|
||||
case SHORTEN_OIDS:
|
||||
case EXPAND_OIDS:
|
||||
ret = transform_todos(flags);
|
||||
break;
|
||||
case CHECK_TODO_LIST:
|
||||
ret = check_todo_list();
|
||||
break;
|
||||
case REARRANGE_SQUASH:
|
||||
ret = rearrange_squash();
|
||||
break;
|
||||
case ADD_EXEC:
|
||||
ret = sequencer_add_exec_commands(cmd);
|
||||
break;
|
||||
case MAKE_SCRIPT:
|
||||
ret = sequencer_make_script(stdout, argc, argv, flags);
|
||||
break;
|
||||
case SKIP_UNNECESSARY_PICKS: {
|
||||
struct object_id oid;
|
||||
|
||||
ret = skip_unnecessary_picks(&oid);
|
||||
if (!ret)
|
||||
printf("%s\n", oid_to_hex(&oid));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BUG("invalid command '%d'", command);
|
||||
}
|
||||
|
||||
return !!ret;
|
||||
}
|
||||
1722
builtin/rebase.c
Normal file
1722
builtin/rebase.c
Normal file
File diff suppressed because it is too large
Load Diff
1599
builtin/stash.c
Normal file
1599
builtin/stash.c
Normal file
File diff suppressed because it is too large
Load Diff
2
cache.h
2
cache.h
@@ -1312,6 +1312,7 @@ struct object_context {
|
||||
GET_OID_BLOB)
|
||||
|
||||
extern int get_oid(const char *str, struct object_id *oid);
|
||||
extern int get_oidf(struct object_id *oid, const char *fmt, ...);
|
||||
extern int get_oid_commit(const char *str, struct object_id *oid);
|
||||
extern int get_oid_committish(const char *str, struct object_id *oid);
|
||||
extern int get_oid_tree(const char *str, struct object_id *oid);
|
||||
@@ -1465,6 +1466,7 @@ extern const char *fmt_name(const char *name, const char *email);
|
||||
extern const char *ident_default_name(void);
|
||||
extern const char *ident_default_email(void);
|
||||
extern const char *git_editor(void);
|
||||
extern const char *git_sequence_editor(void);
|
||||
extern const char *git_pager(int stdout_is_tty);
|
||||
extern int is_terminal_dumb(void);
|
||||
extern int git_ident_config(const char *, const char *, void *);
|
||||
|
||||
81
commit.c
81
commit.c
@@ -17,6 +17,7 @@
|
||||
#include "sha1-lookup.h"
|
||||
#include "wt-status.h"
|
||||
#include "advice.h"
|
||||
#include "refs.h"
|
||||
|
||||
static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
|
||||
|
||||
@@ -961,6 +962,86 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
|
||||
return result;
|
||||
}
|
||||
|
||||
struct rev_collect {
|
||||
struct commit **commit;
|
||||
int nr;
|
||||
int alloc;
|
||||
unsigned int initial : 1;
|
||||
};
|
||||
|
||||
static void add_one_commit(struct object_id *oid, struct rev_collect *revs)
|
||||
{
|
||||
struct commit *commit;
|
||||
|
||||
if (is_null_oid(oid))
|
||||
return;
|
||||
|
||||
commit = lookup_commit(the_repository, oid);
|
||||
if (!commit ||
|
||||
(commit->object.flags & TMP_MARK) ||
|
||||
parse_commit(commit))
|
||||
return;
|
||||
|
||||
ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc);
|
||||
revs->commit[revs->nr++] = commit;
|
||||
commit->object.flags |= TMP_MARK;
|
||||
}
|
||||
|
||||
static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid,
|
||||
const char *ident, timestamp_t timestamp,
|
||||
int tz, const char *message, void *cbdata)
|
||||
{
|
||||
struct rev_collect *revs = cbdata;
|
||||
|
||||
if (revs->initial) {
|
||||
revs->initial = 0;
|
||||
add_one_commit(ooid, revs);
|
||||
}
|
||||
add_one_commit(noid, revs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct commit *get_fork_point(const char *refname, struct commit *commit)
|
||||
{
|
||||
struct object_id oid;
|
||||
struct rev_collect revs;
|
||||
struct commit_list *bases;
|
||||
int i;
|
||||
struct commit *ret = NULL;
|
||||
|
||||
memset(&revs, 0, sizeof(revs));
|
||||
revs.initial = 1;
|
||||
for_each_reflog_ent(refname, collect_one_reflog_ent, &revs);
|
||||
|
||||
if (!revs.nr && !get_oid(refname, &oid))
|
||||
add_one_commit(&oid, &revs);
|
||||
|
||||
for (i = 0; i < revs.nr; i++)
|
||||
revs.commit[i]->object.flags &= ~TMP_MARK;
|
||||
|
||||
bases = get_merge_bases_many(commit, revs.nr, revs.commit);
|
||||
|
||||
/*
|
||||
* There should be one and only one merge base, when we found
|
||||
* a common ancestor among reflog entries.
|
||||
*/
|
||||
if (!bases || bases->next)
|
||||
goto cleanup_return;
|
||||
|
||||
/* And the found one must be one of the reflog entries */
|
||||
for (i = 0; i < revs.nr; i++)
|
||||
if (&bases->item->object == &revs.commit[i]->object)
|
||||
break; /* found */
|
||||
if (revs.nr <= i)
|
||||
goto cleanup_return;
|
||||
|
||||
ret = bases->item;
|
||||
|
||||
cleanup_return:
|
||||
free_commit_list(bases);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct commit_list *get_octopus_merge_bases(struct commit_list *in)
|
||||
{
|
||||
struct commit_list *i, *j, *k, *ret = NULL;
|
||||
|
||||
2
commit.h
2
commit.h
@@ -211,6 +211,8 @@ extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
|
||||
/* To be used only when object flags after this call no longer matter */
|
||||
extern struct commit_list *get_merge_bases_many_dirty(struct commit *one, int n, struct commit **twos);
|
||||
|
||||
struct commit *get_fork_point(const char *refname, struct commit *commit);
|
||||
|
||||
/* largest positive number a signed 32-bit integer can contain */
|
||||
#define INFINITE_DEPTH 0x7fffffff
|
||||
|
||||
|
||||
27
editor.c
27
editor.c
@@ -1,4 +1,5 @@
|
||||
#include "cache.h"
|
||||
#include "config.h"
|
||||
#include "strbuf.h"
|
||||
#include "run-command.h"
|
||||
#include "sigchain.h"
|
||||
@@ -34,10 +35,21 @@ const char *git_editor(void)
|
||||
return editor;
|
||||
}
|
||||
|
||||
int launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
|
||||
const char *git_sequence_editor(void)
|
||||
{
|
||||
const char *editor = git_editor();
|
||||
const char *editor = getenv("GIT_SEQUENCE_EDITOR");
|
||||
|
||||
if (!editor)
|
||||
git_config_get_string_const("sequence.editor", &editor);
|
||||
if (!editor)
|
||||
editor = git_editor();
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
static int launch_specified_editor(const char *editor, const char *path,
|
||||
struct strbuf *buffer, const char *const *env)
|
||||
{
|
||||
if (!editor)
|
||||
return error("Terminal is dumb, but EDITOR unset");
|
||||
|
||||
@@ -95,3 +107,14 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
|
||||
return error_errno("could not read file '%s'", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
|
||||
{
|
||||
return launch_specified_editor(git_editor(), path, buffer, env);
|
||||
}
|
||||
|
||||
int launch_sequence_editor(const char *path, struct strbuf *buffer,
|
||||
const char *const *env)
|
||||
{
|
||||
return launch_specified_editor(git_sequence_editor(), path, buffer, env);
|
||||
}
|
||||
|
||||
@@ -95,11 +95,11 @@ git_sequence_editor () {
|
||||
}
|
||||
|
||||
expand_todo_ids() {
|
||||
git rebase--helper --expand-ids
|
||||
git rebase--interactive --expand-ids
|
||||
}
|
||||
|
||||
collapse_todo_ids() {
|
||||
git rebase--helper --shorten-ids
|
||||
git rebase--interactive --shorten-ids
|
||||
}
|
||||
|
||||
# Switch to the branch in $into and notify it in the reflog
|
||||
@@ -131,12 +131,12 @@ get_missing_commit_check_level () {
|
||||
initiate_action () {
|
||||
case "$1" in
|
||||
continue)
|
||||
exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
|
||||
exec git rebase--interactive ${force_rebase:+--no-ff} $allow_empty_message \
|
||||
--continue
|
||||
;;
|
||||
skip)
|
||||
git rerere clear
|
||||
exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
|
||||
exec git rebase--interactive ${force_rebase:+--no-ff} $allow_empty_message \
|
||||
--continue
|
||||
;;
|
||||
edit-todo)
|
||||
@@ -207,8 +207,8 @@ init_revisions_and_shortrevisions () {
|
||||
|
||||
complete_action() {
|
||||
test -s "$todo" || echo noop >> "$todo"
|
||||
test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
|
||||
test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd"
|
||||
test -z "$autosquash" || git rebase--interactive --rearrange-squash || exit
|
||||
test -n "$cmd" && git rebase--interactive --add-exec-commands --cmd "$cmd"
|
||||
|
||||
todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
|
||||
todocount=${todocount##* }
|
||||
@@ -243,7 +243,7 @@ EOF
|
||||
has_action "$todo" ||
|
||||
return 2
|
||||
|
||||
git rebase--helper --check-todo-list || {
|
||||
git rebase--interactive --check-todo-list || {
|
||||
ret=$?
|
||||
checkout_onto
|
||||
exit $ret
|
||||
@@ -252,12 +252,12 @@ EOF
|
||||
expand_todo_ids
|
||||
|
||||
test -n "$force_rebase" ||
|
||||
onto="$(git rebase--helper --skip-unnecessary-picks)" ||
|
||||
onto="$(git rebase--interactive --skip-unnecessary-picks)" ||
|
||||
die "Could not skip unnecessary pick commands"
|
||||
|
||||
checkout_onto
|
||||
require_clean_work_tree "rebase"
|
||||
exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
|
||||
exec git rebase--interactive ${force_rebase:+--no-ff} $allow_empty_message \
|
||||
--continue
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ git_rebase__interactive () {
|
||||
|
||||
init_revisions_and_shortrevisions
|
||||
|
||||
git rebase--helper --make-script ${keep_empty:+--keep-empty} \
|
||||
git rebase--interactive --make-script ${keep_empty:+--keep-empty} \
|
||||
${rebase_merges:+--rebase-merges} \
|
||||
${rebase_cousins:+--rebase-cousins} \
|
||||
$revisions ${restrict_revision+^$restrict_revision} >"$todo" ||
|
||||
@@ -57,12 +57,7 @@ cd_to_toplevel
|
||||
LF='
|
||||
'
|
||||
ok_to_skip_pre_rebase=
|
||||
resolvemsg="
|
||||
$(gettext 'Resolve all conflicts manually, mark them as resolved with
|
||||
"git add/rm <conflicted_files>", then run "git rebase --continue".
|
||||
You can instead skip this commit: run "git rebase --skip".
|
||||
To abort and get back to the state before "git rebase", run "git rebase --abort".')
|
||||
"
|
||||
|
||||
squash_onto=
|
||||
unset onto
|
||||
unset restrict_revision
|
||||
@@ -102,6 +97,7 @@ case "$(git config --bool commit.gpgsign)" in
|
||||
true) gpg_sign_opt=-S ;;
|
||||
*) gpg_sign_opt= ;;
|
||||
esac
|
||||
. git-rebase--common
|
||||
|
||||
read_basic_state () {
|
||||
test -f "$state_dir/head-name" &&
|
||||
@@ -132,67 +128,6 @@ read_basic_state () {
|
||||
}
|
||||
}
|
||||
|
||||
write_basic_state () {
|
||||
echo "$head_name" > "$state_dir"/head-name &&
|
||||
echo "$onto" > "$state_dir"/onto &&
|
||||
echo "$orig_head" > "$state_dir"/orig-head &&
|
||||
echo "$GIT_QUIET" > "$state_dir"/quiet &&
|
||||
test t = "$verbose" && : > "$state_dir"/verbose
|
||||
test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy
|
||||
test -n "$strategy_opts" && echo "$strategy_opts" > \
|
||||
"$state_dir"/strategy_opts
|
||||
test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \
|
||||
"$state_dir"/allow_rerere_autoupdate
|
||||
test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt
|
||||
test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff
|
||||
}
|
||||
|
||||
output () {
|
||||
case "$verbose" in
|
||||
'')
|
||||
output=$("$@" 2>&1 )
|
||||
status=$?
|
||||
test $status != 0 && printf "%s\n" "$output"
|
||||
return $status
|
||||
;;
|
||||
*)
|
||||
"$@"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
move_to_original_branch () {
|
||||
case "$head_name" in
|
||||
refs/*)
|
||||
message="rebase finished: $head_name onto $onto"
|
||||
git update-ref -m "$message" \
|
||||
$head_name $(git rev-parse HEAD) $orig_head &&
|
||||
git symbolic-ref \
|
||||
-m "rebase finished: returning to $head_name" \
|
||||
HEAD $head_name ||
|
||||
die "$(eval_gettext "Could not move back to \$head_name")"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
apply_autostash () {
|
||||
if test -f "$state_dir/autostash"
|
||||
then
|
||||
stash_sha1=$(cat "$state_dir/autostash")
|
||||
if git stash apply $stash_sha1 >/dev/null 2>&1
|
||||
then
|
||||
echo "$(gettext 'Applied autostash.')" >&2
|
||||
else
|
||||
git stash store -m "autostash" -q $stash_sha1 ||
|
||||
die "$(eval_gettext "Cannot store \$stash_sha1")"
|
||||
gettext 'Applying autostash resulted in conflicts.
|
||||
Your changes are safe in the stash.
|
||||
You can run "git stash pop" or "git stash drop" at any time.
|
||||
' >&2
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
finish_rebase () {
|
||||
rm -f "$(git rev-parse --git-path REBASE_HEAD)"
|
||||
apply_autostash &&
|
||||
@@ -206,24 +141,37 @@ run_specific_rebase () {
|
||||
export GIT_EDITOR
|
||||
autosquash=
|
||||
fi
|
||||
. git-rebase--$type
|
||||
|
||||
if test -z "$preserve_merges"
|
||||
if test -n "$interactive_rebase" -a -z "$preserve_merges"
|
||||
then
|
||||
. git-legacy-rebase--$type
|
||||
|
||||
git_rebase__$type
|
||||
else
|
||||
git_rebase__preserve_merges
|
||||
. git-rebase--$type
|
||||
|
||||
if test -z "$preserve_merges"
|
||||
then
|
||||
git_rebase__$type
|
||||
else
|
||||
git_rebase__preserve_merges
|
||||
fi
|
||||
fi
|
||||
|
||||
ret=$?
|
||||
if test $ret -eq 0
|
||||
then
|
||||
finish_rebase
|
||||
elif test $ret -eq 2 # special exit status for rebase -i
|
||||
elif test $ret -eq 2 # special exit status for rebase -p
|
||||
then
|
||||
apply_autostash &&
|
||||
rm -rf "$state_dir" &&
|
||||
die "Nothing to do"
|
||||
if test -n "$interactive_rebase" -a -z "$preserve_merges"
|
||||
then
|
||||
die "error: nothing to do"
|
||||
else
|
||||
die "Nothing to do"
|
||||
fi
|
||||
fi
|
||||
exit $ret
|
||||
}
|
||||
@@ -66,6 +66,28 @@ clear_stash () {
|
||||
fi
|
||||
}
|
||||
|
||||
maybe_quiet () {
|
||||
case "$1" in
|
||||
--keep-stdout)
|
||||
shift
|
||||
if test -n "$GIT_QUIET"
|
||||
then
|
||||
eval "$@" 2>/dev/null
|
||||
else
|
||||
eval "$@"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if test -n "$GIT_QUIET"
|
||||
then
|
||||
eval "$@" >/dev/null 2>&1
|
||||
else
|
||||
eval "$@"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
create_stash () {
|
||||
stash_msg=
|
||||
untracked=
|
||||
@@ -95,15 +117,18 @@ create_stash () {
|
||||
done
|
||||
|
||||
git update-index -q --refresh
|
||||
if no_changes "$@"
|
||||
if maybe_quiet no_changes "$@"
|
||||
then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# state of the base commit
|
||||
if b_commit=$(git rev-parse --verify HEAD)
|
||||
if b_commit=$(maybe_quiet --keep-stdout git rev-parse --verify HEAD)
|
||||
then
|
||||
head=$(git rev-list --oneline -n 1 HEAD --)
|
||||
elif test -n "$GIT_QUIET"
|
||||
then
|
||||
exit 1
|
||||
else
|
||||
die "$(gettext "You do not have the initial commit yet")"
|
||||
fi
|
||||
@@ -298,7 +323,7 @@ push_stash () {
|
||||
test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
|
||||
|
||||
git update-index -q --refresh
|
||||
if no_changes "$@"
|
||||
if maybe_quiet no_changes "$@"
|
||||
then
|
||||
say "$(gettext "No local changes to save")"
|
||||
exit 0
|
||||
@@ -353,6 +378,9 @@ save_stash () {
|
||||
while test $# != 0
|
||||
do
|
||||
case "$1" in
|
||||
-q|--quiet)
|
||||
GIT_QUIET=t
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
68
git-rebase--common.sh
Normal file
68
git-rebase--common.sh
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
resolvemsg="
|
||||
$(gettext 'Resolve all conflicts manually, mark them as resolved with
|
||||
"git add/rm <conflicted_files>", then run "git rebase --continue".
|
||||
You can instead skip this commit: run "git rebase --skip".
|
||||
To abort and get back to the state before "git rebase", run "git rebase --abort".')
|
||||
"
|
||||
|
||||
write_basic_state () {
|
||||
echo "$head_name" > "$state_dir"/head-name &&
|
||||
echo "$onto" > "$state_dir"/onto &&
|
||||
echo "$orig_head" > "$state_dir"/orig-head &&
|
||||
echo "$GIT_QUIET" > "$state_dir"/quiet &&
|
||||
test t = "$verbose" && : > "$state_dir"/verbose
|
||||
test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy
|
||||
test -n "$strategy_opts" && echo "$strategy_opts" > \
|
||||
"$state_dir"/strategy_opts
|
||||
test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \
|
||||
"$state_dir"/allow_rerere_autoupdate
|
||||
test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt
|
||||
test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff
|
||||
}
|
||||
|
||||
apply_autostash () {
|
||||
if test -f "$state_dir/autostash"
|
||||
then
|
||||
stash_sha1=$(cat "$state_dir/autostash")
|
||||
if git stash apply $stash_sha1 >/dev/null 2>&1
|
||||
then
|
||||
echo "$(gettext 'Applied autostash.')" >&2
|
||||
else
|
||||
git stash store -m "autostash" -q $stash_sha1 ||
|
||||
die "$(eval_gettext "Cannot store \$stash_sha1")"
|
||||
gettext 'Applying autostash resulted in conflicts.
|
||||
Your changes are safe in the stash.
|
||||
You can run "git stash pop" or "git stash drop" at any time.
|
||||
' >&2
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
move_to_original_branch () {
|
||||
case "$head_name" in
|
||||
refs/*)
|
||||
message="rebase finished: $head_name onto $onto"
|
||||
git update-ref -m "$message" \
|
||||
$head_name $(git rev-parse HEAD) $orig_head &&
|
||||
git symbolic-ref \
|
||||
-m "rebase finished: returning to $head_name" \
|
||||
HEAD $head_name ||
|
||||
die "$(eval_gettext "Could not move back to \$head_name")"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
output () {
|
||||
case "$verbose" in
|
||||
'')
|
||||
output=$("$@" 2>&1 )
|
||||
status=$?
|
||||
test $status != 0 && printf "%s\n" "$output"
|
||||
return $status
|
||||
;;
|
||||
*)
|
||||
"$@"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -711,11 +711,11 @@ do_rest () {
|
||||
}
|
||||
|
||||
expand_todo_ids() {
|
||||
git rebase--helper --expand-ids
|
||||
git rebase--interactive --expand-ids
|
||||
}
|
||||
|
||||
collapse_todo_ids() {
|
||||
git rebase--helper --shorten-ids
|
||||
git rebase--interactive --shorten-ids
|
||||
}
|
||||
|
||||
# Switch to the branch in $into and notify it in the reflog
|
||||
@@ -876,8 +876,8 @@ init_revisions_and_shortrevisions () {
|
||||
|
||||
complete_action() {
|
||||
test -s "$todo" || echo noop >> "$todo"
|
||||
test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
|
||||
test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd"
|
||||
test -z "$autosquash" || git rebase--interactive --rearrange-squash || exit
|
||||
test -n "$cmd" && git rebase--interactive --add-exec-commands --cmd "$cmd"
|
||||
|
||||
todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
|
||||
todocount=${todocount##* }
|
||||
@@ -912,7 +912,7 @@ However, if you remove everything, the rebase will be aborted.
|
||||
has_action "$todo" ||
|
||||
return 2
|
||||
|
||||
git rebase--helper --check-todo-list || {
|
||||
git rebase--interactive --check-todo-list || {
|
||||
ret=$?
|
||||
checkout_onto
|
||||
exit $ret
|
||||
|
||||
@@ -101,6 +101,7 @@ $LONG_USAGE")"
|
||||
case "$1" in
|
||||
-h)
|
||||
echo "$LONG_USAGE"
|
||||
case "$0" in *git-legacy-stash) exit 129;; esac
|
||||
exit
|
||||
esac
|
||||
fi
|
||||
|
||||
14
git.c
14
git.c
@@ -522,7 +522,13 @@ static struct cmd_struct commands[] = {
|
||||
{ "push", cmd_push, RUN_SETUP },
|
||||
{ "range-diff", cmd_range_diff, RUN_SETUP | USE_PAGER },
|
||||
{ "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},
|
||||
{ "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },
|
||||
/*
|
||||
* NEEDSWORK: Until the rebase is independent and needs no redirection
|
||||
* to rebase shell script this is kept as is, then should be changed to
|
||||
* RUN_SETUP | NEED_WORK_TREE
|
||||
*/
|
||||
{ "rebase", cmd_rebase },
|
||||
{ "rebase--interactive", cmd_rebase__interactive, RUN_SETUP | NEED_WORK_TREE },
|
||||
{ "receive-pack", cmd_receive_pack },
|
||||
{ "reflog", cmd_reflog, RUN_SETUP },
|
||||
{ "remote", cmd_remote, RUN_SETUP },
|
||||
@@ -544,6 +550,12 @@ static struct cmd_struct commands[] = {
|
||||
{ "show-index", cmd_show_index },
|
||||
{ "show-ref", cmd_show_ref, RUN_SETUP },
|
||||
{ "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
|
||||
/*
|
||||
* NEEDSWORK: Until the builtin stash is thoroughly robust and no
|
||||
* longer needs redirection to the stash shell script this is kept as
|
||||
* is, then should be changed to RUN_SETUP | NEED_WORK_TREE
|
||||
*/
|
||||
{ "stash", cmd_stash },
|
||||
{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
|
||||
{ "stripspace", cmd_stripspace },
|
||||
{ "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
|
||||
|
||||
90
rebase-interactive.c
Normal file
90
rebase-interactive.c
Normal file
@@ -0,0 +1,90 @@
|
||||
#include "cache.h"
|
||||
#include "commit.h"
|
||||
#include "rebase-interactive.h"
|
||||
#include "sequencer.h"
|
||||
#include "strbuf.h"
|
||||
|
||||
void append_todo_help(unsigned edit_todo, unsigned keep_empty,
|
||||
struct strbuf *buf)
|
||||
{
|
||||
const char *msg = _("\nCommands:\n"
|
||||
"p, pick <commit> = use commit\n"
|
||||
"r, reword <commit> = use commit, but edit the commit message\n"
|
||||
"e, edit <commit> = use commit, but stop for amending\n"
|
||||
"s, squash <commit> = use commit, but meld into previous commit\n"
|
||||
"f, fixup <commit> = like \"squash\", but discard this commit's log message\n"
|
||||
"x, exec <command> = run command (the rest of the line) using shell\n"
|
||||
"d, drop <commit> = remove commit\n"
|
||||
"l, label <label> = label current HEAD with a name\n"
|
||||
"t, reset <label> = reset HEAD to a label\n"
|
||||
"m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n"
|
||||
". create a merge commit using the original merge commit's\n"
|
||||
". message (or the oneline, if no original merge commit was\n"
|
||||
". specified). Use -c <commit> to reword the commit message.\n"
|
||||
"\n"
|
||||
"These lines can be re-ordered; they are executed from top to bottom.\n");
|
||||
|
||||
strbuf_add_commented_lines(buf, msg, strlen(msg));
|
||||
|
||||
if (get_missing_commit_check_level() == MISSING_COMMIT_CHECK_ERROR)
|
||||
msg = _("\nDo not remove any line. Use 'drop' "
|
||||
"explicitly to remove a commit.\n");
|
||||
else
|
||||
msg = _("\nIf you remove a line here "
|
||||
"THAT COMMIT WILL BE LOST.\n");
|
||||
|
||||
strbuf_add_commented_lines(buf, msg, strlen(msg));
|
||||
|
||||
if (edit_todo)
|
||||
msg = _("\nYou are editing the todo file "
|
||||
"of an ongoing interactive rebase.\n"
|
||||
"To continue rebase after editing, run:\n"
|
||||
" git rebase --continue\n\n");
|
||||
else
|
||||
msg = _("\nHowever, if you remove everything, "
|
||||
"the rebase will be aborted.\n\n");
|
||||
|
||||
strbuf_add_commented_lines(buf, msg, strlen(msg));
|
||||
|
||||
if (!keep_empty) {
|
||||
msg = _("Note that empty commits are commented out");
|
||||
strbuf_add_commented_lines(buf, msg, strlen(msg));
|
||||
}
|
||||
}
|
||||
|
||||
int edit_todo_list(unsigned flags)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
const char *todo_file = rebase_path_todo();
|
||||
|
||||
if (strbuf_read_file(&buf, todo_file, 0) < 0)
|
||||
return error_errno(_("could not read '%s'."), todo_file);
|
||||
|
||||
strbuf_stripspace(&buf, 1);
|
||||
if (write_message(buf.buf, buf.len, todo_file, 0)) {
|
||||
strbuf_release(&buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
strbuf_release(&buf);
|
||||
|
||||
transform_todos(flags | TODO_LIST_SHORTEN_IDS);
|
||||
|
||||
if (strbuf_read_file(&buf, todo_file, 0) < 0)
|
||||
return error_errno(_("could not read '%s'."), todo_file);
|
||||
|
||||
append_todo_help(1, 0, &buf);
|
||||
if (write_message(buf.buf, buf.len, todo_file, 0)) {
|
||||
strbuf_release(&buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
strbuf_release(&buf);
|
||||
|
||||
if (launch_sequence_editor(todo_file, NULL, NULL))
|
||||
return -1;
|
||||
|
||||
transform_todos(flags & ~(TODO_LIST_SHORTEN_IDS));
|
||||
|
||||
return 0;
|
||||
}
|
||||
8
rebase-interactive.h
Normal file
8
rebase-interactive.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef REBASE_INTERACTIVE_H
|
||||
#define REBASE_INTERACTIVE_H
|
||||
|
||||
void append_todo_help(unsigned edit_todo, unsigned keep_empty,
|
||||
struct strbuf *buf);
|
||||
int edit_todo_list(unsigned flags);
|
||||
|
||||
#endif
|
||||
320
sequencer.c
320
sequencer.c
@@ -30,6 +30,7 @@
|
||||
#include "oidset.h"
|
||||
#include "commit-slab.h"
|
||||
#include "alias.h"
|
||||
#include "rebase-interactive.h"
|
||||
|
||||
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
|
||||
|
||||
@@ -52,7 +53,10 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge")
|
||||
* the lines are processed, they are removed from the front of this
|
||||
* file and written to the tail of 'done'.
|
||||
*/
|
||||
static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
|
||||
GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
|
||||
static GIT_PATH_FUNC(rebase_path_todo_backup,
|
||||
"rebase-merge/git-rebase-todo.backup")
|
||||
|
||||
/*
|
||||
* The rebase command lines that have already been processed. A line
|
||||
* is moved here when it is first handled, before any associated user
|
||||
@@ -140,7 +144,7 @@ static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
|
||||
|
||||
/*
|
||||
* The following files are written by git-rebase just after parsing the
|
||||
* command-line (and are only consumed, not modified, by the sequencer).
|
||||
* command-line.
|
||||
*/
|
||||
static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
|
||||
static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")
|
||||
@@ -152,6 +156,7 @@ static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash")
|
||||
static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
|
||||
static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
|
||||
static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
|
||||
static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet")
|
||||
|
||||
static int git_sequencer_config(const char *k, const char *v, void *cb)
|
||||
{
|
||||
@@ -373,8 +378,8 @@ static void print_advice(int show_hint, struct replay_opts *opts)
|
||||
}
|
||||
}
|
||||
|
||||
static int write_message(const void *buf, size_t len, const char *filename,
|
||||
int append_eol)
|
||||
int write_message(const void *buf, size_t len, const char *filename,
|
||||
int append_eol)
|
||||
{
|
||||
struct lock_file msg_file = LOCK_INIT;
|
||||
|
||||
@@ -800,6 +805,23 @@ N_("you have staged changes in your working tree\n"
|
||||
#define VERIFY_MSG (1<<4)
|
||||
#define CREATE_ROOT_COMMIT (1<<5)
|
||||
|
||||
static int run_command_silent_on_success(struct child_process *cmd)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int rc;
|
||||
|
||||
cmd->stdout_to_stderr = 1;
|
||||
rc = pipe_command(cmd,
|
||||
NULL, 0,
|
||||
NULL, 0,
|
||||
&buf, 0);
|
||||
|
||||
if (rc)
|
||||
fputs(buf.buf, stderr);
|
||||
strbuf_release(&buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we are cherry-pick, and if the merge did not result in
|
||||
* hand-editing, we will hit this commit and inherit the original
|
||||
@@ -861,18 +883,11 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
|
||||
|
||||
cmd.git_cmd = 1;
|
||||
|
||||
if (is_rebase_i(opts)) {
|
||||
if (!(flags & EDIT_MSG)) {
|
||||
cmd.stdout_to_stderr = 1;
|
||||
cmd.err = -1;
|
||||
}
|
||||
if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) {
|
||||
const char *gpg_opt = gpg_sign_opt_quoted(opts);
|
||||
|
||||
if (read_env_script(&cmd.env_array)) {
|
||||
const char *gpg_opt = gpg_sign_opt_quoted(opts);
|
||||
|
||||
return error(_(staged_changes_advice),
|
||||
gpg_opt, gpg_opt);
|
||||
}
|
||||
return error(_(staged_changes_advice),
|
||||
gpg_opt, gpg_opt);
|
||||
}
|
||||
|
||||
argv_array_push(&cmd.args, "commit");
|
||||
@@ -902,21 +917,10 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
|
||||
if (opts->allow_empty_message)
|
||||
argv_array_push(&cmd.args, "--allow-empty-message");
|
||||
|
||||
if (cmd.err == -1) {
|
||||
/* hide stderr on success */
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int rc = pipe_command(&cmd,
|
||||
NULL, 0,
|
||||
/* stdout is already redirected */
|
||||
NULL, 0,
|
||||
&buf, 0);
|
||||
if (rc)
|
||||
fputs(buf.buf, stderr);
|
||||
strbuf_release(&buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return run_command(&cmd);
|
||||
if (is_rebase_i(opts) && !(flags & EDIT_MSG))
|
||||
return run_command_silent_on_success(&cmd);
|
||||
else
|
||||
return run_command(&cmd);
|
||||
}
|
||||
|
||||
static int rest_is_empty(const struct strbuf *sb, int start)
|
||||
@@ -2240,21 +2244,14 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
|
||||
void parse_strategy_opts(struct replay_opts *opts, char *raw_opts)
|
||||
{
|
||||
int i;
|
||||
char *strategy_opts_string;
|
||||
char *strategy_opts_string = raw_opts;
|
||||
|
||||
strbuf_reset(buf);
|
||||
if (!read_oneliner(buf, rebase_path_strategy(), 0))
|
||||
return;
|
||||
opts->strategy = strbuf_detach(buf, NULL);
|
||||
if (!read_oneliner(buf, rebase_path_strategy_opts(), 0))
|
||||
return;
|
||||
|
||||
strategy_opts_string = buf->buf;
|
||||
if (*strategy_opts_string == ' ')
|
||||
strategy_opts_string++;
|
||||
|
||||
opts->xopts_nr = split_cmdline(strategy_opts_string,
|
||||
(const char ***)&opts->xopts);
|
||||
for (i = 0; i < opts->xopts_nr; i++) {
|
||||
@@ -2265,6 +2262,18 @@ static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
|
||||
}
|
||||
}
|
||||
|
||||
static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
|
||||
{
|
||||
strbuf_reset(buf);
|
||||
if (!read_oneliner(buf, rebase_path_strategy(), 0))
|
||||
return;
|
||||
opts->strategy = strbuf_detach(buf, NULL);
|
||||
if (!read_oneliner(buf, rebase_path_strategy_opts(), 0))
|
||||
return;
|
||||
|
||||
parse_strategy_opts(opts, buf->buf);
|
||||
}
|
||||
|
||||
static int read_populate_opts(struct replay_opts *opts)
|
||||
{
|
||||
if (is_rebase_i(opts)) {
|
||||
@@ -2332,6 +2341,55 @@ static int read_populate_opts(struct replay_opts *opts)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void write_strategy_opts(struct replay_opts *opts)
|
||||
{
|
||||
int i;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
|
||||
for (i = 0; i < opts->xopts_nr; ++i)
|
||||
strbuf_addf(&buf, " --%s", opts->xopts[i]);
|
||||
|
||||
write_file(rebase_path_strategy_opts(), "%s\n", buf.buf);
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
int write_basic_state(struct replay_opts *opts, const char *head_name,
|
||||
const char *onto, const char *orig_head)
|
||||
{
|
||||
const char *quiet = getenv("GIT_QUIET");
|
||||
|
||||
if (head_name)
|
||||
write_file(rebase_path_head_name(), "%s\n", head_name);
|
||||
if (onto)
|
||||
write_file(rebase_path_onto(), "%s\n", onto);
|
||||
if (orig_head)
|
||||
write_file(rebase_path_orig_head(), "%s\n", orig_head);
|
||||
|
||||
if (quiet)
|
||||
write_file(rebase_path_quiet(), "%s\n", quiet);
|
||||
else
|
||||
write_file(rebase_path_quiet(), "\n");
|
||||
|
||||
if (opts->verbose)
|
||||
write_file(rebase_path_verbose(), "");
|
||||
if (opts->strategy)
|
||||
write_file(rebase_path_strategy(), "%s\n", opts->strategy);
|
||||
if (opts->xopts_nr > 0)
|
||||
write_strategy_opts(opts);
|
||||
|
||||
if (opts->allow_rerere_auto == RERERE_AUTOUPDATE)
|
||||
write_file(rebase_path_allow_rerere_autoupdate(), "--rerere-autoupdate\n");
|
||||
else if (opts->allow_rerere_auto == RERERE_NOAUTOUPDATE)
|
||||
write_file(rebase_path_allow_rerere_autoupdate(), "--no-rerere-autoupdate\n");
|
||||
|
||||
if (opts->gpg_sign)
|
||||
write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign);
|
||||
if (opts->signoff)
|
||||
write_file(rebase_path_signoff(), "--signoff\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int walk_revs_populate_todo(struct todo_list *todo_list,
|
||||
struct replay_opts *opts)
|
||||
{
|
||||
@@ -3282,6 +3340,55 @@ static const char *reflog_message(struct replay_opts *opts,
|
||||
return buf.buf;
|
||||
}
|
||||
|
||||
static int run_git_checkout(struct replay_opts *opts, const char *commit,
|
||||
const char *action)
|
||||
{
|
||||
struct child_process cmd = CHILD_PROCESS_INIT;
|
||||
|
||||
cmd.git_cmd = 1;
|
||||
|
||||
argv_array_push(&cmd.args, "checkout");
|
||||
argv_array_push(&cmd.args, commit);
|
||||
argv_array_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action);
|
||||
|
||||
if (opts->verbose)
|
||||
return run_command(&cmd);
|
||||
else
|
||||
return run_command_silent_on_success(&cmd);
|
||||
}
|
||||
|
||||
int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit)
|
||||
{
|
||||
const char *action;
|
||||
|
||||
if (commit && *commit) {
|
||||
action = reflog_message(opts, "start", "checkout %s", commit);
|
||||
if (run_git_checkout(opts, commit, action))
|
||||
return error(_("could not checkout %s"), commit);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int checkout_onto(struct replay_opts *opts,
|
||||
const char *onto_name, const char *onto,
|
||||
const char *orig_head)
|
||||
{
|
||||
struct object_id oid;
|
||||
const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
|
||||
|
||||
if (get_oid(orig_head, &oid))
|
||||
return error(_("%s: not a valid OID"), orig_head);
|
||||
|
||||
if (run_git_checkout(opts, onto, action)) {
|
||||
apply_autostash(opts);
|
||||
sequencer_remove_state(opts);
|
||||
return error(_("could not detach HEAD"));
|
||||
}
|
||||
|
||||
return update_ref(NULL, "ORIG_HEAD", &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR);
|
||||
}
|
||||
|
||||
static const char rescheduled_advice[] =
|
||||
N_("Could not execute the todo command\n"
|
||||
"\n"
|
||||
@@ -4407,24 +4514,20 @@ int transform_todos(unsigned flags)
|
||||
return i;
|
||||
}
|
||||
|
||||
enum check_level {
|
||||
CHECK_IGNORE = 0, CHECK_WARN, CHECK_ERROR
|
||||
};
|
||||
|
||||
static enum check_level get_missing_commit_check_level(void)
|
||||
enum missing_commit_check_level get_missing_commit_check_level(void)
|
||||
{
|
||||
const char *value;
|
||||
|
||||
if (git_config_get_value("rebase.missingcommitscheck", &value) ||
|
||||
!strcasecmp("ignore", value))
|
||||
return CHECK_IGNORE;
|
||||
return MISSING_COMMIT_CHECK_IGNORE;
|
||||
if (!strcasecmp("warn", value))
|
||||
return CHECK_WARN;
|
||||
return MISSING_COMMIT_CHECK_WARN;
|
||||
if (!strcasecmp("error", value))
|
||||
return CHECK_ERROR;
|
||||
return MISSING_COMMIT_CHECK_ERROR;
|
||||
warning(_("unrecognized setting %s for option "
|
||||
"rebase.missingCommitsCheck. Ignoring."), value);
|
||||
return CHECK_IGNORE;
|
||||
return MISSING_COMMIT_CHECK_IGNORE;
|
||||
}
|
||||
|
||||
define_commit_slab(commit_seen, unsigned char);
|
||||
@@ -4436,7 +4539,7 @@ define_commit_slab(commit_seen, unsigned char);
|
||||
*/
|
||||
int check_todo_list(void)
|
||||
{
|
||||
enum check_level check_level = get_missing_commit_check_level();
|
||||
enum missing_commit_check_level check_level = get_missing_commit_check_level();
|
||||
struct strbuf todo_file = STRBUF_INIT;
|
||||
struct todo_list todo_list = TODO_LIST_INIT;
|
||||
struct strbuf missing = STRBUF_INIT;
|
||||
@@ -4453,7 +4556,7 @@ int check_todo_list(void)
|
||||
advise_to_edit_todo = res =
|
||||
parse_insn_buffer(todo_list.buf.buf, &todo_list);
|
||||
|
||||
if (res || check_level == CHECK_IGNORE)
|
||||
if (res || check_level == MISSING_COMMIT_CHECK_IGNORE)
|
||||
goto leave_check;
|
||||
|
||||
/* Mark the commits in git-rebase-todo as seen */
|
||||
@@ -4488,7 +4591,7 @@ int check_todo_list(void)
|
||||
if (!missing.len)
|
||||
goto leave_check;
|
||||
|
||||
if (check_level == CHECK_ERROR)
|
||||
if (check_level == MISSING_COMMIT_CHECK_ERROR)
|
||||
advise_to_edit_todo = res = 1;
|
||||
|
||||
fprintf(stderr,
|
||||
@@ -4534,17 +4637,17 @@ static int rewrite_file(const char *path, const char *buf, size_t len)
|
||||
}
|
||||
|
||||
/* skip picking commits whose parents are unchanged */
|
||||
int skip_unnecessary_picks(void)
|
||||
int skip_unnecessary_picks(struct object_id *output_oid)
|
||||
{
|
||||
const char *todo_file = rebase_path_todo();
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct todo_list todo_list = TODO_LIST_INIT;
|
||||
struct object_id onto_oid, *oid = &onto_oid, *parent_oid;
|
||||
struct object_id *parent_oid;
|
||||
int fd, i;
|
||||
|
||||
if (!read_oneliner(&buf, rebase_path_onto(), 0))
|
||||
return error(_("could not read 'onto'"));
|
||||
if (get_oid(buf.buf, &onto_oid)) {
|
||||
if (get_oid(buf.buf, output_oid)) {
|
||||
strbuf_release(&buf);
|
||||
return error(_("need a HEAD to fixup"));
|
||||
}
|
||||
@@ -4574,9 +4677,9 @@ int skip_unnecessary_picks(void)
|
||||
if (item->commit->parents->next)
|
||||
break; /* merge commit */
|
||||
parent_oid = &item->commit->parents->item->object.oid;
|
||||
if (hashcmp(parent_oid->hash, oid->hash))
|
||||
if (hashcmp(parent_oid->hash, output_oid->hash))
|
||||
break;
|
||||
oid = &item->commit->object.oid;
|
||||
oidcpy(output_oid, &item->commit->object.oid);
|
||||
}
|
||||
if (i > 0) {
|
||||
int offset = get_item_line_offset(&todo_list, i);
|
||||
@@ -4605,15 +4708,114 @@ int skip_unnecessary_picks(void)
|
||||
|
||||
todo_list.current = i;
|
||||
if (is_fixup(peek_command(&todo_list, 0)))
|
||||
record_in_rewritten(oid, peek_command(&todo_list, 0));
|
||||
record_in_rewritten(output_oid, peek_command(&todo_list, 0));
|
||||
}
|
||||
|
||||
todo_list_release(&todo_list);
|
||||
printf("%s\n", oid_to_hex(oid));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int complete_action(struct replay_opts *opts, unsigned flags,
|
||||
const char *shortrevisions, const char *onto_name,
|
||||
const char *onto, const char *orig_head, const char *cmd,
|
||||
unsigned autosquash)
|
||||
{
|
||||
const char *shortonto, *todo_file = rebase_path_todo();
|
||||
struct todo_list todo_list = TODO_LIST_INIT;
|
||||
struct strbuf *buf = &(todo_list.buf);
|
||||
struct object_id oid;
|
||||
struct stat st;
|
||||
|
||||
get_oid(onto, &oid);
|
||||
shortonto = find_unique_abbrev(&oid, DEFAULT_ABBREV);
|
||||
|
||||
if (!lstat(todo_file, &st) && st.st_size == 0 &&
|
||||
write_message("noop\n", 5, todo_file, 0))
|
||||
return -1;
|
||||
|
||||
if (autosquash && rearrange_squash())
|
||||
return -1;
|
||||
|
||||
if (cmd && *cmd)
|
||||
sequencer_add_exec_commands(cmd);
|
||||
|
||||
if (strbuf_read_file(buf, todo_file, 0) < 0)
|
||||
return error_errno(_("could not read '%s'."), todo_file);
|
||||
|
||||
if (parse_insn_buffer(buf->buf, &todo_list)) {
|
||||
todo_list_release(&todo_list);
|
||||
return error(_("unusable todo list: '%s'"), todo_file);
|
||||
}
|
||||
|
||||
if (count_commands(&todo_list) == 0) {
|
||||
apply_autostash(opts);
|
||||
sequencer_remove_state(opts);
|
||||
todo_list_release(&todo_list);
|
||||
|
||||
return error(_("nothing to do"));
|
||||
}
|
||||
|
||||
strbuf_addch(buf, '\n');
|
||||
strbuf_commented_addf(buf, Q_("Rebase %s onto %s (%d command)",
|
||||
"Rebase %s onto %s (%d commands)",
|
||||
count_commands(&todo_list)),
|
||||
shortrevisions, shortonto, count_commands(&todo_list));
|
||||
append_todo_help(0, flags & TODO_LIST_KEEP_EMPTY, buf);
|
||||
|
||||
if (write_message(buf->buf, buf->len, todo_file, 0)) {
|
||||
todo_list_release(&todo_list);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (copy_file(rebase_path_todo_backup(), todo_file, 0666))
|
||||
return error(_("could not copy '%s' to '%s'."), todo_file,
|
||||
rebase_path_todo_backup());
|
||||
|
||||
if (transform_todos(flags | TODO_LIST_SHORTEN_IDS))
|
||||
return error(_("could not transform the todo list"));
|
||||
|
||||
strbuf_reset(buf);
|
||||
|
||||
if (launch_sequence_editor(todo_file, buf, NULL)) {
|
||||
apply_autostash(opts);
|
||||
sequencer_remove_state(opts);
|
||||
todo_list_release(&todo_list);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
strbuf_stripspace(buf, 1);
|
||||
if (buf->len == 0) {
|
||||
apply_autostash(opts);
|
||||
sequencer_remove_state(opts);
|
||||
todo_list_release(&todo_list);
|
||||
|
||||
return error(_("nothing to do"));
|
||||
}
|
||||
|
||||
todo_list_release(&todo_list);
|
||||
|
||||
if (check_todo_list()) {
|
||||
checkout_onto(opts, onto_name, onto, orig_head);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (transform_todos(flags & ~(TODO_LIST_SHORTEN_IDS)))
|
||||
return error(_("could not transform the todo list"));
|
||||
|
||||
if (opts->allow_ff && skip_unnecessary_picks(&oid))
|
||||
return error(_("could not skip unnecessary pick commands"));
|
||||
|
||||
if (checkout_onto(opts, onto_name, oid_to_hex(&oid), orig_head))
|
||||
return -1;
|
||||
;
|
||||
if (require_clean_work_tree("rebase", "", 1, 1))
|
||||
return -1;
|
||||
|
||||
return sequencer_continue(opts);
|
||||
}
|
||||
|
||||
struct subject2item_entry {
|
||||
struct hashmap_entry entry;
|
||||
int i;
|
||||
|
||||
24
sequencer.h
24
sequencer.h
@@ -8,6 +8,7 @@ struct commit;
|
||||
|
||||
const char *git_path_commit_editmsg(void);
|
||||
const char *git_path_seq_dir(void);
|
||||
const char *rebase_path_todo(void);
|
||||
|
||||
#define APPEND_SIGNOFF_DEDUP (1u << 0)
|
||||
|
||||
@@ -62,6 +63,15 @@ struct replay_opts {
|
||||
};
|
||||
#define REPLAY_OPTS_INIT { .action = -1, .current_fixups = STRBUF_INIT }
|
||||
|
||||
enum missing_commit_check_level {
|
||||
MISSING_COMMIT_CHECK_IGNORE = 0,
|
||||
MISSING_COMMIT_CHECK_WARN,
|
||||
MISSING_COMMIT_CHECK_ERROR
|
||||
};
|
||||
|
||||
int write_message(const void *buf, size_t len, const char *filename,
|
||||
int append_eol);
|
||||
|
||||
/* Call this to setup defaults before parsing command line options */
|
||||
void sequencer_init_config(struct replay_opts *opts);
|
||||
int sequencer_pick_revisions(struct replay_opts *opts);
|
||||
@@ -84,8 +94,12 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,
|
||||
|
||||
int sequencer_add_exec_commands(const char *command);
|
||||
int transform_todos(unsigned flags);
|
||||
enum missing_commit_check_level get_missing_commit_check_level(void);
|
||||
int check_todo_list(void);
|
||||
int skip_unnecessary_picks(void);
|
||||
int complete_action(struct replay_opts *opts, unsigned flags,
|
||||
const char *shortrevisions, const char *onto_name,
|
||||
const char *onto, const char *orig_head, const char *cmd,
|
||||
unsigned autosquash);
|
||||
int rearrange_squash(void);
|
||||
|
||||
extern const char sign_off_header[];
|
||||
@@ -103,8 +117,16 @@ int update_head_with_reflog(const struct commit *old_head,
|
||||
void commit_post_rewrite(const struct commit *current_head,
|
||||
const struct object_id *new_head);
|
||||
|
||||
int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit);
|
||||
|
||||
#define SUMMARY_INITIAL_COMMIT (1 << 0)
|
||||
#define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
|
||||
void print_commit_summary(const char *prefix, const struct object_id *oid,
|
||||
unsigned int flags);
|
||||
#endif
|
||||
|
||||
void parse_strategy_opts(struct replay_opts *opts, char *raw_opts);
|
||||
int write_basic_state(struct replay_opts *opts, const char *head_name,
|
||||
const char *onto, const char *orig_head);
|
||||
|
||||
int skip_unnecessary_picks(struct object_id *output_oid);
|
||||
19
sha1-name.c
19
sha1-name.c
@@ -1471,6 +1471,25 @@ int get_oid(const char *name, struct object_id *oid)
|
||||
return get_oid_with_context(name, 0, oid, &unused);
|
||||
}
|
||||
|
||||
/*
|
||||
* This returns a non-zero value if the string (built using printf
|
||||
* format and the given arguments) is not a valid object.
|
||||
*/
|
||||
int get_oidf(struct object_id *oid, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int ret;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
|
||||
va_start(ap, fmt);
|
||||
strbuf_vaddf(&sb, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
ret = get_oid(sb.buf, oid);
|
||||
strbuf_release(&sb);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Many callers know that the user meant to name a commit-ish by
|
||||
|
||||
9
strbuf.c
9
strbuf.c
@@ -120,6 +120,15 @@ void strbuf_trim_trailing_dir_sep(struct strbuf *sb)
|
||||
sb->buf[sb->len] = '\0';
|
||||
}
|
||||
|
||||
void strbuf_trim_trailing_newline(struct strbuf *sb)
|
||||
{
|
||||
if (sb->len > 0 && sb->buf[sb->len - 1] == '\n') {
|
||||
if (--sb->len > 0 && sb->buf[sb->len - 1] == '\r')
|
||||
--sb->len;
|
||||
sb->buf[sb->len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void strbuf_ltrim(struct strbuf *sb)
|
||||
{
|
||||
char *b = sb->buf;
|
||||
|
||||
5
strbuf.h
5
strbuf.h
@@ -190,6 +190,9 @@ extern void strbuf_ltrim(struct strbuf *);
|
||||
/* Strip trailing directory separators */
|
||||
extern void strbuf_trim_trailing_dir_sep(struct strbuf *);
|
||||
|
||||
/* Strip trailing LF or CR/LF */
|
||||
extern void strbuf_trim_trailing_newline(struct strbuf *sb);
|
||||
|
||||
/**
|
||||
* Replace the contents of the strbuf with a reencoded form. Returns -1
|
||||
* on error, 0 on success.
|
||||
@@ -575,6 +578,8 @@ extern void strbuf_add_unique_abbrev(struct strbuf *sb,
|
||||
* file's contents are not read into the buffer upon completion.
|
||||
*/
|
||||
extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
|
||||
extern int launch_sequence_editor(const char *path, struct strbuf *buffer,
|
||||
const char *const *env);
|
||||
|
||||
extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *buf, size_t size);
|
||||
|
||||
|
||||
@@ -75,6 +75,16 @@ test_expect_success 'rebase --keep-empty' '
|
||||
test_line_count = 6 actual
|
||||
'
|
||||
|
||||
cat > expect <<EOF
|
||||
error: nothing to do
|
||||
EOF
|
||||
|
||||
test_expect_success 'rebase -i with empty HEAD' '
|
||||
set_fake_editor &&
|
||||
test_must_fail env FAKE_LINES="1 exec_true" git rebase -i HEAD^ >actual 2>&1 &&
|
||||
test_i18ncmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rebase -i with the exec command' '
|
||||
git checkout master &&
|
||||
(
|
||||
|
||||
192
t/t3903-stash.sh
192
t/t3903-stash.sh
@@ -8,22 +8,22 @@ test_description='Test git stash'
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'stash some dirty working directory' '
|
||||
echo 1 > file &&
|
||||
echo 1 >file &&
|
||||
git add file &&
|
||||
echo unrelated >other-file &&
|
||||
git add other-file &&
|
||||
test_tick &&
|
||||
git commit -m initial &&
|
||||
echo 2 > file &&
|
||||
echo 2 >file &&
|
||||
git add file &&
|
||||
echo 3 > file &&
|
||||
echo 3 >file &&
|
||||
test_tick &&
|
||||
git stash &&
|
||||
git diff-files --quiet &&
|
||||
git diff-index --cached --quiet HEAD
|
||||
'
|
||||
|
||||
cat > expect << EOF
|
||||
cat >expect <<EOF
|
||||
diff --git a/file b/file
|
||||
index 0cfbf08..00750ed 100644
|
||||
--- a/file
|
||||
@@ -35,7 +35,7 @@ EOF
|
||||
|
||||
test_expect_success 'parents of stash' '
|
||||
test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
|
||||
git diff stash^2..stash > output &&
|
||||
git diff stash^2..stash >output &&
|
||||
test_cmp output expect
|
||||
'
|
||||
|
||||
@@ -74,7 +74,7 @@ test_expect_success 'apply stashed changes' '
|
||||
|
||||
test_expect_success 'apply stashed changes (including index)' '
|
||||
git reset --hard HEAD^ &&
|
||||
echo 6 > other-file &&
|
||||
echo 6 >other-file &&
|
||||
git add other-file &&
|
||||
test_tick &&
|
||||
git commit -m other-file &&
|
||||
@@ -99,12 +99,12 @@ test_expect_success 'stash drop complains of extra options' '
|
||||
|
||||
test_expect_success 'drop top stash' '
|
||||
git reset --hard &&
|
||||
git stash list > stashlist1 &&
|
||||
echo 7 > file &&
|
||||
git stash list >expected &&
|
||||
echo 7 >file &&
|
||||
git stash &&
|
||||
git stash drop &&
|
||||
git stash list > stashlist2 &&
|
||||
test_cmp stashlist1 stashlist2 &&
|
||||
git stash list >actual &&
|
||||
test_cmp expected actual &&
|
||||
git stash apply &&
|
||||
test 3 = $(cat file) &&
|
||||
test 1 = $(git show :file) &&
|
||||
@@ -113,9 +113,9 @@ test_expect_success 'drop top stash' '
|
||||
|
||||
test_expect_success 'drop middle stash' '
|
||||
git reset --hard &&
|
||||
echo 8 > file &&
|
||||
echo 8 >file &&
|
||||
git stash &&
|
||||
echo 9 > file &&
|
||||
echo 9 >file &&
|
||||
git stash &&
|
||||
git stash drop stash@{1} &&
|
||||
test 2 = $(git stash list | wc -l) &&
|
||||
@@ -160,7 +160,7 @@ test_expect_success 'stash pop' '
|
||||
test 0 = $(git stash list | wc -l)
|
||||
'
|
||||
|
||||
cat > expect << EOF
|
||||
cat >expect <<EOF
|
||||
diff --git a/file2 b/file2
|
||||
new file mode 100644
|
||||
index 0000000..1fe912c
|
||||
@@ -170,7 +170,7 @@ index 0000000..1fe912c
|
||||
+bar2
|
||||
EOF
|
||||
|
||||
cat > expect1 << EOF
|
||||
cat >expect1 <<EOF
|
||||
diff --git a/file b/file
|
||||
index 257cc56..5716ca5 100644
|
||||
--- a/file
|
||||
@@ -180,7 +180,7 @@ index 257cc56..5716ca5 100644
|
||||
+bar
|
||||
EOF
|
||||
|
||||
cat > expect2 << EOF
|
||||
cat >expect2 <<EOF
|
||||
diff --git a/file b/file
|
||||
index 7601807..5716ca5 100644
|
||||
--- a/file
|
||||
@@ -198,79 +198,79 @@ index 0000000..1fe912c
|
||||
EOF
|
||||
|
||||
test_expect_success 'stash branch' '
|
||||
echo foo > file &&
|
||||
echo foo >file &&
|
||||
git commit file -m first &&
|
||||
echo bar > file &&
|
||||
echo bar2 > file2 &&
|
||||
echo bar >file &&
|
||||
echo bar2 >file2 &&
|
||||
git add file2 &&
|
||||
git stash &&
|
||||
echo baz > file &&
|
||||
echo baz >file &&
|
||||
git commit file -m second &&
|
||||
git stash branch stashbranch &&
|
||||
test refs/heads/stashbranch = $(git symbolic-ref HEAD) &&
|
||||
test $(git rev-parse HEAD) = $(git rev-parse master^) &&
|
||||
git diff --cached > output &&
|
||||
git diff --cached >output &&
|
||||
test_cmp output expect &&
|
||||
git diff > output &&
|
||||
git diff >output &&
|
||||
test_cmp output expect1 &&
|
||||
git add file &&
|
||||
git commit -m alternate\ second &&
|
||||
git diff master..stashbranch > output &&
|
||||
git diff master..stashbranch >output &&
|
||||
test_cmp output expect2 &&
|
||||
test 0 = $(git stash list | wc -l)
|
||||
'
|
||||
|
||||
test_expect_success 'apply -q is quiet' '
|
||||
echo foo > file &&
|
||||
echo foo >file &&
|
||||
git stash &&
|
||||
git stash apply -q > output.out 2>&1 &&
|
||||
git stash apply -q >output.out 2>&1 &&
|
||||
test_must_be_empty output.out
|
||||
'
|
||||
|
||||
test_expect_success 'save -q is quiet' '
|
||||
git stash save --quiet > output.out 2>&1 &&
|
||||
git stash save --quiet >output.out 2>&1 &&
|
||||
test_must_be_empty output.out
|
||||
'
|
||||
|
||||
test_expect_success 'pop -q is quiet' '
|
||||
git stash pop -q > output.out 2>&1 &&
|
||||
git stash pop -q >output.out 2>&1 &&
|
||||
test_must_be_empty output.out
|
||||
'
|
||||
|
||||
test_expect_success 'pop -q --index works and is quiet' '
|
||||
echo foo > file &&
|
||||
echo foo >file &&
|
||||
git add file &&
|
||||
git stash save --quiet &&
|
||||
git stash pop -q --index > output.out 2>&1 &&
|
||||
git stash pop -q --index >output.out 2>&1 &&
|
||||
test foo = "$(git show :file)" &&
|
||||
test_must_be_empty output.out
|
||||
'
|
||||
|
||||
test_expect_success 'drop -q is quiet' '
|
||||
git stash &&
|
||||
git stash drop -q > output.out 2>&1 &&
|
||||
git stash drop -q >output.out 2>&1 &&
|
||||
test_must_be_empty output.out
|
||||
'
|
||||
|
||||
test_expect_success 'stash -k' '
|
||||
echo bar3 > file &&
|
||||
echo bar4 > file2 &&
|
||||
echo bar3 >file &&
|
||||
echo bar4 >file2 &&
|
||||
git add file2 &&
|
||||
git stash -k &&
|
||||
test bar,bar4 = $(cat file),$(cat file2)
|
||||
'
|
||||
|
||||
test_expect_success 'stash --no-keep-index' '
|
||||
echo bar33 > file &&
|
||||
echo bar44 > file2 &&
|
||||
echo bar33 >file &&
|
||||
echo bar44 >file2 &&
|
||||
git add file2 &&
|
||||
git stash --no-keep-index &&
|
||||
test bar,bar2 = $(cat file),$(cat file2)
|
||||
'
|
||||
|
||||
test_expect_success 'stash --invalid-option' '
|
||||
echo bar5 > file &&
|
||||
echo bar6 > file2 &&
|
||||
echo bar5 >file &&
|
||||
echo bar6 >file2 &&
|
||||
git add file2 &&
|
||||
test_must_fail git stash --invalid-option &&
|
||||
test_must_fail git stash save --invalid-option &&
|
||||
@@ -444,6 +444,36 @@ test_expect_failure 'stash file to directory' '
|
||||
test foo = "$(cat file/file)"
|
||||
'
|
||||
|
||||
test_expect_success 'giving too many ref arguments does not modify files' '
|
||||
git stash clear &&
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
echo foo >file2 &&
|
||||
git stash &&
|
||||
echo bar >file2 &&
|
||||
git stash &&
|
||||
test-tool chmtime =123456789 file2 &&
|
||||
for type in apply pop "branch stash-branch"
|
||||
do
|
||||
test_must_fail git stash $type stash@{0} stash@{1} 2>err &&
|
||||
test_i18ngrep "Too many revisions" err &&
|
||||
test 123456789 = $(test-tool chmtime -g file2) || return 1
|
||||
done
|
||||
'
|
||||
|
||||
test_expect_success 'drop: too many arguments errors out (does nothing)' '
|
||||
git stash list >expect &&
|
||||
test_must_fail git stash drop stash@{0} stash@{1} 2>err &&
|
||||
test_i18ngrep "Too many revisions" err &&
|
||||
git stash list >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'show: too many arguments errors out (does nothing)' '
|
||||
test_must_fail git stash show stash@{0} stash@{1} 2>err 1>out &&
|
||||
test_i18ngrep "Too many revisions" err &&
|
||||
test_must_be_empty out
|
||||
'
|
||||
|
||||
test_expect_success 'stash create - no changes' '
|
||||
git stash clear &&
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
@@ -456,11 +486,12 @@ test_expect_success 'stash branch - no stashes on stack, stash-like argument' '
|
||||
git stash clear &&
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
git reset --hard &&
|
||||
echo foo >> file &&
|
||||
echo foo >>file &&
|
||||
STASH_ID=$(git stash create) &&
|
||||
git reset --hard &&
|
||||
git stash branch stash-branch ${STASH_ID} &&
|
||||
test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" &&
|
||||
test_when_finished "git reset --hard HEAD && git checkout master &&
|
||||
git branch -D stash-branch" &&
|
||||
test $(git ls-files --modified | wc -l) -eq 1
|
||||
'
|
||||
|
||||
@@ -468,25 +499,31 @@ test_expect_success 'stash branch - stashes on stack, stash-like argument' '
|
||||
git stash clear &&
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
git reset --hard &&
|
||||
echo foo >> file &&
|
||||
echo foo >>file &&
|
||||
git stash &&
|
||||
test_when_finished "git stash drop" &&
|
||||
echo bar >> file &&
|
||||
echo bar >>file &&
|
||||
STASH_ID=$(git stash create) &&
|
||||
git reset --hard &&
|
||||
git stash branch stash-branch ${STASH_ID} &&
|
||||
test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" &&
|
||||
test_when_finished "git reset --hard HEAD && git checkout master &&
|
||||
git branch -D stash-branch" &&
|
||||
test $(git ls-files --modified | wc -l) -eq 1
|
||||
'
|
||||
|
||||
test_expect_success 'stash branch complains with no arguments' '
|
||||
test_must_fail git stash branch 2>err &&
|
||||
test_i18ngrep "No branch name specified" err
|
||||
'
|
||||
|
||||
test_expect_success 'stash show format defaults to --stat' '
|
||||
git stash clear &&
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
git reset --hard &&
|
||||
echo foo >> file &&
|
||||
echo foo >>file &&
|
||||
git stash &&
|
||||
test_when_finished "git stash drop" &&
|
||||
echo bar >> file &&
|
||||
echo bar >>file &&
|
||||
STASH_ID=$(git stash create) &&
|
||||
git reset --hard &&
|
||||
cat >expected <<-EOF &&
|
||||
@@ -501,10 +538,10 @@ test_expect_success 'stash show - stashes on stack, stash-like argument' '
|
||||
git stash clear &&
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
git reset --hard &&
|
||||
echo foo >> file &&
|
||||
echo foo >>file &&
|
||||
git stash &&
|
||||
test_when_finished "git stash drop" &&
|
||||
echo bar >> file &&
|
||||
echo bar >>file &&
|
||||
STASH_ID=$(git stash create) &&
|
||||
git reset --hard &&
|
||||
echo "1 0 file" >expected &&
|
||||
@@ -516,10 +553,10 @@ test_expect_success 'stash show -p - stashes on stack, stash-like argument' '
|
||||
git stash clear &&
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
git reset --hard &&
|
||||
echo foo >> file &&
|
||||
echo foo >>file &&
|
||||
git stash &&
|
||||
test_when_finished "git stash drop" &&
|
||||
echo bar >> file &&
|
||||
echo bar >>file &&
|
||||
STASH_ID=$(git stash create) &&
|
||||
git reset --hard &&
|
||||
cat >expected <<-EOF &&
|
||||
@@ -539,7 +576,7 @@ test_expect_success 'stash show - no stashes on stack, stash-like argument' '
|
||||
git stash clear &&
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
git reset --hard &&
|
||||
echo foo >> file &&
|
||||
echo foo >>file &&
|
||||
STASH_ID=$(git stash create) &&
|
||||
git reset --hard &&
|
||||
echo "1 0 file" >expected &&
|
||||
@@ -551,7 +588,7 @@ test_expect_success 'stash show -p - no stashes on stack, stash-like argument' '
|
||||
git stash clear &&
|
||||
test_when_finished "git reset --hard HEAD" &&
|
||||
git reset --hard &&
|
||||
echo foo >> file &&
|
||||
echo foo >>file &&
|
||||
STASH_ID=$(git stash create) &&
|
||||
git reset --hard &&
|
||||
cat >expected <<-EOF &&
|
||||
@@ -567,13 +604,13 @@ test_expect_success 'stash show -p - no stashes on stack, stash-like argument' '
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'stash drop - fail early if specified stash is not a stash reference' '
|
||||
test_expect_success 'drop: fail early if specified stash is not a stash ref' '
|
||||
git stash clear &&
|
||||
test_when_finished "git reset --hard HEAD && git stash clear" &&
|
||||
git reset --hard &&
|
||||
echo foo > file &&
|
||||
echo foo >file &&
|
||||
git stash &&
|
||||
echo bar > file &&
|
||||
echo bar >file &&
|
||||
git stash &&
|
||||
test_must_fail git stash drop $(git rev-parse stash@{0}) &&
|
||||
git stash pop &&
|
||||
@@ -581,13 +618,13 @@ test_expect_success 'stash drop - fail early if specified stash is not a stash r
|
||||
git reset --hard HEAD
|
||||
'
|
||||
|
||||
test_expect_success 'stash pop - fail early if specified stash is not a stash reference' '
|
||||
test_expect_success 'pop: fail early if specified stash is not a stash ref' '
|
||||
git stash clear &&
|
||||
test_when_finished "git reset --hard HEAD && git stash clear" &&
|
||||
git reset --hard &&
|
||||
echo foo > file &&
|
||||
echo foo >file &&
|
||||
git stash &&
|
||||
echo bar > file &&
|
||||
echo bar >file &&
|
||||
git stash &&
|
||||
test_must_fail git stash pop $(git rev-parse stash@{0}) &&
|
||||
git stash pop &&
|
||||
@@ -597,8 +634,8 @@ test_expect_success 'stash pop - fail early if specified stash is not a stash re
|
||||
|
||||
test_expect_success 'ref with non-existent reflog' '
|
||||
git stash clear &&
|
||||
echo bar5 > file &&
|
||||
echo bar6 > file2 &&
|
||||
echo bar5 >file &&
|
||||
echo bar6 >file2 &&
|
||||
git add file2 &&
|
||||
git stash &&
|
||||
test_must_fail git rev-parse --quiet --verify does-not-exist &&
|
||||
@@ -618,8 +655,8 @@ test_expect_success 'ref with non-existent reflog' '
|
||||
test_expect_success 'invalid ref of the form stash@{n}, n >= N' '
|
||||
git stash clear &&
|
||||
test_must_fail git stash drop stash@{0} &&
|
||||
echo bar5 > file &&
|
||||
echo bar6 > file2 &&
|
||||
echo bar5 >file &&
|
||||
echo bar6 >file2 &&
|
||||
git add file2 &&
|
||||
git stash &&
|
||||
test_must_fail git stash drop stash@{1} &&
|
||||
@@ -645,7 +682,7 @@ test_expect_success 'invalid ref of the form "n", n >= N' '
|
||||
git stash drop
|
||||
'
|
||||
|
||||
test_expect_success 'stash branch should not drop the stash if the branch exists' '
|
||||
test_expect_success 'branch: do not drop the stash if the branch exists' '
|
||||
git stash clear &&
|
||||
echo foo >file &&
|
||||
git add file &&
|
||||
@@ -656,7 +693,7 @@ test_expect_success 'stash branch should not drop the stash if the branch exists
|
||||
git rev-parse stash@{0} --
|
||||
'
|
||||
|
||||
test_expect_success 'stash branch should not drop the stash if the apply fails' '
|
||||
test_expect_success 'branch: should not drop the stash if the apply fails' '
|
||||
git stash clear &&
|
||||
git reset HEAD~1 --hard &&
|
||||
echo foo >file &&
|
||||
@@ -670,7 +707,7 @@ test_expect_success 'stash branch should not drop the stash if the apply fails'
|
||||
git rev-parse stash@{0} --
|
||||
'
|
||||
|
||||
test_expect_success 'stash apply shows status same as git status (relative to current directory)' '
|
||||
test_expect_success 'apply: show same status as git status (relative to ./)' '
|
||||
git stash clear &&
|
||||
echo 1 >subdir/subfile1 &&
|
||||
echo 2 >subdir/subfile2 &&
|
||||
@@ -689,7 +726,7 @@ test_expect_success 'stash apply shows status same as git status (relative to cu
|
||||
test_i18ncmp expect actual
|
||||
'
|
||||
|
||||
cat > expect << EOF
|
||||
cat >expect <<EOF
|
||||
diff --git a/HEAD b/HEAD
|
||||
new file mode 100644
|
||||
index 0000000..fe0cbee
|
||||
@@ -702,14 +739,14 @@ EOF
|
||||
test_expect_success 'stash where working directory contains "HEAD" file' '
|
||||
git stash clear &&
|
||||
git reset --hard &&
|
||||
echo file-not-a-ref > HEAD &&
|
||||
echo file-not-a-ref >HEAD &&
|
||||
git add HEAD &&
|
||||
test_tick &&
|
||||
git stash &&
|
||||
git diff-files --quiet &&
|
||||
git diff-index --cached --quiet HEAD &&
|
||||
test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" &&
|
||||
git diff stash^..stash > output &&
|
||||
git diff stash^..stash >output &&
|
||||
test_cmp output expect
|
||||
'
|
||||
|
||||
@@ -1011,7 +1048,7 @@ test_expect_success 'stash push -p with pathspec shows no changes only once' '
|
||||
test_i18ncmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stash push with pathspec shows no changes when there are none' '
|
||||
test_expect_success 'push <pathspec>: show no changes when there are none' '
|
||||
>foo &&
|
||||
git add foo &&
|
||||
git commit -m "tmp" &&
|
||||
@@ -1021,12 +1058,35 @@ test_expect_success 'stash push with pathspec shows no changes when there are no
|
||||
test_i18ncmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'stash push with pathspec not in the repository errors out' '
|
||||
test_expect_success 'push: <pathspec> not in the repository errors out' '
|
||||
>untracked &&
|
||||
test_must_fail git stash push untracked &&
|
||||
test_path_is_file untracked
|
||||
'
|
||||
|
||||
test_expect_success 'push: -q is quiet with changes' '
|
||||
>foo &&
|
||||
git add foo &&
|
||||
git stash push -q >output 2>&1 &&
|
||||
test_must_be_empty output
|
||||
'
|
||||
|
||||
test_expect_success 'push: -q is quiet with no changes' '
|
||||
git stash push -q >output 2>&1 &&
|
||||
test_must_be_empty output
|
||||
'
|
||||
|
||||
test_expect_success 'push: -q is quiet even if there is no initial commit' '
|
||||
git init foo_dir &&
|
||||
test_when_finished rm -rf foo_dir &&
|
||||
(
|
||||
cd foo_dir &&
|
||||
>bar &&
|
||||
test_must_fail git stash push -q >output 2>&1 &&
|
||||
test_must_be_empty output
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'untracked files are left in place when -u is not given' '
|
||||
>file &&
|
||||
git add file &&
|
||||
|
||||
81
t/t3907-stash-show-config.sh
Executable file
81
t/t3907-stash-show-config.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='Test git stash show configuration.'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'setup' '
|
||||
test_commit file
|
||||
'
|
||||
|
||||
# takes three parameters:
|
||||
# 1. the stash.showStat value (or "<unset>")
|
||||
# 2. the stash.showPatch value (or "<unset>")
|
||||
# 3. the diff options of the expected output (or nothing for no output)
|
||||
test_stat_and_patch () {
|
||||
if test "<unset>" = "$1"
|
||||
then
|
||||
test_might_fail git config --unset stash.showStat
|
||||
else
|
||||
test_config stash.showStat "$1"
|
||||
fi &&
|
||||
|
||||
if test "<unset>" = "$2"
|
||||
then
|
||||
test_might_fail git config --unset stash.showPatch
|
||||
else
|
||||
test_config stash.showPatch "$2"
|
||||
fi &&
|
||||
|
||||
shift &&
|
||||
shift &&
|
||||
echo 2 >file.t &&
|
||||
git diff "$@" >expect &&
|
||||
git stash &&
|
||||
git stash show >actual &&
|
||||
|
||||
if test -z "$1"
|
||||
then
|
||||
test_must_be_empty actual
|
||||
else
|
||||
test_cmp expect actual
|
||||
fi
|
||||
}
|
||||
|
||||
test_expect_success 'showStat unset showPatch unset' '
|
||||
test_stat_and_patch "<unset>" "<unset>" --stat
|
||||
'
|
||||
|
||||
test_expect_success 'showStat unset showPatch false' '
|
||||
test_stat_and_patch "<unset>" false --stat
|
||||
'
|
||||
|
||||
test_expect_success 'showStat unset showPatch true' '
|
||||
test_stat_and_patch "<unset>" true --stat -p
|
||||
'
|
||||
|
||||
test_expect_success 'showStat false showPatch unset' '
|
||||
test_stat_and_patch false "<unset>"
|
||||
'
|
||||
|
||||
test_expect_success 'showStat false showPatch false' '
|
||||
test_stat_and_patch false false
|
||||
'
|
||||
|
||||
test_expect_success 'showStat false showPatch true' '
|
||||
test_stat_and_patch false true -p
|
||||
'
|
||||
|
||||
test_expect_success 'showStat true showPatch unset' '
|
||||
test_stat_and_patch true "<unset>" --stat
|
||||
'
|
||||
|
||||
test_expect_success 'showStat true showPatch false' '
|
||||
test_stat_and_patch true false --stat
|
||||
'
|
||||
|
||||
test_expect_success 'showStat true showPatch true' '
|
||||
test_stat_and_patch true true --stat -p
|
||||
'
|
||||
|
||||
test_done
|
||||
Reference in New Issue
Block a user