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:
Jameson Miller
2018-08-28 16:24:35 -04:00
committed by Johannes Schindelin
20 changed files with 2626 additions and 303 deletions

2
.gitignore vendored
View File

@@ -82,6 +82,8 @@
/git-interpret-trailers
/git-instaweb
/git-legacy-rebase
/git-legacy-rebase--interactive
/git-legacy-stash
/git-log
/git-ls-files
/git-ls-remote

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

@@ -617,12 +617,13 @@ SCRIPT_SH += git-merge-resolve.sh
SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-quiltimport.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
@@ -1119,6 +1120,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

View File

@@ -225,6 +225,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

@@ -141,7 +141,8 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
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
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")),
@@ -192,6 +193,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
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()
};
@@ -263,6 +268,17 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
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);
}

View File

@@ -246,6 +246,37 @@ static int read_basic_state(struct rebase_options *opts)
return 0;
}
static int write_basic_state(struct rebase_options *opts)
{
write_file(state_dir_path("head-name", opts), "%s",
opts->head_name ? opts->head_name : "detached HEAD");
write_file(state_dir_path("onto", opts), "%s",
opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");
write_file(state_dir_path("orig-head", opts), "%s",
oid_to_hex(&opts->orig_head));
write_file(state_dir_path("quiet", opts), "%s",
opts->flags & REBASE_NO_QUIET ? "" : "t");
if (opts->flags & REBASE_VERBOSE)
write_file(state_dir_path("verbose", opts), "%s", "");
if (opts->strategy)
write_file(state_dir_path("strategy", opts), "%s",
opts->strategy);
if (opts->strategy_opts)
write_file(state_dir_path("strategy_opts", opts), "%s",
opts->strategy_opts);
if (opts->allow_rerere_autoupdate >= 0)
write_file(state_dir_path("allow_rerere_autoupdate", opts),
"-%s-rerere-autoupdate",
opts->allow_rerere_autoupdate ? "" : "-no");
if (opts->gpg_sign_opt)
write_file(state_dir_path("gpg_sign_opt", opts), "%s",
opts->gpg_sign_opt);
if (opts->signoff)
write_file(state_dir_path("strategy", opts), "--signoff");
return 0;
}
static int apply_autostash(struct rebase_options *opts)
{
const char *path = state_dir_path("autostash", opts);
@@ -333,199 +364,6 @@ static void add_var(struct strbuf *buf, const char *name, const char *value)
}
}
static const char *resolvemsg =
N_("Resolve all conflicts manually, mark them as resolved with\n"
"\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n"
"You can instead skip this commit: run \"git rebase --skip\".\n"
"To abort and get back to the state before \"git rebase\", run "
"\"git rebase --abort\".");
static int run_specific_rebase(struct rebase_options *opts)
{
const char *argv[] = { NULL, NULL };
struct strbuf script_snippet = STRBUF_INIT, buf = STRBUF_INIT;
int status;
const char *backend, *backend_func;
if (opts->type == REBASE_INTERACTIVE) {
/* Run builtin interactive rebase */
struct child_process child = CHILD_PROCESS_INIT;
argv_array_pushf(&child.env_array, "GIT_CHERRY_PICK_HELP=%s",
resolvemsg);
if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
argv_array_push(&child.env_array, "GIT_EDITOR=:");
opts->autosquash = 0;
}
child.git_cmd = 1;
argv_array_push(&child.args, "rebase--interactive");
if (opts->action)
argv_array_pushf(&child.args, "--%s", opts->action);
if (opts->keep_empty)
argv_array_push(&child.args, "--keep-empty");
if (opts->rebase_merges)
argv_array_push(&child.args, "--rebase-merges");
if (opts->rebase_cousins)
argv_array_push(&child.args, "--rebase-cousins");
if (opts->autosquash)
argv_array_push(&child.args, "--autosquash");
if (opts->flags & REBASE_VERBOSE)
argv_array_push(&child.args, "--verbose");
if (opts->flags & REBASE_FORCE)
argv_array_push(&child.args, "--no-ff");
if (opts->restrict_revision)
argv_array_pushf(&child.args,
"--restrict-revision=^%s",
oid_to_hex(&opts->restrict_revision->object.oid));
if (opts->upstream)
argv_array_pushf(&child.args, "--upstream=%s",
oid_to_hex(&opts->upstream->object.oid));
if (opts->onto)
argv_array_pushf(&child.args, "--onto=%s",
oid_to_hex(&opts->onto->object.oid));
if (opts->squash_onto)
argv_array_pushf(&child.args, "--squash-onto=%s",
oid_to_hex(opts->squash_onto));
if (opts->onto_name)
argv_array_pushf(&child.args, "--onto-name=%s",
opts->onto_name);
argv_array_pushf(&child.args, "--head-name=%s",
opts->head_name ?
opts->head_name : "detached HEAD");
if (opts->strategy)
argv_array_pushf(&child.args, "--strategy=%s",
opts->strategy);
if (opts->strategy_opts)
argv_array_pushf(&child.args, "--strategy-opts=%s",
opts->strategy_opts);
if (opts->switch_to)
argv_array_pushf(&child.args, "--switch-to=%s",
opts->switch_to);
if (opts->cmd)
argv_array_pushf(&child.args, "--cmd=%s", opts->cmd);
if (opts->allow_empty_message)
argv_array_push(&child.args, "--allow-empty-message");
if (opts->allow_rerere_autoupdate > 0)
argv_array_push(&child.args, "--rerere-autoupdate");
else if (opts->allow_rerere_autoupdate == 0)
argv_array_push(&child.args, "--no-rerere-autoupdate");
if (opts->gpg_sign_opt)
argv_array_push(&child.args, opts->gpg_sign_opt);
if (opts->signoff)
argv_array_push(&child.args, "--signoff");
status = run_command(&child);
goto finished_rebase;
}
add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir()));
add_var(&script_snippet, "state_dir", opts->state_dir);
add_var(&script_snippet, "upstream_name", opts->upstream_name);
add_var(&script_snippet, "upstream", opts->upstream ?
oid_to_hex(&opts->upstream->object.oid) : NULL);
add_var(&script_snippet, "head_name",
opts->head_name ? opts->head_name : "detached HEAD");
add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head));
add_var(&script_snippet, "onto", opts->onto ?
oid_to_hex(&opts->onto->object.oid) : NULL);
add_var(&script_snippet, "onto_name", opts->onto_name);
add_var(&script_snippet, "revisions", opts->revisions);
add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
add_var(&script_snippet, "GIT_QUIET",
opts->flags & REBASE_NO_QUIET ? "" : "t");
sq_quote_argv_pretty(&buf, opts->git_am_opts.argv);
add_var(&script_snippet, "git_am_opt", buf.buf);
strbuf_release(&buf);
add_var(&script_snippet, "verbose",
opts->flags & REBASE_VERBOSE ? "t" : "");
add_var(&script_snippet, "diffstat",
opts->flags & REBASE_DIFFSTAT ? "t" : "");
add_var(&script_snippet, "force_rebase",
opts->flags & REBASE_FORCE ? "t" : "");
if (opts->switch_to)
add_var(&script_snippet, "switch_to", opts->switch_to);
add_var(&script_snippet, "action", opts->action ? opts->action : "");
add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : "");
add_var(&script_snippet, "allow_rerere_autoupdate",
opts->allow_rerere_autoupdate < 0 ? "" :
opts->allow_rerere_autoupdate ?
"--rerere-autoupdate" : "--no-rerere-autoupdate");
add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
add_var(&script_snippet, "cmd", opts->cmd);
add_var(&script_snippet, "allow_empty_message",
opts->allow_empty_message ? "--allow-empty-message" : "");
add_var(&script_snippet, "rebase_merges",
opts->rebase_merges ? "t" : "");
add_var(&script_snippet, "rebase_cousins",
opts->rebase_cousins ? "t" : "");
add_var(&script_snippet, "strategy", opts->strategy);
add_var(&script_snippet, "strategy_opts", opts->strategy_opts);
add_var(&script_snippet, "rebase_root", opts->root ? "t" : "");
add_var(&script_snippet, "squash_onto",
opts->squash_onto ? oid_to_hex(opts->squash_onto) : "");
add_var(&script_snippet, "git_format_patch_opt",
opts->git_format_patch_opt.buf);
if (is_interactive(opts) &&
!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
strbuf_addstr(&script_snippet,
"GIT_EDITOR=:; export GIT_EDITOR; ");
opts->autosquash = 0;
}
switch (opts->type) {
case REBASE_AM:
backend = "git-rebase--am";
backend_func = "git_rebase__am";
break;
case REBASE_MERGE:
backend = "git-rebase--merge";
backend_func = "git_rebase__merge";
break;
case REBASE_PRESERVE_MERGES:
backend = "git-rebase--preserve-merges";
backend_func = "git_rebase__preserve_merges";
break;
default:
BUG("Unhandled rebase type %d", opts->type);
break;
}
strbuf_addf(&script_snippet,
". git-sh-setup && . git-rebase--common &&"
" . %s && %s", backend, backend_func);
argv[0] = script_snippet.buf;
status = run_command_v_opt(argv, RUN_USING_SHELL);
finished_rebase:
if (opts->dont_finish_rebase)
; /* do nothing */
else if (opts->type == REBASE_INTERACTIVE)
; /* interactive rebase cleans up after itself */
else if (status == 0) {
if (!file_exists(state_dir_path("stopped-sha", opts)))
finish_rebase(opts);
} else if (status == 2) {
struct strbuf dir = STRBUF_INIT;
apply_autostash(opts);
strbuf_addstr(&dir, opts->state_dir);
remove_dir_recursively(&dir, 0);
strbuf_release(&dir);
die("Nothing to do");
}
strbuf_release(&script_snippet);
return status ? -1 : 0;
}
#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
#define RESET_HEAD_DETACH (1<<0)
@@ -631,10 +469,11 @@ static int reset_head(struct object_id *oid, const char *action,
detach_head ? REF_NO_DEREF : 0,
UPDATE_REFS_MSG_ON_ERR);
else {
ret = create_symref("HEAD", switch_to_branch, msg.buf);
ret = update_ref(reflog_orig_head, switch_to_branch, oid,
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
if (!ret)
ret = update_ref(reflog_head, "HEAD", oid, NULL, 0,
UPDATE_REFS_MSG_ON_ERR);
ret = create_symref("HEAD", switch_to_branch,
reflog_head);
}
leave_reset_head:
@@ -645,6 +484,351 @@ leave_reset_head:
return ret;
}
static int move_to_original_branch(struct rebase_options *opts)
{
struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
int ret;
if (!opts->head_name)
return 0; /* nothing to move back to */
if (!opts->onto)
BUG("move_to_original_branch without onto");
strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s",
opts->head_name, oid_to_hex(&opts->onto->object.oid));
strbuf_addf(&head_reflog, "rebase finished: returning to %s",
opts->head_name);
ret = reset_head(NULL, "checkout", opts->head_name,
RESET_HEAD_REFS_ONLY,
orig_head_reflog.buf, head_reflog.buf);
strbuf_release(&orig_head_reflog);
strbuf_release(&head_reflog);
return ret;
}
static const char *resolvemsg =
N_("Resolve all conflicts manually, mark them as resolved with\n"
"\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n"
"You can instead skip this commit: run \"git rebase --skip\".\n"
"To abort and get back to the state before \"git rebase\", run "
"\"git rebase --abort\".");
static int run_am(struct rebase_options *opts)
{
struct child_process am = CHILD_PROCESS_INIT;
struct child_process format_patch = CHILD_PROCESS_INIT;
struct strbuf revisions = STRBUF_INIT;
int status;
char *rebased_patches;
am.git_cmd = 1;
argv_array_push(&am.args, "am");
if (opts->action && !strcmp("continue", opts->action)) {
argv_array_push(&am.args, "--resolved");
argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
if (opts->gpg_sign_opt)
argv_array_push(&am.args, opts->gpg_sign_opt);
status = run_command(&am);
if (status)
return status;
discard_cache();
return move_to_original_branch(opts);
}
if (opts->action && !strcmp("skip", opts->action)) {
argv_array_push(&am.args, "--skip");
argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
status = run_command(&am);
if (status)
return status;
discard_cache();
return move_to_original_branch(opts);
}
if (opts->action && !strcmp("show-current-patch", opts->action)) {
argv_array_push(&am.args, "--show-current-patch");
return run_command(&am);
}
strbuf_addf(&revisions, "%s...%s",
oid_to_hex(opts->root ?
/* this is now equivalent to ! -z "$upstream" */
&opts->onto->object.oid :
&opts->upstream->object.oid),
oid_to_hex(&opts->orig_head));
rebased_patches = xstrdup(git_path("rebased-patches"));
format_patch.out = open(rebased_patches,
O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (format_patch.out < 0) {
status = error_errno(_("could not write '%s'"),
rebased_patches);
free(rebased_patches);
argv_array_clear(&am.args);
return status;
}
format_patch.git_cmd = 1;
argv_array_pushl(&format_patch.args, "format-patch", "-k", "--stdout",
"--full-index", "--cherry-pick", "--right-only",
"--src-prefix=a/", "--dst-prefix=b/", "--no-renames",
"--no-cover-letter", "--pretty=mboxrd", NULL);
if (opts->git_format_patch_opt.len)
argv_array_split(&format_patch.args,
opts->git_format_patch_opt.buf);
argv_array_push(&format_patch.args, revisions.buf);
if (opts->restrict_revision)
argv_array_pushf(&format_patch.args, "^%s",
oid_to_hex(&opts->restrict_revision->object.oid));
status = run_command(&format_patch);
if (status) {
unlink(rebased_patches);
free(rebased_patches);
argv_array_clear(&am.args);
reset_head(&opts->orig_head, "checkout", opts->head_name, 0,
"HEAD", NULL);
error(_("\ngit encountered an error while preparing the "
"patches to replay\n"
"these revisions:\n"
"\n %s\n\n"
"As a result, git cannot rebase them."),
opts->revisions);
strbuf_release(&revisions);
return status;
}
strbuf_release(&revisions);
am.in = open(rebased_patches, O_RDONLY);
if (am.in < 0) {
status = error_errno(_("could not read '%s'"),
rebased_patches);
free(rebased_patches);
argv_array_clear(&am.args);
return status;
}
argv_array_pushv(&am.args, opts->git_am_opts.argv);
argv_array_push(&am.args, "--rebasing");
argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
argv_array_push(&am.args, "--patch-format=mboxrd");
if (opts->allow_rerere_autoupdate > 0)
argv_array_push(&am.args, "--rerere-autoupdate");
else if (opts->allow_rerere_autoupdate == 0)
argv_array_push(&am.args, "--no-rerere-autoupdate");
if (opts->gpg_sign_opt)
argv_array_push(&am.args, opts->gpg_sign_opt);
status = run_command(&am);
unlink(rebased_patches);
free(rebased_patches);
if (!status) {
discard_cache();
return move_to_original_branch(opts);
}
if (is_directory(opts->state_dir))
write_basic_state(opts);
return status;
}
static int run_specific_rebase(struct rebase_options *opts)
{
const char *argv[] = { NULL, NULL };
struct strbuf script_snippet = STRBUF_INIT, buf = STRBUF_INIT;
int status;
const char *backend, *backend_func;
if (opts->type == REBASE_INTERACTIVE) {
/* Run builtin interactive rebase */
struct child_process child = CHILD_PROCESS_INIT;
argv_array_pushf(&child.env_array, "GIT_CHERRY_PICK_HELP=%s",
resolvemsg);
if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
argv_array_push(&child.env_array, "GIT_EDITOR=:");
opts->autosquash = 0;
}
child.git_cmd = 1;
argv_array_push(&child.args, "rebase--interactive");
if (opts->action)
argv_array_pushf(&child.args, "--%s", opts->action);
if (opts->keep_empty)
argv_array_push(&child.args, "--keep-empty");
if (opts->rebase_merges)
argv_array_push(&child.args, "--rebase-merges");
if (opts->rebase_cousins)
argv_array_push(&child.args, "--rebase-cousins");
if (opts->autosquash)
argv_array_push(&child.args, "--autosquash");
if (opts->flags & REBASE_VERBOSE)
argv_array_push(&child.args, "--verbose");
if (opts->flags & REBASE_FORCE)
argv_array_push(&child.args, "--no-ff");
if (opts->restrict_revision)
argv_array_pushf(&child.args,
"--restrict-revision=^%s",
oid_to_hex(&opts->restrict_revision->object.oid));
if (opts->upstream)
argv_array_pushf(&child.args, "--upstream=%s",
oid_to_hex(&opts->upstream->object.oid));
if (opts->onto)
argv_array_pushf(&child.args, "--onto=%s",
oid_to_hex(&opts->onto->object.oid));
if (opts->squash_onto)
argv_array_pushf(&child.args, "--squash-onto=%s",
oid_to_hex(opts->squash_onto));
if (opts->onto_name)
argv_array_pushf(&child.args, "--onto-name=%s",
opts->onto_name);
argv_array_pushf(&child.args, "--head-name=%s",
opts->head_name ?
opts->head_name : "detached HEAD");
if (opts->strategy)
argv_array_pushf(&child.args, "--strategy=%s",
opts->strategy);
if (opts->strategy_opts)
argv_array_pushf(&child.args, "--strategy-opts=%s",
opts->strategy_opts);
if (opts->switch_to)
argv_array_pushf(&child.args, "--switch-to=%s",
opts->switch_to);
if (opts->cmd)
argv_array_pushf(&child.args, "--cmd=%s", opts->cmd);
if (opts->allow_empty_message)
argv_array_push(&child.args, "--allow-empty-message");
if (opts->allow_rerere_autoupdate > 0)
argv_array_push(&child.args, "--rerere-autoupdate");
else if (opts->allow_rerere_autoupdate == 0)
argv_array_push(&child.args, "--no-rerere-autoupdate");
if (opts->gpg_sign_opt)
argv_array_push(&child.args, opts->gpg_sign_opt);
if (opts->signoff)
argv_array_push(&child.args, "--signoff");
status = run_command(&child);
goto finished_rebase;
}
if (opts->type == REBASE_AM) {
status = run_am(opts);
goto finished_rebase;
}
add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir()));
add_var(&script_snippet, "state_dir", opts->state_dir);
add_var(&script_snippet, "upstream_name", opts->upstream_name);
add_var(&script_snippet, "upstream", opts->upstream ?
oid_to_hex(&opts->upstream->object.oid) : NULL);
add_var(&script_snippet, "head_name",
opts->head_name ? opts->head_name : "detached HEAD");
add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head));
add_var(&script_snippet, "onto", opts->onto ?
oid_to_hex(&opts->onto->object.oid) : NULL);
add_var(&script_snippet, "onto_name", opts->onto_name);
add_var(&script_snippet, "revisions", opts->revisions);
add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
add_var(&script_snippet, "GIT_QUIET",
opts->flags & REBASE_NO_QUIET ? "" : "t");
sq_quote_argv_pretty(&buf, opts->git_am_opts.argv);
add_var(&script_snippet, "git_am_opt", buf.buf);
strbuf_release(&buf);
add_var(&script_snippet, "verbose",
opts->flags & REBASE_VERBOSE ? "t" : "");
add_var(&script_snippet, "diffstat",
opts->flags & REBASE_DIFFSTAT ? "t" : "");
add_var(&script_snippet, "force_rebase",
opts->flags & REBASE_FORCE ? "t" : "");
if (opts->switch_to)
add_var(&script_snippet, "switch_to", opts->switch_to);
add_var(&script_snippet, "action", opts->action ? opts->action : "");
add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : "");
add_var(&script_snippet, "allow_rerere_autoupdate",
opts->allow_rerere_autoupdate < 0 ? "" :
opts->allow_rerere_autoupdate ?
"--rerere-autoupdate" : "--no-rerere-autoupdate");
add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
add_var(&script_snippet, "cmd", opts->cmd);
add_var(&script_snippet, "allow_empty_message",
opts->allow_empty_message ? "--allow-empty-message" : "");
add_var(&script_snippet, "rebase_merges",
opts->rebase_merges ? "t" : "");
add_var(&script_snippet, "rebase_cousins",
opts->rebase_cousins ? "t" : "");
add_var(&script_snippet, "strategy", opts->strategy);
add_var(&script_snippet, "strategy_opts", opts->strategy_opts);
add_var(&script_snippet, "rebase_root", opts->root ? "t" : "");
add_var(&script_snippet, "squash_onto",
opts->squash_onto ? oid_to_hex(opts->squash_onto) : "");
add_var(&script_snippet, "git_format_patch_opt",
opts->git_format_patch_opt.buf);
if (is_interactive(opts) &&
!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
strbuf_addstr(&script_snippet,
"GIT_EDITOR=:; export GIT_EDITOR; ");
opts->autosquash = 0;
}
switch (opts->type) {
case REBASE_AM:
backend = "git-rebase--am";
backend_func = "git_rebase__am";
break;
case REBASE_MERGE:
backend = "git-rebase--merge";
backend_func = "git_rebase__merge";
break;
case REBASE_PRESERVE_MERGES:
backend = "git-rebase--preserve-merges";
backend_func = "git_rebase__preserve_merges";
break;
default:
BUG("Unhandled rebase type %d", opts->type);
break;
}
strbuf_addf(&script_snippet,
". git-sh-setup && . git-rebase--common &&"
" . %s && %s", backend, backend_func);
argv[0] = script_snippet.buf;
status = run_command_v_opt(argv, RUN_USING_SHELL);
finished_rebase:
if (opts->dont_finish_rebase)
; /* do nothing */
else if (opts->type == REBASE_INTERACTIVE)
; /* interactive rebase cleans up after itself */
else if (status == 0) {
if (!file_exists(state_dir_path("stopped-sha", opts)))
finish_rebase(opts);
} else if (status == 2) {
struct strbuf dir = STRBUF_INIT;
apply_autostash(opts);
strbuf_addstr(&dir, opts->state_dir);
remove_dir_recursively(&dir, 0);
strbuf_release(&dir);
die("Nothing to do");
}
strbuf_release(&script_snippet);
return status ? -1 : 0;
}
static int rebase_config(const char *var, const char *value, void *data)
{
struct rebase_options *opts = data;

1637
builtin/stash.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1333,6 +1333,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);

