There are a couple ways to approach this. In general, you can either try to rewrite history to completely undo the mistake, or you can add a "revert" to the end of the history, so that the history will include "commit that deletes some files", then "commit that puts the files back".
One thing to consider is that rewriting history after its been shared ('push'ed) has some costs. If you attempt to follow the advice from most other answers, when you try to push
you will get an error; this is git telling you that you're trying to rewrite history which could cause problems for other users. You still can do it, but should not take the decision lightly. Why so many people think it's ok to advise doing this, without discussing the problems it causes, is beyond me.
Another consideration is whether the commit containing the file deletes also contains changes you want to keep.
So let's start simple: if you want to fully reverse all of the changes in the commit, but are willing to keep the commit as part of your branch's history, then you simply do
git revert HEAD
This will create a new commit that takes the content back to how it looked before the most recent commit - so the deleted files should be back, assuming nothing new has been committed after the deletes. You can push this commit and everything will be back as it was, except that git log
will show the commit with the error and the following commit to undo it.
If other work has been committed after the deletes, this can be adapted. You just need to give the revert
command some valid name for the commit you want to undo. This could be a commit ID, or an expression like HEAD~2
(if there are 2 commits after the one to be undone), or under some conditions a reflog expression... there are many possibilities.
If you want to keep some changes from the commit, you could say
git revert -n HEAD
This is similar, but instead of automatically committing the "undo" changes, it just puts them in your index and working tree - in other words, as staged but uncommitted changes. You can then make further edits to the commit, such as using git checkout HEAD -- path/to/a/file/with/changes/you/want/to/keep
.
Throughout this process, you can use git status
to determine what changes are staged, remembering that because this is a revert any change you want to keep from the reverted commit is one you want to not see in the final status
.
If working "backwards" like that is confusing, or if it's just easier to identify the paths of the deleted files, then you could skip the revert command and do the "undo" work manually. For each deleted path
git checkout HEAD^ -- path/to/deleted/file
If you need to be reminded what files were deleted, you can use
git show --name-status HEAD
which will show each deleted path on a line that starts with a D
.
Now, if for some reason you find it worth the extra trouble, it is possible to revise history instead. You might reason that the history looks "cleaner" without accidental commits - but frankly, that's a vanity that costs a lot of people unnecessary effort. Most of the reasons that really justify removing a commit after pushing it (commit contained passwords, commit contained huge files, etc.) require a more thorough procedure than just what I'm going to spell out here; this is just the minimal procedure for "cleaning up" the look of the history, and you'll see it's still a pain in the neck.
So the problem you'll find if you try something like
git reset --hard HEAD^
git push
is that git will reject the push, because your branch would move in a non-fast-forward way. You could force the push using
git push -f
Using push -f
is your warning that you've put other developers in a bad spot; now their repos are making bad assumptions about the condition of the branch, and they're going to have to recover. See "recovering from upstream rebase" in the git rebase
documentation; the guidance there applies to any history rewrite that you push.
In short, other developers will have to forcefully update their local refs to reflect the force push. (If they instead force-push their work, they will undo your fix; so you really do need to coordinate with everyone.) Any work they've already done based on the errant commit will have to be rebased.
If there are few other developers, or they don't work on that branch much; and communication among everyone is good; then it can be made to work. But then again, so can a much simpler revert
.