#include "builtin.h" #include "config.h" #include "parse-options.h" #include "refs.h" #include "lockfile.h" #include "cache-tree.h" #include "unpack-trees.h" #include "merge-recursive.h" #include "argv-array.h" #include "run-command.h" #include "dir.h" #include "rerere.h" static const char * const git_stash_helper_usage[] = { N_("git stash--helper drop [-q|--quiet] []"), N_("git stash--helper apply [--index] [-q|--quiet] []"), N_("git stash--helper clear"), NULL }; static const char * const git_stash_helper_drop_usage[] = { N_("git stash--helper drop [-q|--quiet] []"), NULL }; static const char * const git_stash_helper_apply_usage[] = { N_("git stash--helper apply [--index] [-q|--quiet] []"), NULL }; static const char * const git_stash_helper_clear_usage[] = { N_("git stash--helper clear"), NULL }; static const char *ref_stash = "refs/stash"; static struct strbuf stash_index_path = STRBUF_INIT; /* * w_commit is set to the commit containing the working tree * b_commit is set to the base commit * i_commit is set to the commit containing the index tree * u_commit is set to the commit containing the untracked files tree * w_tree is set to the working tree * b_tree is set to the base tree * i_tree is set to the index tree * u_tree is set to the untracked files tree */ struct stash_info { struct object_id w_commit; struct object_id b_commit; struct object_id i_commit; struct object_id u_commit; struct object_id w_tree; struct object_id b_tree; struct object_id i_tree; struct object_id u_tree; struct strbuf revision; int is_stash_ref; int has_u; }; 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) { if (get_oidf(&info->b_commit, "%s^1", revision) || get_oidf(&info->w_tree, "%s:", revision) || get_oidf(&info->b_tree, "%s^1:", revision) || get_oidf(&info->i_tree, "%s^2:", revision)) die(_("'%s' is not a stash-like commit"), revision); } static int get_stash_info(struct stash_info *info, int argc, const char **argv) { int ret; char *end_of_rev; char *expanded_ref; const char *revision; const char *commit = NULL; struct object_id dummy; struct strbuf symbolic = STRBUF_INIT; if (argc > 1) { int i; struct strbuf refs_msg = STRBUF_INIT; for (i = 0; i < argc; i++) strbuf_addf(&refs_msg, " '%s'", argv[i]); fprintf_ln(stderr, _("Too many revisions specified:%s"), refs_msg.buf); strbuf_release(&refs_msg); return -1; } 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; } strbuf_addf(&info->revision, "%s@{0}", ref_stash); } else if (strspn(commit, "0123456789") == strlen(commit)) { strbuf_addf(&info->revision, "%s@{%s}", ref_stash, commit); } else { strbuf_addstr(&info->revision, commit); } 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; } assert_stash_like(info, revision); info->has_u = !get_oidf(&info->u_tree, "%s^3:", revision); end_of_rev = strchrnul(revision, '@'); strbuf_add(&symbolic, revision, end_of_rev - revision); ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref); strbuf_release(&symbolic); switch (ret) { case 0: /* Not found, but valid ref */ info->is_stash_ref = 0; break; case 1: info->is_stash_ref = !strcmp(expanded_ref, ref_stash); break; default: /* Invalid or ambiguous */ free_stash_info(info); } free(expanded_ref); return !(ret == 0 || ret == 1); } static int do_clear_stash(void) { struct object_id obj; if (get_oid(ref_stash, &obj)) return 0; return delete_ref(NULL, ref_stash, &obj, 0); } static int clear_stash(int argc, const char **argv, const char *prefix) { struct option options[] = { OPT_END() }; argc = parse_options(argc, argv, prefix, options, git_stash_helper_clear_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (argc) return error(_("git stash clear with parameters is " "unimplemented")); return do_clear_stash(); } static int reset_tree(struct object_id *i_tree, int update, int reset) { int nr_trees = 1; struct unpack_trees_options opts; struct tree_desc t[MAX_UNPACK_TREES]; struct tree *tree; struct lock_file lock_file = LOCK_INIT; read_cache_preload(NULL); if (refresh_cache(REFRESH_QUIET)) return -1; hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); memset(&opts, 0, sizeof(opts)); tree = parse_tree_indirect(i_tree); if (parse_tree(tree)) return -1; init_tree_desc(t, 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.update = update; opts.fn = oneway_merge; if (unpack_trees(nr_trees, t, &opts)) return -1; if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) return error(_("unable to write new index file")); return 0; } 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); return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); } static int apply_cached(struct strbuf *out) { struct child_process cp = CHILD_PROCESS_INIT; /* * 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(void) { struct child_process cp = CHILD_PROCESS_INIT; /* * Reset is overall quite simple, however there is no current public * API for resetting. */ cp.git_cmd = 1; argv_array_push(&cp.args, "reset"); return run_command(&cp); } static int get_newly_staged(struct strbuf *out, struct object_id *c_tree) { struct child_process cp = CHILD_PROCESS_INIT; const char *c_tree_hex = oid_to_hex(c_tree); /* * diff-index is very similar to diff-tree above, and should be * converted together with update_index. */ cp.git_cmd = 1; argv_array_pushl(&cp.args, "diff-index", "--cached", "--name-only", "--diff-filter=A", NULL); argv_array_push(&cp.args, c_tree_hex); return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); } static int update_index(struct strbuf *out) { struct child_process cp = CHILD_PROCESS_INIT; /* * Update-index is very complicated and may need to have a public * function exposed in order to remove this forking. */ cp.git_cmd = 1; argv_array_pushl(&cp.args, "update-index", "--add", "--stdin", NULL); return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); } static int restore_untracked(struct object_id *u_tree) { int res; struct child_process cp = CHILD_PROCESS_INIT; /* * We need to run restore files from a given index, but without * affecting the current index, so we use GIT_INDEX_FILE with * run_command to fork processes that will not interfere. */ cp.git_cmd = 1; argv_array_push(&cp.args, "read-tree"); argv_array_push(&cp.args, oid_to_hex(u_tree)); argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", stash_index_path.buf); if (run_command(&cp)) { remove_path(stash_index_path.buf); return -1; } child_process_init(&cp); cp.git_cmd = 1; argv_array_pushl(&cp.args, "checkout-index", "--all", NULL); argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", stash_index_path.buf); res = run_command(&cp); remove_path(stash_index_path.buf); return res; } static int do_apply_stash(const char *prefix, struct stash_info *info, int index, int quiet) { int 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]; read_cache_preload(NULL); if (refresh_cache(REFRESH_QUIET)) return -1; if (write_cache_as_tree(&c_tree, 0, NULL)) return error(_("cannot apply a stash in the middle of a merge")); if (index) { if (oideq(&info->b_tree, &info->i_tree) || oideq(&c_tree, &info->i_tree)) { has_index = 0; } else { struct strbuf out = STRBUF_INIT; if (diff_tree_binary(&out, &info->w_commit)) { strbuf_release(&out); return error(_("could not generate diff %s^!."), oid_to_hex(&info->w_commit)); } ret = apply_cached(&out); strbuf_release(&out); if (ret) return error(_("conflicts in index." "Try without --index.")); discard_cache(); read_cache(); if (write_cache_as_tree(&index_tree, 0, NULL)) return error(_("could not save index tree")); reset_head(); } } if (info->has_u && restore_untracked(&info->u_tree)) return error(_("could not restore untracked files from stash")); init_merge_options(&o); o.branch1 = "Updated upstream"; o.branch2 = "Stashed changes"; if (oideq(&info->b_tree, &c_tree)) o.branch1 = "Version stash was based on"; if (quiet) o.verbosity = 0; if (o.verbosity >= 3) printf_ln(_("Merging %s with %s"), o.branch1, o.branch2); bases[0] = &info->b_tree; ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases, &result); if (ret) { rerere(0); if (index) fprintf_ln(stderr, _("Index was not unstashed.")); return ret; } if (has_index) { if (reset_tree(&index_tree, 0, 0)) return -1; } else { struct strbuf out = STRBUF_INIT; if (get_newly_staged(&out, &c_tree)) { strbuf_release(&out); return -1; } if (reset_tree(&c_tree, 0, 1)) { strbuf_release(&out); return -1; } ret = update_index(&out); strbuf_release(&out); if (ret) return -1; discard_cache(); } if (quiet) { if (refresh_cache(REFRESH_QUIET)) warning("could not refresh index"); } else { struct child_process cp = CHILD_PROCESS_INIT; /* * Status is quite simple and could be replaced with calls to * wt_status in the future, but it adds complexities which may * require more tests. */ cp.git_cmd = 1; cp.dir = prefix; argv_array_push(&cp.args, "status"); run_command(&cp); } return 0; } static int apply_stash(int argc, const char **argv, const char *prefix) { int ret; int quiet = 0; int index = 0; struct stash_info info; struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_BOOL(0, "index", &index, N_("attempt to recreate the index")), OPT_END() }; argc = parse_options(argc, argv, prefix, options, git_stash_helper_apply_usage, 0); if (get_stash_info(&info, argc, argv)) return -1; ret = do_apply_stash(prefix, &info, index, quiet); free_stash_info(&info); return ret; } static int do_drop_stash(const char *prefix, struct stash_info *info, int quiet) { int ret; struct child_process cp_reflog = CHILD_PROCESS_INIT; struct child_process cp = 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; argv_array_pushl(&cp_reflog.args, "reflog", "delete", "--updateref", "--rewrite", NULL); argv_array_push(&cp_reflog.args, info->revision.buf); ret = run_command(&cp_reflog); if (!ret) { if (!quiet) 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); } /* * This could easily be replaced by get_oid, but currently it will throw * a fatal error when a reflog is empty, which we can not recover from. */ cp.git_cmd = 1; /* Even though --quiet is specified, rev-parse still outputs the hash */ cp.no_stdout = 1; argv_array_pushl(&cp.args, "rev-parse", "--verify", "--quiet", NULL); argv_array_pushf(&cp.args, "%s@{0}", ref_stash); ret = run_command(&cp); /* do_clear_stash if we just dropped the last stash entry */ if (ret) do_clear_stash(); return 0; } static void assert_stash_ref(struct stash_info *info) { if (!info->is_stash_ref) { error(_("'%s' is not a stash reference"), info->revision.buf); free_stash_info(info); exit(1); } } static int drop_stash(int argc, const char **argv, const char *prefix) { int ret; int quiet = 0; struct stash_info info; struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_END() }; argc = parse_options(argc, argv, prefix, options, git_stash_helper_drop_usage, 0); if (get_stash_info(&info, argc, argv)) return -1; assert_stash_ref(&info); ret = do_drop_stash(prefix, &info, quiet); free_stash_info(&info); return ret; } int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); const char *index_file; struct option options[] = { OPT_END() }; git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, git_stash_helper_usage, PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); index_file = get_index_file(); strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file, (uintmax_t)pid); if (argc < 1) usage_with_options(git_stash_helper_usage, options); 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); usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), git_stash_helper_usage, options); }