diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index d60ebdb962..e31ea7d303 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -117,9 +117,6 @@ show [] []:: You can use stash.showStat and/or stash.showPatch config variables to change the default behavior. - It accepts any option known to `git diff`, but acts different on - `--quiet` option and exit with zero regardless of differences. - pop [--index] [-q|--quiet] []:: Remove a single stashed state from the stash list and apply it diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index 31eaf87726..551faba558 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -206,6 +206,9 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) 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); @@ -223,6 +226,9 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) 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); diff --git a/builtin/stash.c b/builtin/stash.c index 5657e0de4d..ed669dae0f 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -13,7 +13,6 @@ #include "revision.h" #include "log-tree.h" #include "diffcore.h" -#include "apply.h" #include "exec-cmd.h" static const char * const git_stash_usage[] = { @@ -23,8 +22,6 @@ static const char * const git_stash_usage[] = { N_("git stash ( pop | apply ) [--index] [-q|--quiet] []"), N_("git stash branch []"), N_("git stash clear"), - N_("git stash store [-m|--message ] [-q|--quiet] "), - N_("git stash create []"), N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] [-m|--message ]\n" " [--] [...]]"), @@ -125,7 +122,7 @@ static void free_stash_info(struct stash_info *info) strbuf_release(&info->revision); } -static void assert_stash_like(struct stash_info *info, const char * revision) +static void assert_stash_like(struct stash_info *info, const char *revision) { if (get_oidf(&info->b_commit, "%s^1", revision) || get_oidf(&info->w_tree, "%s:", revision) || @@ -167,7 +164,7 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv) if (!commit) { if (!ref_exists(ref_stash)) { free_stash_info(info); - fprintf_ln(stderr, "No stash entries found."); + fprintf_ln(stderr, _("No stash entries found.")); return -1; } @@ -229,7 +226,7 @@ static int clear_stash(int argc, const char **argv, const char *prefix) git_stash_clear_usage, PARSE_OPT_STOP_AT_NON_OPTION); - if (argc != 0) + if (argc) return error(_("git stash clear with parameters is unimplemented")); return do_clear_stash(); @@ -279,6 +276,10 @@ static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit) struct child_process cp = CHILD_PROCESS_INIT; const char *w_commit_hex = oid_to_hex(w_commit); + /* + * Diff-tree would not be very hard to replace with a native function, + * however it should be done together with apply_cached. + */ cp.git_cmd = 1; argv_array_pushl(&cp.args, "diff-tree", "--binary", NULL); argv_array_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex); @@ -286,39 +287,21 @@ static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit) return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); } -static int apply_patch_from_buf(struct strbuf *patch, int cached, int reverse, - int check_index) +static int apply_cached(struct strbuf *out) { - int ret = 0; - struct apply_state state; - struct argv_array args = ARGV_ARRAY_INIT; - const char *patch_path = ".git/stash_patch.patch"; - FILE *patch_file; + struct child_process cp = CHILD_PROCESS_INIT; - if (init_apply_state(&state, the_repository, NULL)) - return -1; - - state.cached = cached; - state.apply_in_reverse = reverse; - state.check_index = check_index; - if (state.cached) - state.check_index = 1; - if (state.check_index) - state.unsafe_paths = 0; - - patch_file = fopen(patch_path, "w"); - strbuf_write(patch, patch_file); - fclose(patch_file); - - argv_array_push(&args, patch_path); - ret = apply_all_patches(&state, args.argc, args.argv, 0); - - remove_path(patch_path); - clear_apply_state(&state); - return ret; + /* + * Apply currently only reads either from stdin or a file, thus + * apply_all_patches would have to be updated to optionally take a + * buffer. + */ + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "apply", "--cached", NULL); + return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); } -static int reset_head(const char *prefix) +static int reset_head(void) { struct child_process cp = CHILD_PROCESS_INIT; @@ -340,7 +323,7 @@ static void add_diff_to_buf(struct diff_queue_struct *q, for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; strbuf_addstr(data, p->one->path); - strbuf_addch(data, '\n'); + strbuf_addch(data, 0); } } @@ -434,7 +417,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, return -1; } - ret = apply_patch_from_buf(&out, 1, 0, 0); + ret = apply_cached(&out); strbuf_release(&out); if (ret) return -1; @@ -444,7 +427,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, if (write_cache_as_tree(&index_tree, 0, NULL)) return -1; - reset_head(prefix); + reset_head(); } } @@ -469,7 +452,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases, &result); - if (ret != 0) { + if (ret) { rerere(0); if (index) @@ -563,8 +546,8 @@ static int do_drop_stash(const char *prefix, struct stash_info *info) ret = run_command(&cp_reflog); if (!ret) { if (!quiet) - printf(_("Dropped %s (%s)\n"), info->revision.buf, - oid_to_hex(&info->w_commit)); + printf_ln(_("Dropped %s (%s)"), info->revision.buf, + oid_to_hex(&info->w_commit)); } else { return error(_("%s: Could not drop stash entry"), info->revision.buf); @@ -659,7 +642,7 @@ static int branch_stash(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_stash_branch_usage, 0); - if (argc == 0) + if (!argc) return error(_("No branch name specified")); branch = argv[0]; @@ -710,11 +693,11 @@ static int show_patch; static int git_stash_config(const char *var, const char *value, void *cb) { - if (!strcmp(var, "stash.showStat")) { + if (!strcmp(var, "stash.showstat")) { show_stat = git_config_bool(var, value); return 0; } - if (!strcmp(var, "stash.showPatch")) { + if (!strcmp(var, "stash.showpatch")) { show_patch = git_config_bool(var, value); return 0; } @@ -724,7 +707,8 @@ static int git_stash_config(const char *var, const char *value, void *cb) static int show_stash(int argc, const char **argv, const char *prefix) { int i; - int flags = 0; + int opts = 0; + int ret = 0; struct stash_info info; struct rev_info rev; struct argv_array stash_args = ARGV_ARRAY_INIT; @@ -734,70 +718,64 @@ static int show_stash(int argc, const char **argv, const char *prefix) init_diff_ui_defaults(); git_config(git_diff_ui_config, NULL); - init_revisions(&rev, prefix); - /* Push arguments which are not options into stash_args. */ for (i = 1; i < argc; ++i) { if (argv[i][0] != '-') argv_array_push(&stash_args, argv[i]); else - flags++; + opts++; } + ret = get_stash_info(&info, stash_args.argc, stash_args.argv); + argv_array_clear(&stash_args); + if (ret) + return -1; + /* * The config settings are applied only if there are not passed - * any flags. + * any options. */ - if (!flags) { + if (!opts) { git_config(git_stash_config, NULL); if (show_stat) - rev.diffopt.output_format |= DIFF_FORMAT_DIFFSTAT; - if (show_patch) { - rev.diffopt.output_format = ~DIFF_FORMAT_NO_OUTPUT; + rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT; + + if (show_patch) rev.diffopt.output_format |= DIFF_FORMAT_PATCH; + + if (!show_stat && !show_patch) { + free_stash_info(&info); + return 0; } } - if (get_stash_info(&info, stash_args.argc, stash_args.argv)) { - argv_array_clear(&stash_args); - return -1; - } - argc = setup_revisions(argc, argv, &rev, NULL); - if (!rev.diffopt.output_format) - rev.diffopt.output_format = DIFF_FORMAT_PATCH; - diff_setup_done(&rev.diffopt); - rev.diffopt.flags.recursive = 1; - setup_diff_pager(&rev.diffopt); - - /* - * We can return early if there was any option not recognised by - * `diff_opt_parse()`, besides the word `stash`. - */ if (argc > 1) { free_stash_info(&info); - argv_array_clear(&stash_args); usage_with_options(git_stash_show_usage, options); } - /* Do the diff thing. */ + rev.diffopt.flags.recursive = 1; + setup_diff_pager(&rev.diffopt); diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt); log_tree_diff_flush(&rev); free_stash_info(&info); - argv_array_clear(&stash_args); - return 0; + return diff_result_code(&rev.diffopt, 0); } static int do_store_stash(const char *w_commit, const char *stash_msg, int quiet) { int ret = 0; + int need_to_free = 0; struct object_id obj; - if (!stash_msg) + if (!stash_msg) { + need_to_free = 1; stash_msg = xstrdup("Created via \"git stash store\"."); + } ret = get_oid(w_commit, &obj); if (!ret) { @@ -809,7 +787,8 @@ static int do_store_stash(const char *w_commit, const char *stash_msg, if (ret && !quiet) fprintf_ln(stderr, _("Cannot update %s with %s"), ref_stash, w_commit); - + if (need_to_free) + free((char *) stash_msg); return ret; } @@ -827,7 +806,7 @@ static int store_stash(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_UNKNOWN); if (argc != 1) { - fprintf(stderr, _("\"git stash store\" requires one argument\n")); + fprintf_ln(stderr, _("\"git stash store\" requires one argument")); return -1; } @@ -835,12 +814,6 @@ static int store_stash(int argc, const char **argv, const char *prefix) } /* - * `has_untracked_files` is: - * -2 if `get_untracked_files()` hasn't been called - * -1 if there were errors - * 0 if there are no untracked files - * 1 if there are untracked files - * * `untracked_files` will be filled with the names of untracked files. * The return value is: * @@ -848,37 +821,28 @@ static int store_stash(int argc, const char **argv, const char *prefix) * > 0 if there are untracked files */ static struct strbuf untracked_files = STRBUF_INIT; -static int has_untracked_files = -2; -static int get_untracked_files(const char **argv, const char *prefix, - int include_untracked) +static int get_untracked_files(struct pathspec ps, int include_untracked) { int max_len; int i; char *seen; struct dir_struct dir; - struct pathspec pathspec; - - if (has_untracked_files != -2) - return has_untracked_files; memset(&dir, 0, sizeof(dir)); if (include_untracked != 2) setup_standard_excludes(&dir); - parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_FULL, - prefix, argv); - seen = xcalloc(pathspec.nr, 1); + seen = xcalloc(ps.nr, 1); - max_len = fill_directory(&dir, the_repository->index, &pathspec); + max_len = fill_directory(&dir, the_repository->index, &ps); for (i = 0; i < dir.nr; i++) { struct dir_entry *ent = dir.entries[i]; - if (!dir_path_match(&the_index, ent, &pathspec, max_len, seen)) { + if (!dir_path_match(&the_index, ent, &ps, max_len, seen)) { free(ent); continue; } - strbuf_addf(&untracked_files, "%s\n", ent->name); + strbuf_addf(&untracked_files, "%s%c", ent->name, '\0'); free(ent); } @@ -886,78 +850,60 @@ static int get_untracked_files(const char **argv, const char *prefix, free(dir.ignored); clear_directory(&dir); free(seen); - has_untracked_files = untracked_files.len; return untracked_files.len; } /* - * `changes` is: - * -2 if `check_changes()` hasn't been called - * -1 if there were any errors - * 0 if there are no changes - * 1 if there are changes - * - * The return value of `check_changes()` can be: + * The return value of `check_changes_tracked_files()` can be: * * < 0 if there was an error * = 0 if there are no changes. * > 0 if there are changes. */ -static int changes = -2; -static int check_changes(const char **argv, int include_untracked, - const char *prefix) +static int check_changes_tracked_files(struct pathspec ps) { int result; - int ret = 0; struct rev_info rev; struct object_id dummy; - struct argv_array args = ARGV_ARRAY_INIT; - if (changes != -2) - return changes; - - init_revisions(&rev, prefix); - parse_pathspec(&rev.prune_data, 0, PATHSPEC_PREFER_FULL, - prefix, argv); + init_revisions(&rev, NULL); + rev.prune_data = ps; rev.diffopt.flags.quick = 1; rev.diffopt.flags.ignore_submodules = 1; rev.abbrev = 0; /* No initial commit. */ - if (get_oid("HEAD", &dummy)) { - ret = -1; - goto done; - } + if (get_oid("HEAD", &dummy)) + return -1; add_head_to_pending(&rev); diff_setup_done(&rev.diffopt); - if (read_cache() < 0) { - ret = -1; - goto done; - } + if (read_cache() < 0) + return 1; result = run_diff_index(&rev, 1); - if (diff_result_code(&rev.diffopt, result)) { - ret = 1; - goto done; - } + if (diff_result_code(&rev.diffopt, result)) + return 1; object_array_clear(&rev.pending); result = run_diff_files(&rev, 0); - if (diff_result_code(&rev.diffopt, result)) { - ret = 1; - goto done; - } + if (diff_result_code(&rev.diffopt, result)) + return 1; - if (include_untracked && get_untracked_files(argv, prefix, - include_untracked)) + return 0; +} + +static int check_changes(struct pathspec ps, int include_untracked) +{ + int ret = 0; + if (check_changes_tracked_files(ps)) + ret = 1; + + if (include_untracked && get_untracked_files(ps, include_untracked)) ret = 1; -done: - changes = ret; - argv_array_clear(&args); return ret; } @@ -965,23 +911,23 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg) { int ret = 0; struct strbuf untracked_msg = STRBUF_INIT; - struct child_process cp = CHILD_PROCESS_INIT; - struct index_state state = { NULL }; + struct child_process cp_upd_index = CHILD_PROCESS_INIT; + struct index_state istate = { NULL }; - cp.git_cmd = 1; - argv_array_pushl(&cp.args, "update-index", "--add", + cp_upd_index.git_cmd = 1; + argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add", "--remove", "--stdin", NULL); - argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", + argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", stash_index_path.buf); strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf); - if (pipe_command(&cp, untracked_files.buf, untracked_files.len, + if (pipe_command(&cp_upd_index, untracked_files.buf, untracked_files.len, NULL, 0, NULL, 0)) { ret = -1; goto done; } - if (write_index_as_tree(&info->u_tree, &state, stash_index_path.buf, 0, + if (write_index_as_tree(&info->u_tree, &istate, stash_index_path.buf, 0, NULL)) { ret = -1; goto done; @@ -994,6 +940,7 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg) } done: + discard_index(&istate); strbuf_release(&untracked_msg); remove_path(stash_index_path.buf); return ret; @@ -1001,84 +948,83 @@ done: static struct strbuf patch = STRBUF_INIT; -static int stash_patch(struct stash_info *info, const char **argv) +static int stash_patch(struct stash_info *info, struct pathspec ps, int quiet) { + int i; int ret = 0; - struct child_process cp0 = CHILD_PROCESS_INIT; - struct child_process cp1 = CHILD_PROCESS_INIT; - struct child_process cp3 = CHILD_PROCESS_INIT; - struct index_state state = { NULL }; + struct child_process cp_read_tree = CHILD_PROCESS_INIT; + struct child_process cp_add_i = CHILD_PROCESS_INIT; + struct child_process cp_diff_tree = CHILD_PROCESS_INIT; + struct index_state istate = { NULL }; remove_path(stash_index_path.buf); - cp0.git_cmd = 1; - argv_array_pushl(&cp0.args, "read-tree", "HEAD", NULL); - argv_array_pushf(&cp0.env_array, "GIT_INDEX_FILE=%s", + cp_read_tree.git_cmd = 1; + argv_array_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL); + argv_array_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s", stash_index_path.buf); - if (run_command(&cp0)) { + if (run_command(&cp_read_tree)) { ret = -1; goto done; } - cp1.git_cmd = 1; - argv_array_pushl(&cp1.args, "add--interactive", "--patch=stash", + cp_add_i.git_cmd = 1; + argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash", "--", NULL); - if (argv) - argv_array_pushv(&cp1.args, argv); - argv_array_pushf(&cp1.env_array, "GIT_INDEX_FILE=%s", + for (i = 0; i < ps.nr; ++i) + argv_array_push(&cp_add_i.args, ps.items[i].match); + argv_array_pushf(&cp_add_i.env_array, "GIT_INDEX_FILE=%s", stash_index_path.buf); - if (run_command(&cp1)) { + if (run_command(&cp_add_i)) { ret = -1; goto done; } - if (write_index_as_tree(&info->w_tree, &state, stash_index_path.buf, 0, + if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, NULL)) { ret = -1; goto done; } - cp3.git_cmd = 1; - argv_array_pushl(&cp3.args, "diff-tree", "-p", "HEAD", + cp_diff_tree.git_cmd = 1; + argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD", oid_to_hex(&info->w_tree), "--", NULL); - if (pipe_command(&cp3, NULL, 0, &patch, 0, NULL, 0)) + if (pipe_command(&cp_diff_tree, NULL, 0, &patch, 0, NULL, 0)) { ret = -1; + goto done; + } if (!patch.len) { - fprintf_ln(stdout, "No changes selected"); + if (!quiet) + fprintf_ln(stderr, _("No changes selected")); ret = 1; } done: + discard_index(&istate); remove_path(stash_index_path.buf); return ret; } -static int stash_working_tree(struct stash_info *info, - const char **argv, const char *prefix) +static int stash_working_tree(struct stash_info *info, struct pathspec ps) { int ret = 0; - struct child_process cp2 = CHILD_PROCESS_INIT; - struct argv_array args = ARGV_ARRAY_INIT; + struct child_process cp_upd_index = CHILD_PROCESS_INIT; struct strbuf diff_output = STRBUF_INIT; struct rev_info rev; - struct index_state state = { NULL }; + struct index_state istate = { NULL }; set_alternate_index_output(stash_index_path.buf); if (reset_tree(&info->i_tree, 0, 0)) { ret = -1; goto done; } - set_alternate_index_output(".git/index"); + set_alternate_index_output(NULL); - argv_array_push(&args, "dummy"); - if (argv) - argv_array_pushv(&args, argv); git_config(git_diff_basic_config, NULL); - init_revisions(&rev, prefix); - args.argc = setup_revisions(args.argc, args.argv, &rev, NULL); - - rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + init_revisions(&rev, NULL); + rev.prune_data = ps; + rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = add_diff_to_buf; rev.diffopt.format_callback_data = &diff_output; @@ -1093,67 +1039,57 @@ static int stash_working_tree(struct stash_info *info, goto done; } - cp2.git_cmd = 1; - argv_array_pushl(&cp2.args, "update-index", "--add", + cp_upd_index.git_cmd = 1; + argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add", "--remove", "--stdin", NULL); - argv_array_pushf(&cp2.env_array, "GIT_INDEX_FILE=%s", + argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", stash_index_path.buf); - if (pipe_command(&cp2, diff_output.buf, diff_output.len, + if (pipe_command(&cp_upd_index, diff_output.buf, diff_output.len, NULL, 0, NULL, 0)) { ret = -1; goto done; } - if (write_index_as_tree(&info->w_tree, &state, stash_index_path.buf, 0, + if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, NULL)) { - ret = -1; goto done; } - discard_cache(); - read_cache(); - done: + discard_index(&istate); UNLEAK(rev); - argv_array_clear(&args); object_array_clear(&rev.pending); strbuf_release(&diff_output); remove_path(stash_index_path.buf); return ret; } -static int do_create_stash(int argc, const char **argv, const char *prefix, - const char **stash_msg, int include_untracked, - int patch_mode, struct stash_info *info, int quiet) +static int do_create_stash(struct pathspec ps, const char **stash_msg, + int include_untracked, int patch_mode, + struct stash_info *info, int quiet) { int untracked_commit_option = 0; int ret = 0; - int subject_len; int flags; const char *head_short_sha1 = NULL; const char *branch_ref = NULL; - const char *head_subject = NULL; const char *branch_name = "(no branch)"; struct commit *head_commit = NULL; struct commit_list *parents = NULL; struct strbuf msg = STRBUF_INIT; struct strbuf commit_tree_label = STRBUF_INIT; - struct strbuf final_stash_msg = STRBUF_INIT; + struct strbuf stash_msg_buf = STRBUF_INIT; read_cache_preload(NULL); refresh_cache(REFRESH_QUIET); - if (!check_changes(argv, include_untracked, prefix)) { - ret = 1; - goto done; - } - if (get_oid("HEAD", &info->b_commit)) { if (!quiet) - fprintf_ln(stderr, "You do not have the initial commit yet"); + fprintf_ln(stderr, _("You do not have the initial commit yet")); ret = -1; + *stash_msg = NULL; goto done; } else { head_commit = lookup_commit(the_repository, &info->b_commit); @@ -1164,10 +1100,8 @@ static int do_create_stash(int argc, const char **argv, const char *prefix, branch_name = strrchr(branch_ref, '/') + 1; head_short_sha1 = find_unique_abbrev(&head_commit->object.oid, DEFAULT_ABBREV); - subject_len = find_commit_subject(get_commit_buffer(head_commit, NULL), - &head_subject); - strbuf_addf(&msg, "%s: %s %.*s\n", branch_name, head_short_sha1, - subject_len, head_subject); + strbuf_addf(&msg, "%s: %s ", branch_name, head_short_sha1); + pp_commit_easy(CMIT_FMT_ONELINE, head_commit, &msg); strbuf_addf(&commit_tree_label, "index on %s\n", msg.buf); commit_list_insert(head_commit, &parents); @@ -1175,45 +1109,48 @@ static int do_create_stash(int argc, const char **argv, const char *prefix, commit_tree(commit_tree_label.buf, commit_tree_label.len, &info->i_tree, parents, &info->i_commit, NULL, NULL)) { if (!quiet) - fprintf_ln(stderr, "Cannot save the current index state"); + fprintf_ln(stderr, _("Cannot save the current index state")); ret = -1; + *stash_msg = NULL; goto done; } - if (include_untracked && get_untracked_files(argv, prefix, - include_untracked)) { + if (include_untracked) { if (save_untracked_files(info, &msg)) { if (!quiet) - printf_ln("Cannot save the untracked files"); + fprintf_ln(stderr, _("Cannot save the untracked files")); ret = -1; + *stash_msg = NULL; goto done; } untracked_commit_option = 1; } if (patch_mode) { - ret = stash_patch(info, argv); + ret = stash_patch(info, ps, quiet); + *stash_msg = NULL; if (ret < 0) { if (!quiet) - printf_ln("Cannot save the current worktree state"); + fprintf_ln(stderr, _("Cannot save the current worktree state")); goto done; } else if (ret > 0) { goto done; } } else { - if (stash_working_tree(info, argv, prefix)) { + if (stash_working_tree(info, ps)) { if (!quiet) - printf_ln("Cannot save the current worktree state"); + fprintf_ln(stderr, _("Cannot save the current worktree state")); ret = -1; + *stash_msg = NULL; goto done; } } if (!*stash_msg || !strlen(*stash_msg)) - strbuf_addf(&final_stash_msg, "WIP on %s", msg.buf); + strbuf_addf(&stash_msg_buf, "WIP on %s", msg.buf); else - strbuf_addf(&final_stash_msg, "On %s: %s\n", branch_name, + strbuf_addf(&stash_msg_buf, "On %s: %s", branch_name, *stash_msg); - *stash_msg = strbuf_detach(&final_stash_msg, NULL); + *stash_msg = strbuf_detach(&stash_msg_buf, NULL); /* * `parents` will be empty after calling `commit_tree()`, so there is @@ -1228,7 +1165,7 @@ static int do_create_stash(int argc, const char **argv, const char *prefix, if (commit_tree(*stash_msg, strlen(*stash_msg), &info->w_tree, parents, &info->w_commit, NULL, NULL)) { if (!quiet) - printf_ln("Cannot record working tree state"); + fprintf_ln(stderr, _("Cannot record working tree state")); ret = -1; goto done; } @@ -1236,18 +1173,19 @@ static int do_create_stash(int argc, const char **argv, const char *prefix, done: strbuf_release(&commit_tree_label); strbuf_release(&msg); - strbuf_release(&final_stash_msg); + strbuf_release(&stash_msg_buf); return ret; } static int create_stash(int argc, const char **argv, const char *prefix) { int i; - int include_untracked = 0; int ret = 0; + char *to_free = NULL; + const char *stash_msg = NULL; struct stash_info info; + struct pathspec ps; struct strbuf stash_msg_buf = STRBUF_INIT; - const char *stash_msg; struct option options[] = { OPT_END() }; @@ -1256,93 +1194,94 @@ static int create_stash(int argc, const char **argv, const char *prefix) git_stash_create_usage, 0); + memset(&ps, 0, sizeof(ps)); + if (!check_changes_tracked_files(ps)) + return 0; + for (i = 0; i < argc; ++i) strbuf_addf(&stash_msg_buf, "%s ", argv[i]); - stash_msg = strbuf_detach(&stash_msg_buf, NULL); + to_free = (char *) stash_msg; - ret = do_create_stash(0, NULL, prefix, &stash_msg, - include_untracked, 0, &info, 0); - - if (!ret) + if (!(ret = do_create_stash(ps, &stash_msg, 0, 0, &info, 0))) printf_ln("%s", oid_to_hex(&info.w_commit)); - /* - * ret can be 1 if there were no changes. In this case, we should - * not error out. - */ - return ret < 0; + free(to_free); + free((char *) stash_msg); + return ret; } -static int do_push_stash(int argc, const char **argv, const char *prefix, - int keep_index, int patch_mode, int include_untracked, - int quiet, const char *stash_msg) +static void add_ps_items_to_argv_array(struct argv_array *args, + struct pathspec ps) { + int i; + for (i = 0; i < ps.nr; ++i) + argv_array_push(args, ps.items[i].match); +} + +static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, + int keep_index, int patch_mode, int include_untracked) { int ret = 0; - struct pathspec ps; struct stash_info info; if (patch_mode && keep_index == -1) keep_index = 1; if (patch_mode && include_untracked) { - fprintf_ln(stderr, "Can't use --patch and --include-untracked or --all at the same time"); + fprintf_ln(stderr, _("Can't use --patch and --include-untracked or --all at the same time")); return -1; } - parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL, prefix, argv); - - if (read_cache() < 0) - die(_("index file corrupt")); - + read_cache_preload(NULL); if (!include_untracked && ps.nr) { int i; char *ps_matched = xcalloc(ps.nr, 1); for (i = 0; i < active_nr; ++i) { const struct cache_entry *ce = active_cache[i]; - if (!ce_path_match(&the_index, ce, &ps, ps_matched)) - continue; + ce_path_match(&the_index, ce, &ps, ps_matched); } - if (report_path_error(ps_matched, &ps, prefix)) { - fprintf_ln(stderr, "Did you forget to 'git add'?"); + if (report_path_error(ps_matched, &ps, NULL)) { + fprintf_ln(stderr, _("Did you forget to 'git add'?")); return -1; } + free(ps_matched); } - read_cache_preload(NULL); if (refresh_cache(REFRESH_QUIET)) return -1; - if (!check_changes(argv, include_untracked, prefix)) { + if (!check_changes(ps, include_untracked)) { if (!quiet) - fprintf_ln(stdout, "No local changes to save"); + printf_ln(_("No local changes to save")); return 0; } if (!reflog_exists(ref_stash) && do_clear_stash()) { if (!quiet) - fprintf_ln(stderr, "Cannot initialize stash"); + fprintf_ln(stderr, _("Cannot initialize stash")); return -1; } - if ((ret = do_create_stash(argc, argv, prefix, &stash_msg, - include_untracked, patch_mode, &info, - quiet))) - return ret; + if (do_create_stash(ps, &stash_msg, include_untracked, patch_mode, + &info, quiet)) { + ret = -1; + goto done; + } if (do_store_stash(oid_to_hex(&info.w_commit), stash_msg, 1)) { if (!quiet) - fprintf_ln(stderr, "Cannot save the current status"); - return -1; + fprintf_ln(stderr, _("Cannot save the current status")); + ret = -1; + goto done; } if (!quiet) - fprintf(stdout, "Saved working directory and index state %s", + printf_ln(_("Saved working directory and index state %s"), stash_msg); if (!patch_mode) { - if (include_untracked && ps.nr == 0) { + if (include_untracked && !ps.nr) { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; @@ -1350,13 +1289,15 @@ static int do_push_stash(int argc, const char **argv, const char *prefix, "--quiet", "-d", NULL); if (include_untracked == 2) argv_array_push(&cp.args, "-x"); - if (run_command(&cp)) - return -1; + if (run_command(&cp)) { + ret = -1; + goto done; + } } - if (argc != 0) { - int i; + if (ps.nr) { struct child_process cp1 = CHILD_PROCESS_INIT; struct child_process cp2 = CHILD_PROCESS_INIT; + struct child_process cp3 = CHILD_PROCESS_INIT; struct strbuf out = STRBUF_INIT; cp1.git_cmd = 1; @@ -1366,62 +1307,80 @@ static int do_push_stash(int argc, const char **argv, const char *prefix, if (include_untracked == 2) argv_array_push(&cp1.args, "--force"); argv_array_push(&cp1.args, "--"); - for (i = 0; i < ps.nr; ++i) - argv_array_push(&cp1.args, ps.items[i].match); - if (run_command(&cp1)) - return -1; + add_ps_items_to_argv_array(&cp1.args, ps); + if (run_command(&cp1)) { + ret = -1; + goto done; + } cp2.git_cmd = 1; argv_array_pushl(&cp2.args, "diff-index", "-p", "--cached", "--binary", "HEAD", "--", NULL); - for (i = 0; i < ps.nr; ++i) - argv_array_push(&cp2.args, ps.items[i].match); - if (pipe_command(&cp2, NULL, 0, &out, 0, NULL, 0)) - return -1; + add_ps_items_to_argv_array(&cp2.args, ps); + if (pipe_command(&cp2, NULL, 0, &out, 0, NULL, 0)) { + ret = -1; + goto done; + } - discard_cache(); - read_cache(); - if (apply_patch_from_buf(&out, 0, 1, 1)) - return -1; + cp3.git_cmd = 1; + argv_array_pushl(&cp3.args, "apply", "--index", "-R", + NULL); + if (pipe_command(&cp3, out.buf, out.len, NULL, 0, NULL, + 0)) { + ret = -1; + goto done; + } } else { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; argv_array_pushl(&cp.args, "reset", "--hard", "-q", NULL); - if (run_command(&cp)) - return -1; + if (run_command(&cp)) { + ret = -1; + goto done; + } } if (keep_index == 1 && !is_null_oid(&info.i_tree)) { - int i; struct child_process cp1 = CHILD_PROCESS_INIT; struct child_process cp2 = CHILD_PROCESS_INIT; struct strbuf out = STRBUF_INIT; - if (reset_tree(&info.i_tree, 0, 1)) - return -1; + if (reset_tree(&info.i_tree, 0, 1)) { + ret = -1; + goto done; + } cp1.git_cmd = 1; argv_array_pushl(&cp1.args, "ls-files", "-z", "--modified", "--", NULL); - for (i = 0; i < ps.nr; ++i) - argv_array_push(&cp1.args, ps.items[i].match); - if (pipe_command(&cp1, NULL, 0, &out, 0, NULL, 0)) - return -1; + add_ps_items_to_argv_array(&cp1.args, ps); + if (pipe_command(&cp1, NULL, 0, &out, 0, NULL, 0)) { + ret = -1; + goto done; + } cp2.git_cmd = 1; argv_array_pushl(&cp2.args, "checkout-index", "-z", "--force", "--stdin", NULL); if (pipe_command(&cp2, out.buf, out.len, NULL, 0, NULL, - 0)) - return -1; + 0)) { + ret = -1; + goto done; + } } } else { - if (apply_patch_from_buf(&patch, 0, 1, 0)) { + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "apply", "-R", NULL); + + if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) { if (!quiet) - fprintf_ln(stderr, "Cannot remove worktree changes"); - return -1; + fprintf_ln(stderr, _("Cannot remove worktree changes")); + ret = -1; + goto done; } if (keep_index < 1) { @@ -1432,11 +1391,15 @@ static int do_push_stash(int argc, const char **argv, const char *prefix, argv_array_pushl(&cp.args, "reset", "-q", "--", NULL); for (i = 0; i < ps.nr; ++i) argv_array_push(&cp.args, ps.items[i].match); - if (run_command(&cp)) - return -1; + if (run_command(&cp)) { + ret = -1; + goto done; + } } } - return 0; +done: + free((char *) stash_msg); + return ret; } static int push_stash(int argc, const char **argv, const char *prefix) @@ -1446,13 +1409,13 @@ static int push_stash(int argc, const char **argv, const char *prefix) int include_untracked = 0; int quiet = 0; const char *stash_msg = NULL; + struct pathspec ps; struct option options[] = { OPT_SET_INT('k', "keep-index", &keep_index, N_("keep index"), 1), OPT_BOOL('p', "patch", &patch_mode, N_("stash in patch mode")), - OPT_BOOL('q', "quiet", &quiet, - N_("quiet mode")), + OPT__QUIET(&quiet, N_("quiet mode")), OPT_BOOL('u', "include-untracked", &include_untracked, N_("include untracked files in stash")), OPT_SET_INT('a', "all", &include_untracked, @@ -1467,8 +1430,9 @@ static int push_stash(int argc, const char **argv, const char *prefix) git_stash_push_usage, 0); - return do_push_stash(argc, argv, prefix, keep_index, patch_mode, - include_untracked, quiet, stash_msg); + parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL, prefix, argv); + return do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode, + include_untracked); } static int save_stash(int argc, const char **argv, const char *prefix) @@ -1478,21 +1442,21 @@ static int save_stash(int argc, const char **argv, const char *prefix) int patch_mode = 0; int include_untracked = 0; int quiet = 0; - char *stash_msg = NULL; - struct strbuf alt_stash_msg = STRBUF_INIT; + int ret = 0; + const char *stash_msg = NULL; + char *to_free = NULL; + struct strbuf stash_msg_buf = STRBUF_INIT; + struct pathspec ps; struct option options[] = { OPT_SET_INT('k', "keep-index", &keep_index, N_("keep index"), 1), OPT_BOOL('p', "patch", &patch_mode, N_("stash in patch mode")), - OPT_BOOL('q', "quiet", &quiet, - N_("quiet mode")), + OPT__QUIET(&quiet, N_("quiet mode")), OPT_BOOL('u', "include-untracked", &include_untracked, N_("include untracked files in stash")), OPT_SET_INT('a', "all", &include_untracked, N_("include ignore files"), 2), - OPT_STRING('m', "message", &stash_msg, N_("message"), - N_("stash message")), OPT_END() }; @@ -1501,12 +1465,16 @@ static int save_stash(int argc, const char **argv, const char *prefix) 0); for (i = 0; i < argc; ++i) - strbuf_addf(&alt_stash_msg, "%s ", argv[i]); + strbuf_addf(&stash_msg_buf, "%s ", argv[i]); + stash_msg = strbuf_detach(&stash_msg_buf, NULL); + to_free = (char *) stash_msg; - stash_msg = strbuf_detach(&alt_stash_msg, NULL); + memset(&ps, 0, sizeof(ps)); + ret = do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode, + include_untracked); - return do_push_stash(0, NULL, prefix, keep_index, patch_mode, - include_untracked, quiet, stash_msg); + free(to_free); + return ret; } static int use_builtin_stash(void) @@ -1531,8 +1499,10 @@ static int use_builtin_stash(void) int cmd_stash(int argc, const char **argv, const char *prefix) { + int i = -1; pid_t pid = getpid(); const char *index_file; + struct argv_array args = ARGV_ARRAY_INIT; struct option options[] = { OPT_END() @@ -1585,13 +1555,44 @@ int cmd_stash(int argc, const char **argv, const char *prefix) return !!push_stash(argc, argv, prefix); else if (!strcmp(argv[0], "save")) return !!save_stash(argc, argv, prefix); - if (*argv[0] == '-') { - struct argv_array args = ARGV_ARRAY_INIT; - argv_array_push(&args, "push"); - argv_array_pushv(&args, argv); - return !!push_stash(args.argc, args.argv, prefix); + else if (*argv[0] != '-') + usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), + git_stash_usage, options); + + if (strcmp(argv[0], "-p")) { + while (++i < argc && strcmp(argv[i], "--")) { + /* + * `akpqu` is a string which contains all short options, + * except `-m` which is verified separately. + */ + if ((strlen(argv[i]) == 2) && *argv[i] == '-' && + strchr("akpqu", argv[i][1])) + continue; + + if (!strcmp(argv[i], "--all") || + !strcmp(argv[i], "--keep-index") || + !strcmp(argv[i], "--no-keep-index") || + !strcmp(argv[i], "--patch") || + !strcmp(argv[i], "--quiet") || + !strcmp(argv[i], "--include-untracked")) + continue; + + /* + * `-m` and `--message=` are verified separately because + * they need to be immediately followed by a string + * (i.e.`-m"foobar"` or `--message="foobar"`). + */ + if ((strlen(argv[i]) > 2 && + !strncmp(argv[i], "-m", 2)) || + (strlen(argv[i]) > 10 && + !strncmp(argv[i], "--message=", 10))) + continue; + + usage_with_options(git_stash_usage, options); + } } - usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), - git_stash_usage, options); + argv_array_push(&args, "push"); + argv_array_pushv(&args, argv); + return !!push_stash(args.argc, args.argv, prefix); } diff --git a/sequencer.c b/sequencer.c index 5413e4825c..a1be1e2f16 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4703,7 +4703,7 @@ int complete_action(struct replay_opts *opts, unsigned flags, if (!lstat(todo_file, &st) && st.st_size == 0 && write_message("noop\n", 5, todo_file, 0)) - return error_errno(_("could not write '%s'"), todo_file); + return -1; if (autosquash && rearrange_squash()) return -1; @@ -4736,7 +4736,7 @@ int complete_action(struct replay_opts *opts, unsigned flags, if (write_message(buf->buf, buf->len, todo_file, 0)) { todo_list_release(&todo_list); - return error_errno(_("could not write '%s'"), todo_file); + return -1; } if (copy_file(rebase_path_todo_backup(), todo_file, 0666)) @@ -4778,7 +4778,9 @@ int complete_action(struct replay_opts *opts, unsigned flags, if (opts->allow_ff && skip_unnecessary_picks(&oid)) return error(_("could not skip unnecessary pick commands")); - checkout_onto(opts, onto_name, oid_to_hex(&oid), orig_head); + if (checkout_onto(opts, onto_name, oid_to_hex(&oid), orig_head)) + return -1; +; if (require_clean_work_tree("rebase", "", 1, 1)) return -1; diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 2665f08964..9e06494ba0 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -682,7 +682,7 @@ test_expect_success 'invalid ref of the form "n", n >= N' ' git stash drop ' -test_expect_success '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 && @@ -707,7 +707,7 @@ test_expect_success 'branch: should not drop the stash if the apply fails' ' git rev-parse stash@{0} -- ' -test_expect_success 'apply: shows same status as git status (relative to ./)' ' +test_expect_success 'apply: show same status as git status (relative to ./)' ' git stash clear && echo 1 >subdir/subfile1 && echo 2 >subdir/subfile2 && @@ -1048,7 +1048,7 @@ test_expect_success 'stash push -p with pathspec shows no changes only once' ' test_i18ncmp expect actual ' -test_expect_success 'push: shows no changes when there are none' ' +test_expect_success 'push : show no changes when there are none' ' >foo && git add foo && git commit -m "tmp" && @@ -1066,6 +1066,7 @@ test_expect_success 'push: not in the repository errors out' ' 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 ' @@ -1077,12 +1078,13 @@ test_expect_success 'push: -q is quiet with no changes' ' test_expect_success 'push: -q is quiet even if there is no initial commit' ' git init foo_dir && - cd foo_dir && - touch bar && - test_must_fail git stash push -q >output 2>&1 && - test_must_be_empty output && - cd .. && - rm -rf 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' ' diff --git a/t/t3907-stash-show-config.sh b/t/t3907-stash-show-config.sh new file mode 100755 index 0000000000..8fe369c1a1 --- /dev/null +++ b/t/t3907-stash-show-config.sh @@ -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 "") +# 2. the stash.showPatch value (or "") +# 3. the diff options of the expected output (or nothing for no output) +test_stat_and_patch () { + if test "" = "$1" + then + test_might_fail git config --unset stash.showStat + else + test_config stash.showStat "$1" + fi && + + if test "" = "$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 "" "" --stat +' + +test_expect_success 'showStat unset showPatch false' ' + test_stat_and_patch "" false --stat +' + +test_expect_success 'showStat unset showPatch true' ' + test_stat_and_patch "" true --stat -p +' + +test_expect_success 'showStat false showPatch unset' ' + test_stat_and_patch false "" +' + +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 "" --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