views:

61

answers:

4

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 ...).

+1  A: 

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.

halfdan
+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?).
Andrew Aylett
Why didn't I add them earlier? Because I'm a human..., and this hinders me in rebuilding former commits.
Rob
Check git status before commiting and check if all required files are committed / tracked.
halfdan
Of 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
A: 

Possible solution when the last commit only contains the file additions:

$ git diff HEAD^ >diff
$ git checkout HEAD^
$ git apply diff

At this time the branch is detached and contains the formerly untracked files. From now on, further checkouts in commit history are possible, and consistent.

Rob
A: 

You might like git commit --amend to tack on stuff to the previous commit that you forgot.

Mark Peters
+1  A: 

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 ...
Jefromi
Your 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
To 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.
Rob
@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.
Jefromi
Jefromi, 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
Amendment: 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.
Rob
So 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.
Jefromi
I 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
Experiment: 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.
Rob