git checkout HEAD~2 (example) deletes untracked files if they were added with last commit
Solution 1
They should not [be deleted].
Yes, they should. Commits are historical states. You're checking out a state before they were committed, so they should not be there. Just imagine working on your project a year from now. Half the files have been renamed, there are dozens of new files, and for whatever reason you decide to check out today's version. Surely you wouldn't want all those dozens of files to just sit around and clutter it up! They don't belong there! (And of course "untracked files...added with last commit" doesn't make any sense - if you committed them, they're now tracked.)
If the files really should have been in the old commit, likely what you want to do is use an interactive rebase (git rebase -i <start commit> <branch>
, further instructions provided by git)to squash a couple commits together. Perhaps you'll need to reorder the commits too, pushing the "add these files" commit back farther in the history where it belongs. Or, if you notice this right after you forget to add the files, simply add them and use commit --amend
to amend to the previous commit instead of creating a separate one.
Finally, if you really do get this set in the history this way (others have pulled so you don't want to rebase/amend), you can check out the old commit, and then check out the files from the newer commit:
git checkout <old-commit>
git checkout <new-commit> file1 file2 dir1 dir2/file3 ...
Solution 2
Just checkout HEAD again and you get those files back. Git checkout HEAD~2 reverts the directory of your repository back to the tracked status you had two commits ago. This is totally expected behaviour.
Comments
-
Rob almost 2 years
I add new files (which were present before as untracked files) and commit them. When I checkout to before this commit, these files are deleted. They should not.
It does not matter whether .gitignore lists these files or not (which requires to do git add -f ...).
-
Mark Peters over 13 yearsAre you sure you're the authority to suggest what should happen? Seems perfectly logical to me; those files don't exist at the point of your checkout.
-
-
Andrew Aylett over 13 years+1: Git has no idea whether you added those newly-tracked objects just before the checkin where you committed them (usual, should therefore delete them) or whether you had them lying around in your repository before-hand (decidedly unusual, why didn't you add them earlier?).
-
Rob over 13 yearsWhy didn't I add them earlier? Because I'm a human..., and this hinders me in rebuilding former commits.
-
halfdan over 13 yearsCheck git status before commiting and check if all required files are committed / tracked.
-
Rob over 13 yearsOf course. When unaccidentally untracked files remain untracked, it is no problem to revisit an older commit. But I add them, and a revisit of an older commit shows that those files are deleted. I'd wish that checkout offered a flag to not delete files.
-
Rob over 13 yearsYour conclusions are right, because finally clean. Indeed I don't want to modify the history - only the last proposal applies. Only goal of this action: compare target program behaviour because of a bug. Your solution with two checkouts provides the same effect as my solution with diff/checkout/apply
-
Rob over 13 yearsTo be precise: technically it is quite well possible not to delete files when returning to a former commit as in this case: Imagine a big leap backwards. While the "machine" is internally working through history, on every border between "tracked" and untracked", it could decide to leave that file(s) in the state just beyond that border. To my opinion, it wouldn't mess up any working directory content, rather restore even more precisely any commit state.
-
Cascabel over 13 years@Rob: So you think that when you check out an old commit, you should end up with the union of the files existing from that commit through to the future? What if your code dynamically loads all available plugins, and there are plugins in the future version which won't work with the older version? What if there are two divergent branches which have the checked-out commit as an ancestor - how will the VCS know which branch's version to put in the tree? This is not at all a consistent idea. If you check out a state, the VCS must actually put things into that state.
-
Rob over 13 yearsJefromi, I'm not an expert, but when the git engine traverses each commit upon a checkout in history (condition: linear return to any parent level), I'd believe that perfect reversibility of each individual commit 'border' is possible, when a border is discovered with "file track" addition. Of course only, when a specific flag is used. If going in history before an "add" point, such files get unaltered state. This is a risk, because e.g. .config may very well have changed in untracked state as well. The user should be aware of this, comparable as awareness about history hacking.
-
Rob over 13 yearsAmendment: while editing the above message, I accidentally removed a "real-world" note. One of the files I added during development was a kernel .config in a product specific branch. This should clarify the context.
-
Cascabel over 13 yearsSo you want the behavior of
git checkout <tree-ish>
to depend on the current HEAD. That's kind of scary - in my mind, it should only depend on the tree-ish you check out. And I understand what you're trying to do with the config files, I think, but asking for checkout to have different behavior isn't the solution. This is definitely a special case, and you have to handle it by manually checking out the needed files, or by doing temporary cherry-picks or merges. -
Rob over 13 yearsI needed some time to check out the history concept. As git points out to be (the only?) SCM that does not apply delta storage of commits, it needs no history traverse to checkout a certain commit. So, you win ;-) - thanks for your critical remarks.
-
Rob over 13 yearsExperiment: I have created a test commit on top of the tip, where .config is git removed. When I checkout a former commit where .config is not tracked, too (beyond the bunch of commits where it is tracked), this file remains untouched. This proves that git does not traverse history while doing checkouts.