View File

@@ -0,0 +1,283 @@
# This shell script fragment is sourced by git-rebase to implement
# its interactive mode. "git rebase --interactive" makes it easy
# to fix up commits in the middle of a series and rearrange commits.
#
# Copyright (c) 2006 Johannes E. Schindelin
#
# The original idea comes from Eric W. Biederman, in
# https://public-inbox.org/git/m1odwkyuf5.fsf_-_@ebiederm.dsl.xmission.com/
#
# The file containing rebase commands, comments, and empty lines.
# This file is created by "git rebase -i" then edited by the user. As
# the lines are processed, they are removed from the front of this
# file and written to the tail of $done.
todo="$state_dir"/git-rebase-todo
GIT_CHERRY_PICK_HELP="$resolvemsg"
export GIT_CHERRY_PICK_HELP
comment_char=$(git config --get core.commentchar 2>/dev/null)
case "$comment_char" in
'' | auto)
comment_char="#"
;;
?)
;;
*)
comment_char=$(echo "$comment_char" | cut -c1)
;;
esac
orig_reflog_action="$GIT_REFLOG_ACTION"
comment_for_reflog () {
case "$orig_reflog_action" in
''|rebase*)
GIT_REFLOG_ACTION="rebase -i ($1)"
export GIT_REFLOG_ACTION
;;
esac
}
append_todo_help () {
gettext "
Commands:
p, pick <commit> = use commit
r, reword <commit> = use commit, but edit the commit message
e, edit <commit> = use commit, but stop for amending
s, squash <commit> = use commit, but meld into previous commit
f, fixup <commit> = like \"squash\", but discard this commit's log message
x, exec <command> = run command (the rest of the line) using shell
d, drop <commit> = remove commit
l, label <label> = label current HEAD with a name
t, reset <label> = reset HEAD to a label
m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
. create a merge commit using the original merge commit's
. message (or the oneline, if no original merge commit was
. specified). Use -c <commit> to reword the commit message.
These lines can be re-ordered; they are executed from top to bottom.
" | git stripspace --comment-lines >>"$todo"
if test $(get_missing_commit_check_level) = error
then
gettext "
Do not remove any line. Use 'drop' explicitly to remove a commit.
" | git stripspace --comment-lines >>"$todo"
else
gettext "
If you remove a line here THAT COMMIT WILL BE LOST.
" | git stripspace --comment-lines >>"$todo"
fi
}
die_abort () {
apply_autostash
rm -rf "$state_dir"
die "$1"
}
has_action () {
test -n "$(git stripspace --strip-comments <"$1")"
}
git_sequence_editor () {
if test -z "$GIT_SEQUENCE_EDITOR"
then
GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
if [ -z "$GIT_SEQUENCE_EDITOR" ]
then
GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
fi
fi
eval "$GIT_SEQUENCE_EDITOR" '"$@"'
}
expand_todo_ids() {
git rebase--interactive --expand-ids
}
collapse_todo_ids() {
git rebase--interactive --shorten-ids
}
# Switch to the branch in $into and notify it in the reflog
checkout_onto () {
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
output git checkout $onto || die_abort "$(gettext "could not detach HEAD")"
git update-ref ORIG_HEAD $orig_head
}
get_missing_commit_check_level () {
check_level=$(git config --get rebase.missingCommitsCheck)
check_level=${check_level:-ignore}
# Don't be case sensitive
printf '%s' "$check_level" | tr 'A-Z' 'a-z'
}
# Initiate an action. If the cannot be any
# further action it may exec a command
# or exit and not return.
#
# TODO: Consider a cleaner return model so it
# never exits and always return 0 if process
# is complete.
#
# Parameter 1 is the action to initiate.
#
# Returns 0 if the action was able to complete
# and if 1 if further processing is required.
initiate_action () {
case "$1" in
continue)
exec git rebase--interactive ${force_rebase:+--no-ff} $allow_empty_message \
--continue
;;
skip)
git rerere clear
exec git rebase--interactive ${force_rebase:+--no-ff} $allow_empty_message \
--continue
;;
edit-todo)
git stripspace --strip-comments <"$todo" >"$todo".new
mv -f "$todo".new "$todo"
collapse_todo_ids
append_todo_help
gettext "
You are editing the todo file of an ongoing interactive rebase.
To continue rebase after editing, run:
git rebase --continue
" | git stripspace --comment-lines >>"$todo"
git_sequence_editor "$todo" ||
die "$(gettext "Could not execute editor")"
expand_todo_ids
exit
;;
show-current-patch)
exec git show REBASE_HEAD --
;;
*)
return 1 # continue
;;
esac
}
setup_reflog_action () {
comment_for_reflog start
if test ! -z "$switch_to"
then
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to"
output git checkout "$switch_to" -- ||
die "$(eval_gettext "Could not checkout \$switch_to")"
comment_for_reflog start
fi
}
init_basic_state () {
orig_head=$(git rev-parse --verify HEAD) || die "$(gettext "No HEAD?")"
mkdir -p "$state_dir" || die "$(eval_gettext "Could not create temporary \$state_dir")"
rm -f "$(git rev-parse --git-path REBASE_HEAD)"
: > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")"
write_basic_state
}
init_revisions_and_shortrevisions () {
shorthead=$(git rev-parse --short $orig_head)
shortonto=$(git rev-parse --short $onto)
if test -z "$rebase_root"
# this is now equivalent to ! -z "$upstream"
then
shortupstream=$(git rev-parse --short $upstream)
revisions=$upstream...$orig_head
shortrevisions=$shortupstream..$shorthead
else
revisions=$onto...$orig_head
shortrevisions=$shorthead
test -z "$squash_onto" ||
echo "$squash_onto" >"$state_dir"/squash-onto
fi
}
complete_action() {
test -s "$todo" || echo noop >> "$todo"
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##* }
cat >>"$todo" <<EOF
$comment_char $(eval_ngettext \
"Rebase \$shortrevisions onto \$shortonto (\$todocount command)" \
"Rebase \$shortrevisions onto \$shortonto (\$todocount commands)" \
"$todocount")
EOF
append_todo_help
gettext "
However, if you remove everything, the rebase will be aborted.
" | git stripspace --comment-lines >>"$todo"
if test -z "$keep_empty"
then
printf '%s\n' "$comment_char $(gettext "Note that empty commits are commented out")" >>"$todo"
fi
has_action "$todo" ||
return 2
cp "$todo" "$todo".backup
collapse_todo_ids
git_sequence_editor "$todo" ||
die_abort "$(gettext "Could not execute editor")"
has_action "$todo" ||
return 2
git rebase--interactive --check-todo-list || {
ret=$?
checkout_onto
exit $ret
}
expand_todo_ids
test -n "$force_rebase" ||
onto="$(git rebase--interactive --skip-unnecessary-picks)" ||
die "Could not skip unnecessary pick commands"
checkout_onto
require_clean_work_tree "rebase"
exec git rebase--interactive ${force_rebase:+--no-ff} $allow_empty_message \
--continue
}
git_rebase__interactive () {
initiate_action "$action"
ret=$?
if test $ret = 0; then
return 0
fi
setup_reflog_action
init_basic_state
init_revisions_and_shortrevisions
git rebase--interactive --make-script ${keep_empty:+--keep-empty} \
${rebase_merges:+--rebase-merges} \
${rebase_cousins:+--rebase-cousins} \
$revisions ${restrict_revision+^$restrict_revision} >"$todo" ||
die "$(gettext "Could not generate todo list")"
complete_action
}

