stash: implement '--staged' option for 'push' and 'save'
Stash only the changes that are staged. This mode allows to easily stash-out for later reuse some changes unrelated to the current work in progress. Unlike 'stash push --patch', --staged supports use of any tool to select the changes to stash-out, including, but not limited to 'git add --interactive'. Signed-off-by: Sergey Organov <sorganov@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Sergey Organov committed
Oct 18, 2021 at 19:09 UTC
41a28eb6c124bd06ca4a4fea9a1da3178a09467b
3 files changed
+113
-12
Documentation/git-stash.txt
+31
-3
@@ -13,7 +13,7 @@ SYNOPSIS
13
'git stash' drop [-q|--quiet] [<stash>]
14
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
15
'git stash' branch <branchname> [<stash>]
16
-'git stash' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
16
+'git stash' [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]
17
[-u|--include-untracked] [-a|--all] [-m|--message <message>]
18
[--pathspec-from-file=<file> [--pathspec-file-nul]]
19
[--] [<pathspec>...]]
@@ -47,7 +47,7 @@ stash index (e.g. the integer `n` is equivalent to `stash@{n}`).
47
COMMANDS
48
--------
49
50
-push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--pathspec-from-file=<file> [--pathspec-file-nul]] [--] [<pathspec>...]::
50
+push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--pathspec-from-file=<file> [--pathspec-file-nul]] [--] [<pathspec>...]::
51
52
Save your local modifications to a new 'stash entry' and roll them
53
back to HEAD (in the working tree and in the index).
@@ -60,7 +60,7 @@ subcommand from making an unwanted stash entry. The two exceptions to this
60
are `stash -p` which acts as alias for `stash push -p` and pathspec elements,
61
which are allowed after a double hyphen `--` for disambiguation.
62
63
-save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
63
+save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
64
65
This option is deprecated in favour of 'git stash push'. It
66
differs from "stash push" in that it cannot take pathspec.
@@ -205,6 +205,16 @@ to learn how to operate the `--patch` mode.
205
The `--patch` option implies `--keep-index`. You can use
206
`--no-keep-index` to override this.
207
208
+-S::
209
+--staged::
210
+ This option is only valid for `push` and `save` commands.
211
++
212
+Stash only the changes that are currently staged. This is similar to
213
+basic `git commit` except the state is committed to the stash instead
214
+of current branch.
215
++
216
+The `--patch` option has priority over this one.
217
+
218
--pathspec-from-file=<file>::
219
This option is only valid for `push` command.
220
+
@@ -341,6 +351,24 @@ $ edit/build/test remaining parts
351
$ git commit foo -m 'Remaining parts'
352
----------------------------------------------------------------
353
354
+Saving unrelated changes for future use::
355
+
356
+When you are in the middle of massive changes and you find some
357
+unrelated issue that you don't want to forget to fix, you can do the
358
+change(s), stage them, and use `git stash push --staged` to stash them
359
+out for future use. This is similar to committing the staged changes,
360
+only the commit ends-up being in the stash and not on the current branch.
361
++
362
+----------------------------------------------------------------
363
+# ... hack hack hack ...
364
+$ git add --patch foo # add unrelated changes to the index
365
+$ git stash push --staged # save these changes to the stash
366
+# ... hack hack hack, finish curent changes ...
367
+$ git commit -m 'Massive' # commit fully tested changes
368
+$ git switch fixup-branch # switch to another branch
369
+$ git stash pop # to finish work on the saved changes
370
+----------------------------------------------------------------
371
+
372
Recovering stash entries that were cleared/dropped erroneously::
373
374
If you mistakenly drop or clear stash entries, they cannot be recovered
builtin/stash.c
+71
-9
@@ -27,11 +27,11 @@ static const char * const git_stash_usage[] = {
27
N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
28
N_("git stash branch <branchname> [<stash>]"),
29
"git stash clear",
30
- N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
30
+ N_("git stash [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\n"
31
" [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
32
" [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
33
" [--] [<pathspec>...]]"),
34
- N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
34
+ N_("git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\n"
35
" [-u|--include-untracked] [-a|--all] [<message>]"),
36
NULL
37
};
@@ -1132,6 +1132,38 @@ done:
1132
return ret;
1133
}
1134
1135
+static int stash_staged(struct stash_info *info, const struct pathspec *ps,
1136
+ struct strbuf *out_patch, int quiet)
1137
+{
1138
+ int ret = 0;
1139
+ struct child_process cp_diff_tree = CHILD_PROCESS_INIT;
1140
+ struct index_state istate = { NULL };
1141
+
1142
+ if (write_index_as_tree(&info->w_tree, &istate, the_repository->index_file,
1143
+ 0, NULL)) {
1144
+ ret = -1;
1145
+ goto done;
1146
+ }
1147
+
1148
+ cp_diff_tree.git_cmd = 1;
1149
+ strvec_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD",
1150
+ oid_to_hex(&info->w_tree), "--", NULL);
1151
+ if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {
1152
+ ret = -1;
1153
+ goto done;
1154
+ }
1155
+
1156
+ if (!out_patch->len) {
1157
+ if (!quiet)
1158
+ fprintf_ln(stderr, _("No staged changes"));
1159
+ ret = 1;
1160
+ }
1161
+
1162
+done:
1163
+ discard_index(&istate);
1164
+ return ret;
1165
+}
1166
+
1167
static int stash_patch(struct stash_info *info, const struct pathspec *ps,
1168
struct strbuf *out_patch, int quiet)
1169
{
@@ -1258,7 +1290,7 @@ done:
1290
}
1291
1292
static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,
1261
- int include_untracked, int patch_mode,
1293
+ int include_untracked, int patch_mode, int only_staged,
1294
struct stash_info *info, struct strbuf *patch,
1295
int quiet)
1296
{
@@ -1337,6 +1369,16 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
1369
} else if (ret > 0) {
1370
goto done;
1371
}
1372
+ } else if (only_staged) {
1373
+ ret = stash_staged(info, ps, patch, quiet);
1374
+ if (ret < 0) {
1375
+ if (!quiet)
1376
+ fprintf_ln(stderr, _("Cannot save the current "
1377
+ "staged state"));
1378
+ goto done;
1379
+ } else if (ret > 0) {
1380
+ goto done;
1381
+ }
1382
} else {
1383
if (stash_working_tree(info, ps)) {
1384
if (!quiet)
@@ -1395,7 +1437,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)
1437
if (!check_changes_tracked_files(&ps))
1438
return 0;
1439
1398
- ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info,
1440
+ ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info,
1441
NULL, 0);
1442
if (!ret)
1443
printf_ln("%s", oid_to_hex(&info.w_commit));
@@ -1405,7 +1447,7 @@ static int create_stash(int argc, const char **argv, const char *prefix)
1447
}
1448
1449
static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,
1408
- int keep_index, int patch_mode, int include_untracked)
1450
+ int keep_index, int patch_mode, int include_untracked, int only_staged)
1451
{
1452
int ret = 0;
1453
struct stash_info info;
@@ -1423,6 +1465,17 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
1465
goto done;
1466
}
1467
1468
+ /* --patch overrides --staged */
1469
+ if (patch_mode)
1470
+ only_staged = 0;
1471
+
1472
+ if (only_staged && include_untracked) {
1473
+ fprintf_ln(stderr, _("Can't use --staged and --include-untracked"
1474
+ " or --all at the same time"));
1475
+ ret = -1;
1476
+ goto done;
1477
+ }
1478
+
1479
read_cache_preload(NULL);
1480
if (!include_untracked && ps->nr) {
1481
int i;
@@ -1463,7 +1516,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
1516
1517
if (stash_msg)
1518
strbuf_addstr(&stash_msg_buf, stash_msg);
1466
- if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,
1519
+ if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged,
1520
&info, &patch, quiet)) {
1521
ret = -1;
1522
goto done;
@@ -1480,7 +1533,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
1533
printf_ln(_("Saved working directory and index state %s"),
1534
stash_msg_buf.buf);
1535
1483
- if (!patch_mode) {
1536
+ if (!(patch_mode || only_staged)) {
1537
if (include_untracked && !ps->nr) {
1538
struct child_process cp = CHILD_PROCESS_INIT;
1539
@@ -1598,6 +1651,7 @@ static int push_stash(int argc, const char **argv, const char *prefix,
1651
{
1652
int force_assume = 0;
1653
int keep_index = -1;
1654
+ int only_staged = 0;
1655
int patch_mode = 0;
1656
int include_untracked = 0;
1657
int quiet = 0;
@@ -1608,6 +1662,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,
1662
struct option options[] = {
1663
OPT_BOOL('k', "keep-index", &keep_index,
1664
N_("keep index")),
1665
+ OPT_BOOL('S', "staged", &only_staged,
1666
+ N_("stash staged changes only")),
1667
OPT_BOOL('p', "patch", &patch_mode,
1668
N_("stash in patch mode")),
1669
OPT__QUIET(&quiet, N_("quiet mode")),
@@ -1646,6 +1702,9 @@ static int push_stash(int argc, const char **argv, const char *prefix,
1702
if (patch_mode)
1703
die(_("--pathspec-from-file is incompatible with --patch"));
1704
1705
+ if (only_staged)
1706
+ die(_("--pathspec-from-file is incompatible with --staged"));
1707
+
1708
if (ps.nr)
1709
die(_("--pathspec-from-file is incompatible with pathspec arguments"));
1710
@@ -1657,12 +1716,13 @@ static int push_stash(int argc, const char **argv, const char *prefix,
1716
}
1717
1718
return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
1660
- include_untracked);
1719
+ include_untracked, only_staged);
1720
}
1721
1722
static int save_stash(int argc, const char **argv, const char *prefix)
1723
{
1724
int keep_index = -1;
1725
+ int only_staged = 0;
1726
int patch_mode = 0;
1727
int include_untracked = 0;
1728
int quiet = 0;
@@ -1673,6 +1733,8 @@ static int save_stash(int argc, const char **argv, const char *prefix)
1733
struct option options[] = {
1734
OPT_BOOL('k', "keep-index", &keep_index,
1735
N_("keep index")),
1736
+ OPT_BOOL('S', "staged", &only_staged,
1737
+ N_("stash staged changes only")),
1738
OPT_BOOL('p', "patch", &patch_mode,
1739
N_("stash in patch mode")),
1740
OPT__QUIET(&quiet, N_("quiet mode")),
@@ -1694,7 +1756,7 @@ static int save_stash(int argc, const char **argv, const char *prefix)
1756
1757
memset(&ps, 0, sizeof(ps));
1758
ret = do_push_stash(&ps, stash_msg, quiet, keep_index,
1697
- patch_mode, include_untracked);
1759
+ patch_mode, include_untracked, only_staged);
1760
1761
strbuf_release(&stash_msg_buf);
1762
return ret;
t/t3903-stash.sh
+11
@@ -288,6 +288,17 @@ test_expect_success 'stash --no-keep-index' '
288
test bar,bar2 = $(cat file),$(cat file2)
289
'
290
291
+test_expect_success 'stash --staged' '
292
+ echo bar3 >file &&
293
+ echo bar4 >file2 &&
294
+ git add file2 &&
295
+ git stash --staged &&
296
+ test bar3,bar2 = $(cat file),$(cat file2) &&
297
+ git reset --hard &&
298
+ git stash pop &&
299
+ test bar,bar4 = $(cat file),$(cat file2)
300
+'
301
+
302
test_expect_success 'dont assume push with non-option args' '
303
test_must_fail git stash -q drop 2>err &&
304
test_i18ngrep -e "subcommand wasn'\''t specified; '\''push'\'' can'\''t be assumed due to unexpected token '\''drop'\''" err