1

Imagine you have already staged and committed a number of changes but then decided that the changes ought to be separated into two commits. For newly created files, you can stage removing them from the commit without removing them from disk (with git rm --cached filename). Is there a similar command for modified files?

Essentially I would like to be able to stage the version of the file from the previous commit, and afterwards be able to re-run git add -p filename to select which changes belong in the first new commit vs the second. I don't want anything on-disk to change; my only goal is to split one commit into two with minimal work.

git checkout HEAD~1 -- filename stages what I want, but removes the changes from disk. I need them to stay on disk so I can proceed to add them to the next commit. (Note: You can undo this command with git reset filename && git checkout filename)

git add HEAD~1 -- filename feels like it should do what I want, but the command fails to run with fatal: pathspec 'HEAD~1' did not match any files

Devon Parsons
  • 1,234
  • 14
  • 23
  • Possible duplicate of [How to undo 'git add' before commit?](https://stackoverflow.com/questions/348170/how-to-undo-git-add-before-commit) – phd Apr 25 '19 at 14:23
  • https://stackoverflow.com/search?q=%5Bgit%5D+undo+add – phd Apr 25 '19 at 14:23
  • 3
    `git reset HEAD~1 -- filename` – phd Apr 25 '19 at 14:23
  • Interesting, I thought `reset` only operated on the current staging environment. I still wouldn't call this a duplicate of `git undo add` but nevertheless I appreciate the quick response! Thanks – Devon Parsons Apr 25 '19 at 14:28
  • https://stackoverflow.com/questions/3528245/whats-the-difference-between-git-reset-mixed-soft-and-hard – phd Apr 25 '19 at 15:37

1 Answers1

2

As phd noted, git reset can do this, because one of git reset's many potential jobs is copy a file from a commit to the index without touching the work-tree.

It's a bit surprising because the default job of git reset starts by moving the current branch, as indicated by HEAD, to some new user-specified commit, and only then maybe—depending on --soft vs --mixed vs --hard—also copying files from the user-specified commit to the index, and then maybe on to the work-tree. However, when git reset is used with path names, it changes tactics, dropping the move current branch job entirely. That's why git reset does not allow --hard, --soft, or --mixed with path names.

When git reset is moving the current branch, if you—the user—don't specify some particular commit, the commit that git reset chooses is the one that the current branch names. So this "move" from commit $old to commit $new is one where $old is $new, and there's no motion at all. That's why git reset --soft with no arguments is a no-op, and why git reset --mixed or git reset --hard with no extra arguments does not change which commit is the current commit, but does reset the index and maybe also the work-tree.

Ultimately I always think that git reset packs too many actions into one user-facing command: it should be at least two, and probably more, separate user-facing commands, all of which might run git reset as their plumbing version of how do we accomplish this user-goal-oriented operation. The git checkout command does this too, since it has several different user goals (switch to new branch, get one specific file from one specific commit without switching branches, and so on).

torek
  • 448,244
  • 59
  • 642
  • 775