Merge builtin-stash-rebase-v2

This trick was performed by rebasing the builtin-stash-rebase branch
thicket via `git rebase -kir v2.19.0-rc2`, replacing all branches that
made it into `pu` by their current versions (and also the builtin-stash
by the newest iteration as of ungps/git), and then calling these
commands:

	# save current tip
	tip=$(git rev-parse HEAD)

	# revert previous merge
	git reset --hard git-for-windows/master^0
	git revert -n -m 1 HEAD
	git commit --squash HEAD -s -m "Let's drop this"

	# now perform the 3-way merge with v2.19.0-rc2 as base
	git merge-recursive v2.19.0-rc2 -- HEAD $tip
	git merge --ff-only \
		$(git commit-tree -p HEAD -p $tip -m "Merge" \
			$(git write-tree))
	git commit -c HEAD^^ --amend -s

The merge-recursive dance is necessary because of the merging-rebases:
the fake merges with which these start are mistaken by `git merge` to
mean that the branches were already merged, when the fake merges undid
the corresponding changes.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This commit is contained in:
Jameson Miller
2018-08-28 16:24:35 -04:00
30 changed files with 4518 additions and 391 deletions

5
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,286 @@
#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")),
OPT_STRING('S', "gpg-sign", &opts.gpg_sign, N_("gpg-sign"),
N_("GPG-sign commits")),
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;
}

1721
builtin/rebase.c Normal file

File diff suppressed because it is too large Load Diff

1598
builtin/stash.c Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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