diff options
Diffstat (limited to 'builtin/stash.c')
-rw-r--r-- | builtin/stash.c | 615 |
1 files changed, 380 insertions, 235 deletions
diff --git a/builtin/stash.c b/builtin/stash.c index 8f42360..062be1f 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1,91 +1,125 @@ -#define USE_THE_INDEX_COMPATIBILITY_MACROS +#define USE_THE_INDEX_VARIABLE #include "builtin.h" +#include "abspath.h" #include "config.h" +#include "environment.h" +#include "gettext.h" +#include "hash.h" +#include "hex.h" +#include "object-name.h" #include "parse-options.h" #include "refs.h" #include "lockfile.h" #include "cache-tree.h" #include "unpack-trees.h" #include "merge-recursive.h" +#include "merge-ort-wrappers.h" #include "strvec.h" #include "run-command.h" #include "dir.h" #include "entry.h" +#include "preload-index.h" +#include "read-cache.h" #include "rerere.h" #include "revision.h" +#include "setup.h" +#include "sparse-index.h" #include "log-tree.h" #include "diffcore.h" -#include "exec-cmd.h" -#include "entry.h" +#include "reflog.h" +#include "add-interactive.h" #define INCLUDE_ALL_FILES 2 +#define BUILTIN_STASH_LIST_USAGE \ + N_("git stash list [<log-options>]") +#define BUILTIN_STASH_SHOW_USAGE \ + N_("git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]") +#define BUILTIN_STASH_DROP_USAGE \ + N_("git stash drop [-q | --quiet] [<stash>]") +#define BUILTIN_STASH_POP_USAGE \ + N_("git stash pop [--index] [-q | --quiet] [<stash>]") +#define BUILTIN_STASH_APPLY_USAGE \ + N_("git stash apply [--index] [-q | --quiet] [<stash>]") +#define BUILTIN_STASH_BRANCH_USAGE \ + N_("git stash branch <branchname> [<stash>]") +#define BUILTIN_STASH_STORE_USAGE \ + N_("git stash store [(-m | --message) <message>] [-q | --quiet] <commit>") +#define BUILTIN_STASH_PUSH_USAGE \ + N_("git stash [push [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n" \ + " [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n" \ + " [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" \ + " [--] [<pathspec>...]]") +#define BUILTIN_STASH_SAVE_USAGE \ + N_("git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n" \ + " [-u | --include-untracked] [-a | --all] [<message>]") +#define BUILTIN_STASH_CREATE_USAGE \ + N_("git stash create [<message>]") +#define BUILTIN_STASH_CLEAR_USAGE \ + "git stash clear" + static const char * const git_stash_usage[] = { - N_("git stash list [<options>]"), - N_("git stash show [<options>] [<stash>]"), - N_("git stash drop [-q|--quiet] [<stash>]"), - N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"), - N_("git stash branch <branchname> [<stash>]"), - "git stash clear", - N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" - " [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n" - " [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" - " [--] [<pathspec>...]]"), - N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" - " [-u|--include-untracked] [-a|--all] [<message>]"), + BUILTIN_STASH_LIST_USAGE, + BUILTIN_STASH_SHOW_USAGE, + BUILTIN_STASH_DROP_USAGE, + BUILTIN_STASH_POP_USAGE, + BUILTIN_STASH_APPLY_USAGE, + BUILTIN_STASH_BRANCH_USAGE, + BUILTIN_STASH_PUSH_USAGE, + BUILTIN_STASH_SAVE_USAGE, + BUILTIN_STASH_CLEAR_USAGE, + BUILTIN_STASH_CREATE_USAGE, + BUILTIN_STASH_STORE_USAGE, NULL }; static const char * const git_stash_list_usage[] = { - N_("git stash list [<options>]"), + BUILTIN_STASH_LIST_USAGE, NULL }; static const char * const git_stash_show_usage[] = { - N_("git stash show [<options>] [<stash>]"), + BUILTIN_STASH_SHOW_USAGE, NULL }; static const char * const git_stash_drop_usage[] = { - N_("git stash drop [-q|--quiet] [<stash>]"), + BUILTIN_STASH_DROP_USAGE, NULL }; static const char * const git_stash_pop_usage[] = { - N_("git stash pop [--index] [-q|--quiet] [<stash>]"), + BUILTIN_STASH_POP_USAGE, NULL }; static const char * const git_stash_apply_usage[] = { - N_("git stash apply [--index] [-q|--quiet] [<stash>]"), + BUILTIN_STASH_APPLY_USAGE, NULL }; static const char * const git_stash_branch_usage[] = { - N_("git stash branch <branchname> [<stash>]"), + BUILTIN_STASH_BRANCH_USAGE, NULL }; static const char * const git_stash_clear_usage[] = { - "git stash clear", + BUILTIN_STASH_CLEAR_USAGE, NULL }; static const char * const git_stash_store_usage[] = { - N_("git stash store [-m|--message <message>] [-q|--quiet] <commit>"), + BUILTIN_STASH_STORE_USAGE, NULL }; static const char * const git_stash_push_usage[] = { - N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" - " [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n" - " [--] [<pathspec>...]]"), + BUILTIN_STASH_PUSH_USAGE, NULL }; static const char * const git_stash_save_usage[] = { - N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" - " [-u|--include-untracked] [-a|--all] [<message>]"), + BUILTIN_STASH_SAVE_USAGE, NULL }; @@ -116,6 +150,10 @@ struct stash_info { int has_u; }; +#define STASH_INFO_INIT { \ + .revision = STRBUF_INIT, \ +} + static void free_stash_info(struct stash_info *info) { strbuf_release(&info->revision); @@ -157,10 +195,8 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv) if (argc == 1) commit = argv[0]; - strbuf_init(&info->revision, 0); if (!commit) { if (!ref_exists(ref_stash)) { - free_stash_info(info); fprintf_ln(stderr, _("No stash entries found.")); return -1; } @@ -174,11 +210,8 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv) revision = info->revision.buf; - if (get_oid(revision, &info->w_commit)) { - error(_("%s is not a valid reference"), revision); - free_stash_info(info); - return -1; - } + if (repo_get_oid(the_repository, revision, &info->w_commit)) + return error(_("%s is not a valid reference"), revision); assert_stash_like(info, revision); @@ -187,7 +220,8 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv) end_of_rev = strchrnul(revision, '@'); strbuf_add(&symbolic, revision, end_of_rev - revision); - ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref, 0); + ret = repo_dwim_ref(the_repository, symbolic.buf, symbolic.len, + &dummy, &expanded_ref, 0); strbuf_release(&symbolic); switch (ret) { case 0: /* Not found, but valid ref */ @@ -197,7 +231,7 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv) info->is_stash_ref = !strcmp(expanded_ref, ref_stash); break; default: /* Invalid or ambiguous */ - free_stash_info(info); + break; } free(expanded_ref); @@ -207,7 +241,7 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv) static int do_clear_stash(void) { struct object_id obj; - if (get_oid(ref_stash, &obj)) + if (repo_get_oid(the_repository, ref_stash, &obj)) return 0; return delete_ref(NULL, ref_stash, &obj, 0); @@ -238,11 +272,11 @@ static int reset_tree(struct object_id *i_tree, int update, int reset) struct tree *tree; struct lock_file lock_file = LOCK_INIT; - read_cache_preload(NULL); - if (refresh_cache(REFRESH_QUIET)) + repo_read_index_preload(the_repository, NULL, 0); + if (refresh_index(&the_index, REFRESH_QUIET, NULL, NULL, NULL)) return -1; - hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); + repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); memset(&opts, 0, sizeof(opts)); @@ -250,14 +284,16 @@ static int reset_tree(struct object_id *i_tree, int update, int reset) if (parse_tree(tree)) return -1; - init_tree_desc(t, tree->buffer, tree->size); + init_tree_desc(t, &tree->object.oid, tree->buffer, tree->size); opts.head_idx = 1; opts.src_index = &the_index; opts.dst_index = &the_index; opts.merge = 1; - opts.reset = reset; + opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0; opts.update = update; + if (update) + opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ opts.fn = oneway_merge; if (unpack_trees(nr_trees, t, &opts)) @@ -308,18 +344,32 @@ static int reset_head(void) * API for resetting. */ cp.git_cmd = 1; - strvec_push(&cp.args, "reset"); + strvec_pushl(&cp.args, "reset", "--quiet", "--refresh", NULL); return run_command(&cp); } +static int is_path_a_directory(const char *path) +{ + /* + * This function differs from abspath.c:is_directory() in that + * here we use lstat() instead of stat(); we do not want to + * follow symbolic links here. + */ + struct stat st; + return (!lstat(path, &st) && S_ISDIR(st.st_mode)); +} + static void add_diff_to_buf(struct diff_queue_struct *q, - struct diff_options *options, + struct diff_options *options UNUSED, void *data) { int i; for (i = 0; i < q->nr; i++) { + if (is_path_a_directory(q->queue[i]->one->path)) + continue; + strbuf_addstr(data, q->queue[i]->one->path); /* NUL-terminate: will be fed to update-index -z */ @@ -340,7 +390,7 @@ static int restore_untracked(struct object_id *u_tree) cp.git_cmd = 1; strvec_push(&cp.args, "read-tree"); strvec_push(&cp.args, oid_to_hex(u_tree)); - strvec_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", + strvec_pushf(&cp.env, "GIT_INDEX_FILE=%s", stash_index_path.buf); if (run_command(&cp)) { remove_path(stash_index_path.buf); @@ -350,7 +400,7 @@ static int restore_untracked(struct object_id *u_tree) child_process_init(&cp); cp.git_cmd = 1; strvec_pushl(&cp.args, "checkout-index", "--all", NULL); - strvec_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", + strvec_pushf(&cp.env, "GIT_INDEX_FILE=%s", stash_index_path.buf); res = run_command(&cp); @@ -387,7 +437,7 @@ static void unstage_changes_unless_new(struct object_id *orig_tree) * to the index before a merge was run) and the current index * (reflecting the changes brought in by the merge). */ - diff_setup(&diff_opts); + repo_diff_setup(the_repository, &diff_opts); diff_opts.flags.recursive = 1; diff_opts.detect_rename = 0; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; @@ -415,10 +465,10 @@ static void unstage_changes_unless_new(struct object_id *orig_tree) * path, but left it out of the working tree, then clear the * SKIP_WORKTREE bit and write it to the working tree. */ - if (pos >= 0 && ce_skip_worktree(active_cache[pos])) { + if (pos >= 0 && ce_skip_worktree(the_index.cache[pos])) { struct stat st; - ce = active_cache[pos]; + ce = the_index.cache[pos]; if (!lstat(ce->name, &st)) { /* Conflicting path present; relocate it */ struct strbuf new_path = STRBUF_INIT; @@ -470,25 +520,27 @@ static void unstage_changes_unless_new(struct object_id *orig_tree) repo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR); if (write_locked_index(&the_index, &lock, COMMIT_LOCK | SKIP_IF_UNCHANGED)) - die(_("Unable to write index.")); + die(_("could not write index")); } static int do_apply_stash(const char *prefix, struct stash_info *info, int index, int quiet) { - int ret; + int clean, ret; int has_index = index; struct merge_options o; struct object_id c_tree; struct object_id index_tree; - struct commit *result; - const struct object_id *bases[1]; + struct tree *head, *merge, *merge_base; + struct lock_file lock = LOCK_INIT; - read_cache_preload(NULL); - if (refresh_and_write_cache(REFRESH_QUIET, 0, 0)) - return -1; + repo_read_index_preload(the_repository, NULL, 0); + if (repo_refresh_and_write_index(the_repository, REFRESH_QUIET, 0, 0, + NULL, NULL, NULL)) + return error(_("could not write index")); - if (write_cache_as_tree(&c_tree, 0, NULL)) + if (write_index_as_tree(&c_tree, &the_index, get_index_file(), 0, + NULL)) return error(_("cannot apply a stash in the middle of a merge")); if (index) { @@ -510,24 +562,23 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, return error(_("conflicts in index. " "Try without --index.")); - discard_cache(); - read_cache(); - if (write_cache_as_tree(&index_tree, 0, NULL)) + discard_index(&the_index); + repo_read_index(the_repository); + if (write_index_as_tree(&index_tree, &the_index, + get_index_file(), 0, NULL)) return error(_("could not save index tree")); reset_head(); - discard_cache(); - read_cache(); + discard_index(&the_index); + repo_read_index(the_repository); } } - if (info->has_u && restore_untracked(&info->u_tree)) - return error(_("could not restore untracked files from stash")); - init_merge_options(&o, the_repository); o.branch1 = "Updated upstream"; o.branch2 = "Stashed changes"; + o.ancestor = "Stash base"; if (oideq(&info->b_tree, &c_tree)) o.branch1 = "Version stash was based on"; @@ -538,26 +589,46 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, if (o.verbosity >= 3) printf_ln(_("Merging %s with %s"), o.branch1, o.branch2); - bases[0] = &info->b_tree; + head = lookup_tree(o.repo, &c_tree); + merge = lookup_tree(o.repo, &info->w_tree); + merge_base = lookup_tree(o.repo, &info->b_tree); + + repo_hold_locked_index(o.repo, &lock, LOCK_DIE_ON_ERROR); + clean = merge_ort_nonrecursive(&o, head, merge, merge_base); + + /* + * If 'clean' >= 0, reverse the value for 'ret' so 'ret' is 0 when the + * merge was clean, and nonzero if the merge was unclean or encountered + * an error. + */ + ret = clean >= 0 ? !clean : clean; + + if (ret < 0) + rollback_lock_file(&lock); + else if (write_locked_index(o.repo->index, &lock, + COMMIT_LOCK | SKIP_IF_UNCHANGED)) + ret = error(_("could not write index")); - ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases, - &result); if (ret) { - rerere(0); + repo_rerere(the_repository, 0); if (index) fprintf_ln(stderr, _("Index was not unstashed.")); - return ret; + goto restore_untracked; } if (has_index) { if (reset_tree(&index_tree, 0, 0)) - return -1; + ret = -1; } else { unstage_changes_unless_new(&c_tree); } +restore_untracked: + if (info->has_u && restore_untracked(&info->u_tree)) + ret = error(_("could not restore untracked files from stash")); + if (!quiet) { struct child_process cp = CHILD_PROCESS_INIT; @@ -568,23 +639,23 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, */ cp.git_cmd = 1; cp.dir = prefix; - strvec_pushf(&cp.env_array, GIT_WORK_TREE_ENVIRONMENT"=%s", + strvec_pushf(&cp.env, GIT_WORK_TREE_ENVIRONMENT"=%s", absolute_path(get_git_work_tree())); - strvec_pushf(&cp.env_array, GIT_DIR_ENVIRONMENT"=%s", + strvec_pushf(&cp.env, GIT_DIR_ENVIRONMENT"=%s", absolute_path(get_git_dir())); strvec_push(&cp.args, "status"); run_command(&cp); } - return 0; + return ret; } static int apply_stash(int argc, const char **argv, const char *prefix) { - int ret; + int ret = -1; int quiet = 0; int index = 0; - struct stash_info info; + struct stash_info info = STASH_INFO_INIT; struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_BOOL(0, "index", &index, @@ -596,16 +667,20 @@ static int apply_stash(int argc, const char **argv, const char *prefix) git_stash_apply_usage, 0); if (get_stash_info(&info, argc, argv)) - return -1; + goto cleanup; ret = do_apply_stash(prefix, &info, index, quiet); +cleanup: free_stash_info(&info); return ret; } -static int reject_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *email, timestamp_t timestamp, int tz, - const char *message, void *cb_data) +static int reject_reflog_ent(struct object_id *ooid UNUSED, + struct object_id *noid UNUSED, + const char *email UNUSED, + timestamp_t timestamp UNUSED, + int tz UNUSED, const char *message UNUSED, + void *cb_data UNUSED) { return 1; } @@ -617,20 +692,9 @@ static int reflog_is_empty(const char *refname) static int do_drop_stash(struct stash_info *info, int quiet) { - int ret; - struct child_process cp_reflog = CHILD_PROCESS_INIT; - - /* - * reflog does not provide a simple function for deleting refs. One will - * need to be added to avoid implementing too much reflog code here - */ - - cp_reflog.git_cmd = 1; - strvec_pushl(&cp_reflog.args, "reflog", "delete", "--updateref", - "--rewrite", NULL); - strvec_push(&cp_reflog.args, info->revision.buf); - ret = run_command(&cp_reflog); - if (!ret) { + if (!reflog_delete(info->revision.buf, + EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_UPDATE_REF, + 0)) { if (!quiet) printf_ln(_("Dropped %s (%s)"), info->revision.buf, oid_to_hex(&info->w_commit)); @@ -645,20 +709,25 @@ static int do_drop_stash(struct stash_info *info, int quiet) return 0; } -static void assert_stash_ref(struct stash_info *info) +static int get_stash_info_assert(struct stash_info *info, int argc, + const char **argv) { - if (!info->is_stash_ref) { - error(_("'%s' is not a stash reference"), info->revision.buf); - free_stash_info(info); - exit(1); - } + int ret = get_stash_info(info, argc, argv); + + if (ret < 0) + return ret; + + if (!info->is_stash_ref) + return error(_("'%s' is not a stash reference"), info->revision.buf); + + return 0; } static int drop_stash(int argc, const char **argv, const char *prefix) { - int ret; + int ret = -1; int quiet = 0; - struct stash_info info; + struct stash_info info = STASH_INFO_INIT; struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_END() @@ -667,22 +736,21 @@ static int drop_stash(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_stash_drop_usage, 0); - if (get_stash_info(&info, argc, argv)) - return -1; - - assert_stash_ref(&info); + if (get_stash_info_assert(&info, argc, argv)) + goto cleanup; ret = do_drop_stash(&info, quiet); +cleanup: free_stash_info(&info); return ret; } static int pop_stash(int argc, const char **argv, const char *prefix) { - int ret; + int ret = -1; int index = 0; int quiet = 0; - struct stash_info info; + struct stash_info info = STASH_INFO_INIT; struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_BOOL(0, "index", &index, @@ -693,25 +761,25 @@ static int pop_stash(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_stash_pop_usage, 0); - if (get_stash_info(&info, argc, argv)) - return -1; + if (get_stash_info_assert(&info, argc, argv)) + goto cleanup; - assert_stash_ref(&info); if ((ret = do_apply_stash(prefix, &info, index, quiet))) printf_ln(_("The stash entry is kept in case " "you need it again.")); else ret = do_drop_stash(&info, quiet); +cleanup: free_stash_info(&info); return ret; } static int branch_stash(int argc, const char **argv, const char *prefix) { - int ret; + int ret = -1; const char *branch = NULL; - struct stash_info info; + struct stash_info info = STASH_INFO_INIT; struct child_process cp = CHILD_PROCESS_INIT; struct option options[] = { OPT_END() @@ -728,7 +796,7 @@ static int branch_stash(int argc, const char **argv, const char *prefix) branch = argv[0]; if (get_stash_info(&info, argc - 1, argv + 1)) - return -1; + goto cleanup; cp.git_cmd = 1; strvec_pushl(&cp.args, "checkout", "-b", NULL); @@ -740,8 +808,8 @@ static int branch_stash(int argc, const char **argv, const char *prefix) if (!ret && info.is_stash_ref) ret = do_drop_stash(&info, 0); +cleanup: free_stash_info(&info); - return ret; } @@ -754,7 +822,7 @@ static int list_stash(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_stash_list_usage, - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); if (!ref_exists(ref_stash)) return 0; @@ -771,9 +839,9 @@ static int list_stash(int argc, const char **argv, const char *prefix) static int show_stat = 1; static int show_patch; static int show_include_untracked; -static int use_legacy_stash; -static int git_stash_config(const char *var, const char *value, void *cb) +static int git_stash_config(const char *var, const char *value, + const struct config_context *ctx, void *cb) { if (!strcmp(var, "stash.showstat")) { show_stat = git_config_bool(var, value); @@ -787,11 +855,7 @@ static int git_stash_config(const char *var, const char *value, void *cb) show_include_untracked = git_config_bool(var, value); return 0; } - if (!strcmp(var, "stash.usebuiltin")) { - use_legacy_stash = !git_config_bool(var, value); - return 0; - } - return git_diff_basic_config(var, value, cb); + return git_diff_basic_config(var, value, ctx, cb); } static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt) @@ -806,7 +870,8 @@ static void diff_include_untracked(const struct stash_info *info, struct diff_op tree[i] = parse_tree_indirect(oid[i]); if (parse_tree(tree[i]) < 0) die(_("failed to parse tree")); - init_tree_desc(&tree_desc[i], tree[i]->buffer, tree[i]->size); + init_tree_desc(&tree_desc[i], &tree[i]->object.oid, + tree[i]->buffer, tree[i]->size); } unpack_tree_opt.head_idx = -1; @@ -824,8 +889,8 @@ static void diff_include_untracked(const struct stash_info *info, struct diff_op static int show_stash(int argc, const char **argv, const char *prefix) { int i; - int ret = 0; - struct stash_info info; + int ret = -1; + struct stash_info info = STASH_INFO_INIT; struct rev_info rev; struct strvec stash_args = STRVEC_INIT; struct strvec revision_args = STRVEC_INIT; @@ -843,13 +908,14 @@ static int show_stash(int argc, const char **argv, const char *prefix) UNTRACKED_ONLY, PARSE_OPT_NONEG), OPT_END() }; + int do_usage = 0; init_diff_ui_defaults(); git_config(git_diff_ui_config, NULL); - init_revisions(&rev, prefix); + repo_init_revisions(the_repository, &rev, prefix); argc = parse_options(argc, argv, prefix, options, git_stash_show_usage, - PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_KEEP_DASHDASH); strvec_push(&revision_args, argv[0]); @@ -860,10 +926,8 @@ static int show_stash(int argc, const char **argv, const char *prefix) strvec_push(&revision_args, argv[i]); } - ret = get_stash_info(&info, stash_args.nr, stash_args.v); - strvec_clear(&stash_args); - if (ret) - return -1; + if (get_stash_info(&info, stash_args.nr, stash_args.v)) + goto cleanup; /* * The config settings are applied only if there are not passed @@ -877,16 +941,14 @@ static int show_stash(int argc, const char **argv, const char *prefix) rev.diffopt.output_format |= DIFF_FORMAT_PATCH; if (!show_stat && !show_patch) { - free_stash_info(&info); - return 0; + ret = 0; + goto cleanup; } } argc = setup_revisions(revision_args.nr, revision_args.v, &rev, NULL); - if (argc > 1) { - free_stash_info(&info); - usage_with_options(git_stash_show_usage, options); - } + if (argc > 1) + goto usage; if (!rev.diffopt.output_format) { rev.diffopt.output_format = DIFF_FORMAT_PATCH; diff_setup_done(&rev.diffopt); @@ -911,13 +973,28 @@ static int show_stash(int argc, const char **argv, const char *prefix) } log_tree_diff_flush(&rev); + ret = diff_result_code(&rev.diffopt); +cleanup: + strvec_clear(&stash_args); free_stash_info(&info); - return diff_result_code(&rev.diffopt, 0); + release_revisions(&rev); + if (do_usage) + usage_with_options(git_stash_show_usage, options); + return ret; +usage: + do_usage = 1; + goto cleanup; } static int do_store_stash(const struct object_id *w_commit, const char *stash_msg, int quiet) { + struct stash_info info; + char revision[GIT_MAX_HEXSZ]; + + oid_to_hex_r(revision, w_commit); + assert_stash_like(&info, revision); + if (!stash_msg) stash_msg = "Created via \"git stash store\"."; @@ -950,7 +1027,7 @@ static int store_stash(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_stash_store_usage, - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); if (argc != 1) { if (!quiet) @@ -1018,19 +1095,18 @@ static int get_untracked_files(const struct pathspec *ps, int include_untracked, */ static int check_changes_tracked_files(const struct pathspec *ps) { - int result; struct rev_info rev; struct object_id dummy; int ret = 0; /* No initial commit. */ - if (get_oid("HEAD", &dummy)) + if (repo_get_oid(the_repository, "HEAD", &dummy)) return -1; - if (read_cache() < 0) + if (repo_read_index(the_repository) < 0) return -1; - init_revisions(&rev, NULL); + repo_init_revisions(the_repository, &rev, NULL); copy_pathspec(&rev.prune_data, ps); rev.diffopt.flags.quick = 1; @@ -1040,21 +1116,20 @@ static int check_changes_tracked_files(const struct pathspec *ps) add_head_to_pending(&rev); diff_setup_done(&rev.diffopt); - result = run_diff_index(&rev, 1); - if (diff_result_code(&rev.diffopt, result)) { + run_diff_index(&rev, DIFF_INDEX_CACHED); + if (diff_result_code(&rev.diffopt)) { ret = 1; goto done; } - object_array_clear(&rev.pending); - result = run_diff_files(&rev, 0); - if (diff_result_code(&rev.diffopt, result)) { + run_diff_files(&rev, 0); + if (diff_result_code(&rev.diffopt)) { ret = 1; goto done; } done: - clear_pathspec(&rev.prune_data); + release_revisions(&rev); return ret; } @@ -1082,12 +1157,12 @@ 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_upd_index = CHILD_PROCESS_INIT; - struct index_state istate = { NULL }; + struct index_state istate = INDEX_STATE_INIT(the_repository); cp_upd_index.git_cmd = 1; strvec_pushl(&cp_upd_index.args, "update-index", "-z", "--add", "--remove", "--stdin", NULL); - strvec_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", + strvec_pushf(&cp_upd_index.env, "GIT_INDEX_FILE=%s", stash_index_path.buf); strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf); @@ -1110,26 +1185,58 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg, } done: - discard_index(&istate); + release_index(&istate); strbuf_release(&untracked_msg); remove_path(stash_index_path.buf); return ret; } +static int stash_staged(struct stash_info *info, struct strbuf *out_patch, + int quiet) +{ + int ret = 0; + struct child_process cp_diff_tree = CHILD_PROCESS_INIT; + struct index_state istate = INDEX_STATE_INIT(the_repository); + + if (write_index_as_tree(&info->w_tree, &istate, the_repository->index_file, + 0, NULL)) { + ret = -1; + goto done; + } + + cp_diff_tree.git_cmd = 1; + strvec_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD", + oid_to_hex(&info->w_tree), "--", NULL); + if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) { + ret = -1; + goto done; + } + + if (!out_patch->len) { + if (!quiet) + fprintf_ln(stderr, _("No staged changes")); + ret = 1; + } + +done: + release_index(&istate); + return ret; +} + static int stash_patch(struct stash_info *info, const struct pathspec *ps, struct strbuf *out_patch, int quiet) { int ret = 0; struct child_process cp_read_tree = CHILD_PROCESS_INIT; struct child_process cp_diff_tree = CHILD_PROCESS_INIT; - struct index_state istate = { NULL }; + struct index_state istate = INDEX_STATE_INIT(the_repository); char *old_index_env = NULL, *old_repo_index_file; remove_path(stash_index_path.buf); cp_read_tree.git_cmd = 1; strvec_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL); - strvec_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s", + strvec_pushf(&cp_read_tree.env, "GIT_INDEX_FILE=%s", stash_index_path.buf); if (run_command(&cp_read_tree)) { ret = -1; @@ -1142,7 +1249,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); - ret = run_add_interactive(NULL, "--patch=stash", ps); + ret = !!run_add_p(the_repository, ADD_P_STASH, NULL, ps); the_repository->index_file = old_repo_index_file; if (old_index_env && *old_index_env) @@ -1173,7 +1280,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, } done: - discard_index(&istate); + release_index(&istate); remove_path(stash_index_path.buf); return ret; } @@ -1184,9 +1291,9 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps struct rev_info rev; struct child_process cp_upd_index = CHILD_PROCESS_INIT; struct strbuf diff_output = STRBUF_INIT; - struct index_state istate = { NULL }; + struct index_state istate = INDEX_STATE_INIT(the_repository); - init_revisions(&rev, NULL); + repo_init_revisions(the_repository, &rev, NULL); copy_pathspec(&rev.prune_data, ps); set_alternate_index_output(stash_index_path.buf); @@ -1200,23 +1307,20 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps rev.diffopt.format_callback = add_diff_to_buf; rev.diffopt.format_callback_data = &diff_output; - if (read_cache_preload(&rev.diffopt.pathspec) < 0) { + if (repo_read_index_preload(the_repository, &rev.diffopt.pathspec, 0) < 0) { ret = -1; goto done; } add_pending_object(&rev, parse_object(the_repository, &info->b_commit), ""); - if (run_diff_index(&rev, 0)) { - ret = -1; - goto done; - } + run_diff_index(&rev, 0); cp_upd_index.git_cmd = 1; strvec_pushl(&cp_upd_index.args, "update-index", "--ignore-skip-worktree-entries", "-z", "--add", "--remove", "--stdin", NULL); - strvec_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", + strvec_pushf(&cp_upd_index.env, "GIT_INDEX_FILE=%s", stash_index_path.buf); if (pipe_command(&cp_upd_index, diff_output.buf, diff_output.len, @@ -1232,17 +1336,15 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps } done: - discard_index(&istate); - UNLEAK(rev); - object_array_clear(&rev.pending); - clear_pathspec(&rev.prune_data); + release_index(&istate); + release_revisions(&rev); strbuf_release(&diff_output); remove_path(stash_index_path.buf); return ret; } static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf, - int include_untracked, int patch_mode, + int include_untracked, int patch_mode, int only_staged, struct stash_info *info, struct strbuf *patch, int quiet) { @@ -1260,13 +1362,14 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b prepare_fallback_ident("git stash", "git@stash"); - read_cache_preload(NULL); - if (refresh_and_write_cache(REFRESH_QUIET, 0, 0) < 0) { - ret = -1; + repo_read_index_preload(the_repository, NULL, 0); + if (repo_refresh_and_write_index(the_repository, REFRESH_QUIET, 0, 0, + NULL, NULL, NULL) < 0) { + ret = error(_("could not write index")); goto done; } - if (get_oid("HEAD", &info->b_commit)) { + if (repo_get_oid(the_repository, "HEAD", &info->b_commit)) { if (!quiet) fprintf_ln(stderr, _("You do not have " "the initial commit yet")); @@ -1283,15 +1386,17 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b branch_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flags); if (flags & REF_ISSYMREF) - branch_name = strrchr(branch_ref, '/') + 1; - head_short_sha1 = find_unique_abbrev(&head_commit->object.oid, - DEFAULT_ABBREV); + skip_prefix(branch_ref, "refs/heads/", &branch_name); + head_short_sha1 = repo_find_unique_abbrev(the_repository, + &head_commit->object.oid, + DEFAULT_ABBREV); 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); - if (write_cache_as_tree(&info->i_tree, 0, NULL) || + if (write_index_as_tree(&info->i_tree, &the_index, get_index_file(), 0, + NULL) || commit_tree(commit_tree_label.buf, commit_tree_label.len, &info->i_tree, parents, &info->i_commit, NULL, NULL)) { if (!quiet) @@ -1321,6 +1426,16 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b } else if (ret > 0) { goto done; } + } else if (only_staged) { + ret = stash_staged(info, patch, quiet); + if (ret < 0) { + if (!quiet) + fprintf_ln(stderr, _("Cannot save the current " + "staged state")); + goto done; + } else if (ret > 0) { + goto done; + } } else { if (stash_working_tree(info, ps)) { if (!quiet) @@ -1365,11 +1480,11 @@ done: return ret; } -static int create_stash(int argc, const char **argv, const char *prefix) +static int create_stash(int argc, const char **argv, const char *prefix UNUSED) { - int ret = 0; + int ret; struct strbuf stash_msg_buf = STRBUF_INIT; - struct stash_info info; + struct stash_info info = STASH_INFO_INIT; struct pathspec ps; /* Starting with argv[1], since argv[0] is "create" */ @@ -1379,20 +1494,21 @@ static int create_stash(int argc, const char **argv, const char *prefix) if (!check_changes_tracked_files(&ps)) return 0; - ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info, + ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info, NULL, 0); if (!ret) printf_ln("%s", oid_to_hex(&info.w_commit)); + free_stash_info(&info); strbuf_release(&stash_msg_buf); return ret; } static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet, - int keep_index, int patch_mode, int include_untracked) + int keep_index, int patch_mode, int include_untracked, int only_staged) { int ret = 0; - struct stash_info info; + struct stash_info info = STASH_INFO_INIT; struct strbuf patch = STRBUF_INIT; struct strbuf stash_msg_buf = STRBUF_INIT; struct strbuf untracked_files = STRBUF_INIT; @@ -1407,15 +1523,26 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q goto done; } - read_cache_preload(NULL); + /* --patch overrides --staged */ + if (patch_mode) + only_staged = 0; + + if (only_staged && include_untracked) { + fprintf_ln(stderr, _("Can't use --staged and --include-untracked" + " or --all at the same time")); + ret = -1; + goto done; + } + + repo_read_index_preload(the_repository, NULL, 0); if (!include_untracked && ps->nr) { int i; char *ps_matched = xcalloc(ps->nr, 1); /* TODO: audit for interaction with sparse-index. */ ensure_full_index(&the_index); - for (i = 0; i < active_nr; i++) - ce_path_match(&the_index, active_cache[i], ps, + for (i = 0; i < the_index.cache_nr; i++) + ce_path_match(&the_index, the_index.cache[i], ps, ps_matched); if (report_path_error(ps_matched, ps)) { @@ -1427,8 +1554,9 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q free(ps_matched); } - if (refresh_and_write_cache(REFRESH_QUIET, 0, 0)) { - ret = -1; + if (repo_refresh_and_write_index(the_repository, REFRESH_QUIET, 0, 0, + NULL, NULL, NULL)) { + ret = error(_("could not write index")); goto done; } @@ -1447,7 +1575,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q if (stash_msg) strbuf_addstr(&stash_msg_buf, stash_msg); - if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, + if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged, &info, &patch, quiet)) { ret = -1; goto done; @@ -1464,13 +1592,19 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q printf_ln(_("Saved working directory and index state %s"), stash_msg_buf.buf); - if (!patch_mode) { + if (!(patch_mode || only_staged)) { if (include_untracked && !ps->nr) { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; + if (startup_info->original_cwd) { + cp.dir = startup_info->original_cwd; + strvec_pushf(&cp.env, "%s=%s", + GIT_WORK_TREE_ENVIRONMENT, + the_repository->worktree); + } strvec_pushl(&cp.args, "clean", "--force", - "--quiet", "-d", NULL); + "--quiet", "-d", ":/", NULL); if (include_untracked == INCLUDE_ALL_FILES) strvec_push(&cp.args, "-x"); if (run_command(&cp)) { @@ -1478,7 +1612,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q goto done; } } - discard_cache(); + discard_index(&the_index); if (ps->nr) { struct child_process cp_add = CHILD_PROCESS_INIT; struct child_process cp_diff = CHILD_PROCESS_INIT; @@ -1519,6 +1653,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q } else { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; + /* BUG: this nukes untracked files in the way */ strvec_pushl(&cp.args, "reset", "--hard", "-q", "--no-recurse-submodules", NULL); if (run_command(&cp)) { @@ -1561,7 +1696,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; - strvec_pushl(&cp.args, "reset", "-q", "--", NULL); + strvec_pushl(&cp.args, "reset", "-q", "--refresh", "--", + NULL); add_pathspecs(&cp.args, ps); if (run_command(&cp)) { ret = -1; @@ -1572,7 +1708,10 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q } done: + strbuf_release(&patch); + free_stash_info(&info); strbuf_release(&stash_msg_buf); + strbuf_release(&untracked_files); return ret; } @@ -1581,6 +1720,7 @@ static int push_stash(int argc, const char **argv, const char *prefix, { int force_assume = 0; int keep_index = -1; + int only_staged = 0; int patch_mode = 0; int include_untracked = 0; int quiet = 0; @@ -1591,6 +1731,8 @@ static int push_stash(int argc, const char **argv, const char *prefix, struct option options[] = { OPT_BOOL('k', "keep-index", &keep_index, N_("keep index")), + OPT_BOOL('S', "staged", &only_staged, + N_("stash staged changes only")), OPT_BOOL('p', "patch", &patch_mode, N_("stash in patch mode")), OPT__QUIET(&quiet, N_("quiet mode")), @@ -1604,10 +1746,12 @@ static int push_stash(int argc, const char **argv, const char *prefix, OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END() }; + int ret; if (argc) { force_assume = !strcmp(argv[0], "-p"); argc = parse_options(argc, argv, prefix, options, + push_assumed ? git_stash_usage : git_stash_push_usage, PARSE_OPT_KEEP_DASHDASH); } @@ -1627,25 +1771,36 @@ static int push_stash(int argc, const char **argv, const char *prefix, if (pathspec_from_file) { if (patch_mode) - die(_("--pathspec-from-file is incompatible with --patch")); + die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--patch"); + + if (only_staged) + die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--staged"); if (ps.nr) - die(_("--pathspec-from-file is incompatible with pathspec arguments")); + die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file"); parse_pathspec_file(&ps, 0, PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN, prefix, pathspec_from_file, pathspec_file_nul); } else if (pathspec_file_nul) { - die(_("--pathspec-file-nul requires --pathspec-from-file")); + die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file"); } - return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode, - include_untracked); + ret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode, + include_untracked, only_staged); + clear_pathspec(&ps); + return ret; +} + +static int push_stash_unassumed(int argc, const char **argv, const char *prefix) +{ + return push_stash(argc, argv, prefix, 0); } static int save_stash(int argc, const char **argv, const char *prefix) { int keep_index = -1; + int only_staged = 0; int patch_mode = 0; int include_untracked = 0; int quiet = 0; @@ -1656,6 +1811,8 @@ static int save_stash(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_BOOL('k', "keep-index", &keep_index, N_("keep index")), + OPT_BOOL('S', "staged", &only_staged, + N_("stash staged changes only")), OPT_BOOL('p', "patch", &patch_mode, N_("stash in patch mode")), OPT__QUIET(&quiet, N_("quiet mode")), @@ -1677,7 +1834,7 @@ static int save_stash(int argc, const char **argv, const char *prefix) memset(&ps, 0, sizeof(ps)); ret = do_push_stash(&ps, stash_msg, quiet, keep_index, - patch_mode, include_untracked); + patch_mode, include_untracked, only_staged); strbuf_release(&stash_msg_buf); return ret; @@ -1688,52 +1845,40 @@ int cmd_stash(int argc, const char **argv, const char *prefix) pid_t pid = getpid(); const char *index_file; struct strvec args = STRVEC_INIT; - + parse_opt_subcommand_fn *fn = NULL; struct option options[] = { + OPT_SUBCOMMAND("apply", &fn, apply_stash), + OPT_SUBCOMMAND("clear", &fn, clear_stash), + OPT_SUBCOMMAND("drop", &fn, drop_stash), + OPT_SUBCOMMAND("pop", &fn, pop_stash), + OPT_SUBCOMMAND("branch", &fn, branch_stash), + OPT_SUBCOMMAND("list", &fn, list_stash), + OPT_SUBCOMMAND("show", &fn, show_stash), + OPT_SUBCOMMAND("store", &fn, store_stash), + OPT_SUBCOMMAND("create", &fn, create_stash), + OPT_SUBCOMMAND("push", &fn, push_stash_unassumed), + OPT_SUBCOMMAND_F("save", &fn, save_stash, PARSE_OPT_NOCOMPLETE), OPT_END() }; git_config(git_stash_config, NULL); - if (use_legacy_stash || - !git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1)) - warning(_("the stash.useBuiltin support has been removed!\n" - "See its entry in 'git help config' for details.")); - argc = parse_options(argc, argv, prefix, options, git_stash_usage, - PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); + PARSE_OPT_SUBCOMMAND_OPTIONAL | + PARSE_OPT_KEEP_UNKNOWN_OPT | + PARSE_OPT_KEEP_DASHDASH); + + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; index_file = get_index_file(); strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file, (uintmax_t)pid); - if (!argc) - return !!push_stash(0, NULL, prefix, 0); - else if (!strcmp(argv[0], "apply")) - return !!apply_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "clear")) - return !!clear_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "drop")) - return !!drop_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "pop")) - return !!pop_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "branch")) - return !!branch_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "list")) - return !!list_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "show")) - return !!show_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "store")) - return !!store_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "create")) - return !!create_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "push")) - return !!push_stash(argc, argv, prefix, 0); - else if (!strcmp(argv[0], "save")) - return !!save_stash(argc, argv, prefix); - else if (*argv[0] != '-') - usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), - git_stash_usage, options); + if (fn) + return !!fn(argc, argv, prefix); + else if (!argc) + return !!push_stash_unassumed(0, NULL, prefix); /* Assume 'stash push' */ strvec_push(&args, "push"); |