If you don't have a newer git which has the --staged
option, here is how to do it directly.
The git stash
command is just a complicated shell script, which manipulates tree objects and commits and such. We can do the things that it does, manually.
Overview
The stash stack records special commits. We are going to create a commit out of the staged changes, and then manually transfer it into the stash. Then, get rid of the commit.
The setup
I have a project in which there are two changes to the Makefile
. One is staged and one is unstaged:
$ git diff --cached
diff --git a/Makefile b/Makefile
index 4ca6058f..c8c7480a 100644
--- a/Makefile
+++ b/Makefile
@@ -605,7 +605,7 @@ conftest2: conftest1.c conftest2.c
$(V)if echo | $(CC) -dM -E - | grep -s __ANDROID__ > /dev/null 2>&1 ; then \
echo yes ; \
fi
-
+# FOO
.PHONY: conftest.clean
conftest.clean:
$(V)rm -f conftest$(EXE) conftest.[co] \
$ git diff
diff --git a/Makefile b/Makefile
index c8c7480a..270c313d 100644
--- a/Makefile
+++ b/Makefile
@@ -611,3 +611,4 @@ conftest2: conftest1.c conftest2.c
$(V)rm -f conftest$(EXE) conftest.[co] \
conftest2$(EXE) conftest[12].[oc] \
conftest.err
+# BAR
The addition of the # FOO
line is staged; the addition of # BAR
is unstaged.
Step 1: create tree object.
First, we create a tree object from the current index (which holds the staged items).
$ git write-tree
0d9651ad74328e747a053a9434d9867c8cd79d41 <-- output
Step 2: create two commits.
First, create a commit from the tree, which has one parent, the current branch HEAD
:
$ git commit-tree -p HEAD -m 'add # FOO' 0d9651ad74328e747a053a9434d9867c8cd79d41
baa34222e781078d82cefed519ff105715c7f665 <-- output
Then, create another commit from the tree, which has two parents: HEAD
and the baa34222...
commit we just made:
$ git commit-tree -p HEAD -p baa34222e781078d82cefed519ff105715c7f665 -m 'add # FOO' 0d9651ad74328e747a053a9434d9867c8cd79d41
2c96b028e475a05d84f472da7f2a70ac53d0ac90 <-- output
This two-parent 2c96b02...
will be the commit we install into the stash.
Note that git commit-tree
is not git commit
. It is a lower-level command. These commits are not doing anything to your current branch; we are just allocating objects in Git's storage, and not doing anything with the branch that we are on or altering the index or working tree.
Step 3.
Next, we write this commit into .git/refs/stash
. You may want to back up this file.
$ echo 2c96b028e475a05d84f472da7f2a70ac53d0ac90 > .git/refs/stash
Step 4.
We hook the same commit into the .git/logs/refs/stash
file. Before the edit, the last line in the file looks like this:
b1819d98ab24720796315b9497236172d1fb1f5f 3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76 Au Thor <[email protected]> 1654892876 -0700 On master: elim-aliases
We manually add this fake line:
3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76 2c96b028e475a05d84f472da7f2a70ac53d0ac90 Au Thor <[email protected]> 1654892876 -0700 On master: add # FOO
You may also want to back up this file. However, if something goes wrong, things are easy to restore.
Note that the left hash 3b2ecc...
in this new line is the same as the right hash in the previous line. That's the previous stash commit, unrelated to what we are doing here, which has to be repeated to link this line into the stash stack. To the right of it, we have our hash 2c96b028e4...
. Then the rest of the line faked out. There is a hard tab after the timezone -0700
, not spaces. I just copy pasted that.
Step 5.
We verify that we have added the commit to the stash stack:
$ git stash list | head -3
stash@{0}: On master: add # FOO
stash@{1}: On master: elim-aliases
stash@{2}: On master: compiler-safe-eval
and:
$ git stash show -p
diff --git a/Makefile b/Makefile
index 4ca6058f..c8c7480a 100644
--- a/Makefile
+++ b/Makefile
@@ -605,7 +605,7 @@ conftest2: conftest1.c conftest2.c
$(V)if echo | $(CC) -dM -E - | grep -s __ANDROID__ > /dev/null 2>&1 ; then \
echo yes ; \
fi
-
+# FOO
.PHONY: conftest.clean
conftest.clean:
$(V)rm -f conftest$(EXE) conftest.[co] \
There it is; git stash
thinks our commit is a "stash-like" commit, and accepts it.
Summary
We manually took the index with a staged change, and produced a tree object.
We then turned the tree object into a regular commit object and then one more two-parent commit. The two-parent object is acceptable as a stash-like commit.
Lastly, we patched this commit into the stash stack manually by editing a pair of files.
Appendix
We have not executed any unsafe commands that manipulate our index or working tree. However, we have unsafely manipulated the git stash stack. If something goes wrong, here is how to fix it (other than restoring from backup files):
Delete the fake line we added to .git/logs/refs/stash
, so that once again this is the last line:
b1819d98ab24720796315b9497236172d1fb1f5f 3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76 Au Thor <[email protected]> 1654892876 -0700 On master: elim-aliases
Take the right side hash 3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76
and plant it into the .git/refs/stash
file:
$ echo 3b2ecc6604d77c9df4fe72efd1fbd384b2c43f76 > .git/refs/stash`
The previous stash is now restored.
git stash push --staged
. See my answer below – Eleonoreeleoptenepush --staged
switch. – Fortyfive