View File

@@ -135,37 +135,6 @@ finish_rebase () {
rm -rf "$state_dir"
}
run_interactive () {
GIT_CHERRY_PICK_HELP="$resolvemsg"
export GIT_CHERRY_PICK_HELP
test -n "$keep_empty" && keep_empty="--keep-empty"
test -n "$rebase_merges" && rebase_merges="--rebase-merges"
test -n "$rebase_cousins" && rebase_cousins="--rebase-cousins"
test -n "$autosquash" && autosquash="--autosquash"
test -n "$verbose" && verbose="--verbose"
test -n "$force_rebase" && force_rebase="--no-ff"
test -n "$restrict_revision" && \
restrict_revision="--restrict-revision=^$restrict_revision"
test -n "$upstream" && upstream="--upstream=$upstream"
test -n "$onto" && onto="--onto=$onto"
test -n "$squash_onto" && squash_onto="--squash-onto=$squash_onto"
test -n "$onto_name" && onto_name="--onto-name=$onto_name"
test -n "$head_name" && head_name="--head-name=$head_name"
test -n "$strategy" && strategy="--strategy=$strategy"
test -n "$strategy_opts" && strategy_opts="--strategy-opts=$strategy_opts"
test -n "$switch_to" && switch_to="--switch-to=$switch_to"
test -n "$cmd" && cmd="--cmd=$cmd"
test -n "$action" && action="--$action"
exec git rebase--interactive "$action" "$keep_empty" "$rebase_merges" "$rebase_cousins" \
"$upstream" "$onto" "$squash_onto" "$restrict_revision" \
"$allow_empty_message" "$autosquash" "$verbose" \
"$force_rebase" "$onto_name" "$head_name" "$strategy" \
"$strategy_opts" "$cmd" "$switch_to" \
"$allow_rerere_autoupdate" "$gpg_sign_opt" "$signoff"
}
run_specific_rebase () {
if [ "$interactive_rebase" = implied ]; then
GIT_EDITOR=:
@@ -175,7 +144,9 @@ run_specific_rebase () {
if test -n "$interactive_rebase" -a -z "$preserve_merges"
then
run_interactive
. git-legacy-rebase--$type
git_rebase__$type
else
. git-rebase--$type
@@ -195,7 +166,12 @@ run_specific_rebase () {
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

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

6
git.c
View File

@@ -554,6 +554,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 },

View File

@@ -4729,7 +4729,7 @@ static int rewrite_file(const char *path, const char *buf, size_t len)
}
/* skip picking commits whose parents are unchanged */
static int skip_unnecessary_picks(struct object_id *output_oid)
int skip_unnecessary_picks(struct object_id *output_oid)
{
const char *todo_file = rebase_path_todo();
struct strbuf buf = STRBUF_INIT;

View File

@@ -138,3 +138,5 @@ int read_author_script(const char *path, char **name, char **email, char **date,
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

@@ -1542,6 +1542,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

@@ -268,6 +268,21 @@ void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2)
strbuf_setlen(sb, sb->len + sb2->len);
}
const char *strbuf_join_argv(struct strbuf *buf,
int argc, const char **argv, char delim)
{
if (!argc)
return buf->buf;
strbuf_addstr(buf, *argv);
while (--argc) {
strbuf_addch(buf, delim);
strbuf_addstr(buf, *(++argv));
}
return buf->buf;
}
void strbuf_addchars(struct strbuf *sb, int c, size_t n)
{
strbuf_grow(sb, n);

View File

@@ -288,6 +288,13 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s)
*/
void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2);
/**
* Join the arguments into a buffer. `delim` is put between every
* two arguments.
*/
const char *strbuf_join_argv(struct strbuf *buf, int argc,
const char **argv, char delim);
/**
* This function can be used to expand a format string containing
* placeholders. To that end, it parses the string and calls the specified

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 expect output
'
@@ -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 expect output &&
git diff > output &&
git diff >output &&
test_cmp expect1 output &&
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 expect output
'
@@ -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 &&

83
t/t3907-stash-show-config.sh Executable file
View File

@@ -0,0 +1,83 @@
#!/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_unconfig stash.showStat
else
test_config stash.showStat "$1"
fi &&
if test "<unset>" = "$2"
then
test_unconfig stash.showPatch
else
test_config stash.showPatch "$2"
fi &&
shift 2 &&
echo 2 >file.t &&
if test $# != 0
then
git diff "$@" >expect
fi &&
git stash &&
git stash show >actual &&
if test $# = 0
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