Reputation: 6994
There are cases where I want to discard all the files currently in the staging area and only the files currently in the staging area. Essentially I want something that is just like making a commit and then immediately discarding the commit that was just made.
Right now in practice I mostly use git stash
for this purpose and just remember that I don't actually want the thing that I'm stashing.
However, stash
ing and discarding are not really the same thing and stash
appears to capture all the tracked files, not just the ones currently in the staging area, as the script below shows.
Is there a way to completely discard (changes to) exactly the files that are currently in the staging area?
#!/bin/bash
mkdir -p ./a
echo foo > ./a/foo
echo bar > ./a/bar
(
# add initial commit
cd ./a
git init
git add foo
git add bar
git commit --message='initial commit'
# change first line of foo and bar
ed ./foo <<'EOF'
s/$/ aaaaaa/
wq
EOF
ed ./bar <<'EOF'
s/$/ aaaaaaa/
wq
EOF
git add ./foo
git stash save
git status
)
When run, this script produces the following output
$ bash gitrepo.sh
Initialized empty Git repository in ~/git/a/a/.git/
[master (root-commit) a8f0104] initial commit
2 files changed, 2 insertions(+)
create mode 100644 bar
create mode 100644 foo
4
11
4
12
Saved working directory and index state WIP on master: a8f0104 initial commit
HEAD is now at a8f0104 initial commit
I've also tried essentially the same script with git clean -f
and git reset
in place of git stash save
git clean -f
appears to discard neither the changes to foo
nor to bar
.
[master (root-commit) 0c10b47] initial commit
2 files changed, 2 insertions(+)
create mode 100644 bar
create mode 100644 foo
4
11
4
12
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: foo
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: bar
git reset
in this specific case appears to simply remove foo
from the staging area.
[master (root-commit) 65fce7b] initial commit
2 files changed, 2 insertions(+)
create mode 100644 bar
create mode 100644 foo
4
11
4
12
Unstaged changes after reset:
M bar
M foo
The current best solution I have so far is the following script, which has the right behavior in the few cases I've tested it with. However, it accomplishes this behavior by creating, committing to, and then deleting a new branch named by a UUID. I'd prefer a way of discarding the changes in the staging area that's less circuitous.
#!/bin/bash
branch="$(python -c 'import uuid; print(str(uuid.uuid4()))')"
git checkout -b "$branch"
git commit -m "commit for $branch"
git checkout -
git branch -D "$branch"
When using gitdiscard
(the script above), the tester script produces the right results, foo's change is discarded, but the unstaged modifications to bar
are still there.
[master (root-commit) e4e81d9] initial commit
2 files changed, 2 insertions(+)
create mode 100644 bar
create mode 100644 foo
4
11
4
12
M bar
M foo
Switched to a new branch 'abebe893-e579-4ecc-9422-6c99051d67f6'
[abebe893-e579-4ecc-9422-6c99051d67f6 05916d5] commit for abebe893-e579-4ecc-9422-6c99051d67f6
1 file changed, 1 insertion(+), 1 deletion(-)
M bar
Switched to branch 'master'
Deleted branch abebe893-e579-4ecc-9422-6c99051d67f6 (was 05916d5).
Non-exhaustive list of similar but non-duplicate questions:
This question is not a duplicate because it refers to discarding untracked files, not changes in the staging area.
This question is not a duplicate because it refers to clearing the entire working directly, not to discarding changes in the staging area.
This question covers untracked files and untracked directories, but doesn't cover discarding changes to files in the staging area.
Upvotes: 2
Views: 541
Reputation: 21998
You might consider the --keep-index
flag for git stash
It does, in fact, the contrary of what you're asking for : it stashes everything but what's in the index.
You could take advantage of this and stash only the unstaged changes, then reset your index and working tree, and finally re-apply the stash.
(I'm unsure of how you want the untracked files to be dealt with, but there's also the stash flag --include-untracked
to keep in mind as a possibility. Use it on the first stash if you need to include them in the final result)
git stash --keep-index [--include-untracked]
git reset --hard
git stash apply
Upvotes: 3
Reputation: 488453
First, remember that when git status
says:
nothing to commit, working tree clean
this does not mean that the staging area is empty. In fact, the staging area is perfectly full: every file in the staging area matches every file in the current (or HEAD
) commit. Meanwhile every file in the staging area also matches every tracked file in the work-tree.
Is there a way to completely discard (changes to) exactly the files that are currently in the staging area?
Consider git reset
, git checkout
, and—in Git 2.23 (which I have not yet used) git restore
:
The git reset
command has a mode in which it copies one file from the current commit, i.e., HEAD
, to the index / staging-area, without touching the work-tree at all:
git reset -- file
The --
is only needed if the file name file
resembles a branch name or a git reset
option, e.g., if you have a file named master
and you want to make the staged version match the committed version, git reset master
is wrong but git reset -- master
is right.
git reset HEAD
does exactly what you've asked for here: it copies every file from the HEAD
commit into the staging area, so that no changes are staged.
If you would like the work-tree copy of the file updated as well, one of the many modes of git checkout
does precisely that:
git checkout HEAD -- file
As before, you need the --
only if the file name resembles a git checkout
option. For instance, if you want to discard staged and/or unstaged changes to a file named --patch
, git checkout HEAD --patch
is wrong but git checkout HEAD -- --patch
is right. (Since you have to specify HEAD
here, git checkout HEAD master
works to throw out staged-and-unstaged changes to a file named master
. However, it's safer to get in the habit of just using the --
every time.)
As before, you can check out every file from the current commit, but this time it requires using the fact that checkout out a folder (directory) name causes Git to check out the contents of that folder recursively, so:
git checkout HEAD -- .
does the trick, though you need .
to refer to the correct level of your work-tree.
In Git 2.23, the new git restore
command gives you the ability to overwrite index, work-tree, or both copies as you like.
Remember, Git keeps three active copies of each file at all times. The committed HEAD
copy cannot be changed—it's in a commit and commits cannot be changed—but it can be accessed with git reset
and git checkout HEAD
. The index copy—also called the staging copy—can be overwritten at any time by:
HEAD
: now no changes are staged, orThe work-tree copy is a plain ordinary file, so you can do whatever you want to it; but various forms of git checkout
will overwrite it at your request.
Upvotes: 1