ProbableOdyssey

How to un-commit files in git

There’s a few situations that can arise when you’d want to reverse committing a file to your repo:

However you’ve landed in this position, it’s luckily not too hard to get out of! But it requires going a bit beyond the basics of Git and understanding a bit more about how Git works.

Git’s tracking architecture optimizes for tracking changes between snapshots. When I learned about Git initially, I incorrectly assumed it was just like Dropbox or other versioned file storage systems, and it took a while to un-learn this.

Each commit is a snapshot of the repository’s state (referenced by a random SHA such as d85e8fe...), and Git internally optimizes storage by storing differences (deltas) between these states (represented as the added/removed lines across each file in the codebase). These diffs can be thought of as patches which can be applied to move from one commit to another, enabling rapid traversal of codebase versions and branches by following the tree of commits!

I made changes to my codebase, and I just want to go back to the HEAD commit

Save your uncommitted changes with git stash, so that you can apply them later with git stash pop. This reverts the codebase to the last commit, and most people usually learn this early on their Git journey.

But if I’ve already git add-ed some changes (staged my changes), this command will unstage all changes without deleting anything in my codebase:

1git reset HEAD

But if you’d prefer to delete changes made beyond the last commit:

1git reset --hard HEAD

I committed something I shouldn’t have a few commits ago, help!

Lets assume we’re working with a codebase with an output of git log --oneline -n 5:

d85e8fe (HEAD -> main) add even more code
14bfedd edit file I shouldn't have added
f18083a add code
b425f55 add file I shouldn't have added
974d26b (origin/main, origin/HEAD) add some code

I’ve made 5 commits before pushing them to Github, two of them (b425f55 and 14bfedd) touch a file I shouldn’t have added. I can list the commits that touch the file I want to remove using

1git log --oneline --follow -- <file_path>

Which gives me the output

14bfedd edit file I shouldn't have added
b425f55 add file I shouldn't have added

Lets edit the commits starting from the earliest commit in that result:

1git rebase -i b425f55^

This starts an interactive rebase (starting from the parent of the first bad commit), which opens up a text editor to the following:


pick d85e8fe add even more code
pick 14bfedd edit file I shouldn't have added
pick f18083a add code
pick b425f55 add file I shouldn't have added
# Rebase 974d26b..d85e8fe onto 974d26b (4 commands)
#
# Commands:
# ...

We’ll change pick to edit for the commits we’ve selected, then save and quit.

Our first stop is the first bad commit b425f55. At this stop we’ll

  1. Remove the bad file (and make the edits we need to this snapshot)
  2. Update the commit message with git commit --amend
  3. Continue the rebase with git rebase --continue

Our next stop will be the next bad commit 14bfedd. We’ll repeat the steps above

There may be some conflicts that need to be resolved. This will take a bit of time, but it’ll work out if you look through them slowly and methodically.

After the rebase is done, all the commits are back in order, and we can safely git push out code now! (If we’ve already pushed commits, we’ll need to do a git push --force to edit the history on Github, which is a destructive action).

Prevention is the best cure

The awkwardness of Git is a great incentive to do things the right way. Operations with Git that go outside the developers knowledge can be hazardous in some cases. I think it’s worth learning a little bit about what’s happening under the hood, but avoiding these situations is also a smart thing to do. There are a few pre-commit hooks that might be worth checking out:

Reply to this post by email ↪