summaryrefslogtreecommitdiff
path: root/builtin/stash.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/stash.c')
-rw-r--r--builtin/stash.c187
1 files changed, 165 insertions, 22 deletions
diff --git a/builtin/stash.c b/builtin/stash.c
index 9bc85f91cd..18c812bbe0 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -10,11 +10,13 @@
#include "strvec.h"
#include "run-command.h"
#include "dir.h"
+#include "entry.h"
#include "rerere.h"
#include "revision.h"
#include "log-tree.h"
#include "diffcore.h"
#include "exec-cmd.h"
+#include "entry.h"
#define INCLUDE_ALL_FILES 2
@@ -24,12 +26,12 @@ static const char * const git_stash_usage[] = {
N_("git stash drop [-q|--quiet] [<stash>]"),
N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
N_("git stash branch <branchname> [<stash>]"),
- N_("git stash clear"),
- N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+ "git stash clear",
+ 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>...]]"),
- N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+ N_("git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\n"
" [-u|--include-untracked] [-a|--all] [<message>]"),
NULL
};
@@ -65,7 +67,7 @@ static const char * const git_stash_branch_usage[] = {
};
static const char * const git_stash_clear_usage[] = {
- N_("git stash clear"),
+ "git stash clear",
NULL
};
@@ -83,11 +85,11 @@ static const char * const git_stash_push_usage[] = {
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>]"),
+ " [-u|--include-untracked] [-a|--all] [<message>]"),
NULL
};
-static const char *ref_stash = "refs/stash";
+static const char ref_stash[] = "refs/stash";
static struct strbuf stash_index_path = STRBUF_INIT;
/*
@@ -222,7 +224,7 @@ static int clear_stash(int argc, const char **argv, const char *prefix)
PARSE_OPT_STOP_AT_NON_OPTION);
if (argc)
- return error(_("git stash clear with parameters is "
+ return error(_("git stash clear with arguments is "
"unimplemented"));
return do_clear_stash();
@@ -254,8 +256,10 @@ static int reset_tree(struct object_id *i_tree, int update, int reset)
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))
@@ -311,6 +315,17 @@ static int reset_head(void)
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,
void *data)
@@ -318,6 +333,9 @@ static void add_diff_to_buf(struct diff_queue_struct *q,
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 */
@@ -519,9 +537,6 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
}
}
- 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";
@@ -556,6 +571,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
unstage_changes_unless_new(&c_tree);
}
+ if (info->has_u && restore_untracked(&info->u_tree))
+ return error(_("could not restore untracked files from stash"));
+
if (!quiet) {
struct child_process cp = CHILD_PROCESS_INIT;
@@ -759,7 +777,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)
cp.git_cmd = 1;
strvec_pushl(&cp.args, "log", "--format=%gd: %gs", "-g",
- "--first-parent", "-m", NULL);
+ "--first-parent", NULL);
strvec_pushv(&cp.args, argv);
strvec_push(&cp.args, ref_stash);
strvec_push(&cp.args, "--");
@@ -768,6 +786,7 @@ 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)
@@ -780,6 +799,10 @@ static int git_stash_config(const char *var, const char *value, void *cb)
show_patch = git_config_bool(var, value);
return 0;
}
+ if (!strcmp(var, "stash.showincludeuntracked")) {
+ 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;
@@ -787,6 +810,33 @@ static int git_stash_config(const char *var, const char *value, void *cb)
return git_diff_basic_config(var, value, cb);
}
+static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)
+{
+ const struct object_id *oid[] = { &info->w_commit, &info->u_tree };
+ struct tree *tree[ARRAY_SIZE(oid)];
+ struct tree_desc tree_desc[ARRAY_SIZE(oid)];
+ struct unpack_trees_options unpack_tree_opt = { 0 };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(oid); i++) {
+ 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);
+ }
+
+ unpack_tree_opt.head_idx = -1;
+ unpack_tree_opt.src_index = &the_index;
+ unpack_tree_opt.dst_index = &the_index;
+ unpack_tree_opt.merge = 1;
+ unpack_tree_opt.fn = stash_worktree_untracked_merge;
+
+ if (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt))
+ die(_("failed to unpack trees"));
+
+ do_diff_cache(&info->b_commit, diff_opt);
+}
+
static int show_stash(int argc, const char **argv, const char *prefix)
{
int i;
@@ -795,7 +845,18 @@ static int show_stash(int argc, const char **argv, const char *prefix)
struct rev_info rev;
struct strvec stash_args = STRVEC_INIT;
struct strvec revision_args = STRVEC_INIT;
+ enum {
+ UNTRACKED_NONE,
+ UNTRACKED_INCLUDE,
+ UNTRACKED_ONLY
+ } show_untracked = show_include_untracked ? UNTRACKED_INCLUDE : UNTRACKED_NONE;
struct option options[] = {
+ OPT_SET_INT('u', "include-untracked", &show_untracked,
+ N_("include untracked files in the stash"),
+ UNTRACKED_INCLUDE),
+ OPT_SET_INT_F(0, "only-untracked", &show_untracked,
+ N_("only show untracked files in the stash"),
+ UNTRACKED_ONLY, PARSE_OPT_NONEG),
OPT_END()
};
@@ -803,6 +864,10 @@ static int show_stash(int argc, const char **argv, const char *prefix)
git_config(git_diff_ui_config, NULL);
init_revisions(&rev, prefix);
+ argc = parse_options(argc, argv, prefix, options, git_stash_show_usage,
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
+ PARSE_OPT_KEEP_DASHDASH);
+
strvec_push(&revision_args, argv[0]);
for (i = 1; i < argc; i++) {
if (argv[i][0] != '-')
@@ -845,7 +910,21 @@ static int show_stash(int argc, const char **argv, const char *prefix)
rev.diffopt.flags.recursive = 1;
setup_diff_pager(&rev.diffopt);
- diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
+ switch (show_untracked) {
+ case UNTRACKED_NONE:
+ diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
+ break;
+ case UNTRACKED_ONLY:
+ if (info.has_u)
+ diff_root_tree_oid(&info.u_tree, "", &rev.diffopt);
+ break;
+ case UNTRACKED_INCLUDE:
+ if (info.has_u)
+ diff_include_untracked(&info, &rev.diffopt);
+ else
+ diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
+ break;
+ }
log_tree_diff_flush(&rev);
free_stash_info(&info);
@@ -928,9 +1007,8 @@ static int get_untracked_files(const struct pathspec *ps, int include_untracked,
{
int i;
int found = 0;
- struct dir_struct dir;
+ struct dir_struct dir = DIR_INIT;
- dir_init(&dir);
if (include_untracked != INCLUDE_ALL_FILES)
setup_standard_excludes(&dir);
@@ -1054,6 +1132,38 @@ done:
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 = { NULL };
+
+ 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:
+ discard_index(&istate);
+ return ret;
+}
+
static int stash_patch(struct stash_info *info, const struct pathspec *ps,
struct strbuf *out_patch, int quiet)
{
@@ -1180,7 +1290,7 @@ done:
}
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)
{
@@ -1259,6 +1369,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)
@@ -1317,7 +1437,7 @@ 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));
@@ -1327,7 +1447,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)
}
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;
@@ -1345,11 +1465,24 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
goto done;
}
+ /* --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;
+ }
+
read_cache_preload(NULL);
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,
ps_matched);
@@ -1383,7 +1516,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;
@@ -1400,7 +1533,7 @@ 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;
@@ -1455,6 +1588,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)) {
@@ -1517,6 +1651,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;
@@ -1527,6 +1662,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")),
@@ -1565,6 +1702,9 @@ static int push_stash(int argc, const char **argv, const char *prefix,
if (patch_mode)
die(_("--pathspec-from-file is incompatible with --patch"));
+ if (only_staged)
+ die(_("--pathspec-from-file is incompatible with --staged"));
+
if (ps.nr)
die(_("--pathspec-from-file is incompatible with pathspec arguments"));
@@ -1576,12 +1716,13 @@ static int push_stash(int argc, const char **argv, const char *prefix,
}
return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
- include_untracked);
+ include_untracked, only_staged);
}
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;
@@ -1592,6 +1733,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")),
@@ -1613,7 +1756,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;