Fixing the past – 17 common Git problems with solutions

This article presents 17 common Git problems that can happen when working in Git, with recipes for how to recover from them.

It is very common to accidentally make mistakes when working with Git. Maybe you created commits in the wrong branch, or your commit history has become a mess. Fortunately, almost everything can be fixed afterwards. Almost all these fixes can be done with the commands git reset and git rebase --interactive presented in my Git guide, although sometimes there are better alternatives.

Let’s look at common Git Problems: real-life scenarios where things went wrong when using Git, and how to correct them.

Git (sometimes) shows a weird error message when I check out another branch or tag

Let’s see what happens when you use “git checkout <destination-reference>” to check out an existing branch, tag, or commit. As the Working directory vs. staging area vs. commits section of my Git guide indicates, the git checkout command transfers data from a commit to your working directory and index, overwriting them. Let’s assume that you already had <source-reference> checked out, before running the git checkout <destination-reference> command. According to the docsgit checkout makes it so that the index and working directory exactly match whatever the <destination-reference> says.

However, if you have unsaved changes in the index and/or working directory, things get more complicated. Sometimes Git can “save” these unsaved changes you made, and let these changes “survive” the checkout. This is the case if

  • the file(s) you modified have equal content in <source-reference> and <destination-reference>
  • you created a new file at a path that is not used (yet) in <destination-reference>

In all other cases, git checkout won’t do anything and abort with an error, to avoid that you lose data (in either your working directory or index). The error message asks you to save these changes by other means (e.g. stashing them), or to discard (rollback) the changes.

Git (sometimes) asks me whether I want to merge or rebase remote changes when I run “git push”

Tools such as IntelliJ IDEs sometimes show a dialog like this:

IntelliJ git push rejected

This happens in situations like these:

remote branch is ahead

Pushing your local commits (here: C4+C5) fails, because other users already pushed some other commits to the same branch (here: C2+C3). That is, the local and remote version of the branch have diverged. Git lets you fix this, by first pulling from the remote, handling the divergences, and then pushing your updated branch again. During the pull, Git can handle the divergences by either merging or rebasing, as shown in the following image:

git merge or rebase remote changes

I would generally recommend you use rebasing, which rebases your local (not yet pushed) commits on top of the (updated) remote state of the branch (here: rebase C4/C5 on top of C3). Why? Because rebasing gives you a much cleaner commit history, compared to merging. Rebasing is a “safe” operation in this scenario, because only your local (not-yet-pushed) commits are affected.

Configuring default behavior for rebase vs. merge

Many tools will not ask you how to handle the divergences (including VS Code or the Git CLI). They typically use merging as default. I recommend you consider changing the default to rebasing as follows:

  • VS Code: the setting is called “Git: Rebase When Sync” (checkbox, disabled by default)
  • Git CLI: run this command (once) to update your global Git configuration:
    git config branch.autosetuprebase always

I staged files I did not want to stage

Reverting a change only in your staging area (index) is easy if you use a GUI (e.g. in VS Code or IntelliJ-based IDEs): simply right-click the files in the staging area and unstage them.

With the Git CLI:

  • Run git reset <relative path> to remove only specific path(s) from the index
  • Run git reset (which is equal to git reset --mixed) to clean the entire index.

See the Git reset section of my Git guide for a detailed explanation of the git reset command.

I only want to commit files partially (e.g. specific lines, not all changes)

The approach depends on your used tool:

  • If you use the Git CLI, use git add -p but note that the interface is text-based and quite horrible. See here for pointers. Only recommended for the die-hard 1980-era terminal purist.
  • In VS Code, the feature is called “Stage selected ranges” – see here for pointers.
  • In IntelliJ-based IDEs, the commit view lets you commit parts of a file (see here), and you can even commit specific individual lines, see here.

I need to delete the N most recent commits

Let’s say you find that the work done in your most recent commits is obsolete, and you want to completely delete them (incurs “data loss”).

Make sure that the branch in question is already checked out, and that you don’t have any other unsaved work in your index or working directory (if you do, stash it first).

There are several ways to do delete the N most recent commits:

  • git reset --hard HEAD~N (replace N with the number of commits you want to delete).
  • git rebase --interactive HEAD~N (replace N with the number of commits you want to delete). In the dialog that is shown, replace pick with drop for all commits you want to drop.

This affects only your local commits!

I want to squash the N most recent commits

Sometimes you want to combine/squash several already-existing commits to one, e.g. because there is no benefit in keeping them all in the (long-term) Git commit history.

Make sure that the branch in question is already checked out, and that you don’t have any unsaved work in your index or working directory (if you do, stash it first).

There are several ways to squash these commits:

  • git reset --soft HEAD~N followed by git commit -m "squash commit message" uses the git reset approach. See the Git reset section of my Git guide to learn why and how git reset works.
  • git rebase --interactive HEAD~<N+1> (e.g. HEAD~3 if you want to squash the 2 most recent commits). In the dialog that is shown, replace pick with squash for all commits you want to squash.

There is yet another approach, in case you want to merge a topic branch and you want to squash all commits of that topic branch while merging:

  • Check out the target branch (the one into which you want to merge the topic branch)
  • git merge --squash topic-branch-name (note: the output of that command indicates that you still need to make the actual commit – the squashed changes have only been put into the index)
  • git commit (opens an editor window with a proposal for a commit message that you can edit), or run
    git commit -m "squash commit message"

There is no squash command

Fun fact: Git does not have a command that squashes two commits. I used to believe this, because many IDEs have a “squash command” in the context-menu that is shown when right-clicking two selected commits. Under the hood, IDEs use either git rebase or git reset, as presented above.

I want to squash some older commits (not the most recent ones)

Sometimes you discover that your most recent commit(s) are fine, but you would like to clean up some of the older commits.

Run git rebase --interactive <ref> and replace <ref> with the hash of the commit that comes right before the oldest commit into which you want to squash other commits. In the editor window that opens, the commits are sorted in the opposite order you would expect: the oldest commits are shown at the top, the newest ones at the bottom. Replace pick with squash for all those commits you want to squash, then save the file and close the editor window.

See the Interactive Rebase section of my Git guide to learn the background of how interactive rebasing works.

I want to undo a commit

When undoing a commit, you have two options:

  • Do you want to rewrite history and completely delete the commit, so that other team members won’t ever know it existed? If so, see the above section “I need to delete the N most recent commits”.
  • Do you want to keep the “faulty” commit and create another “reverse” commit that undoes the changes? Then run
    git revert <faulty-commit-hash> to create this reverse-commit. There are two common reasons to do so:
    1. You want transparency, e.g. to demonstrate to your team that the faulty commit needed to be reverted. For instance, maybe a feature could not be implemented as originally thought, or it introduced bugs.
    2. You already pushed the faulty commits, other team members already pulled these changes, and it is likely that they already created their own commits on top of it (which might not have been pushed yet).

Note: git revert <fauly-commit-hash> creates a commit that reverts all files of the faulty commit. If you only want to revert the changes of a specific file of that faulty commit, run
git checkout <ancestor-commit-hash> -- path/to/file
instead, where <ancestor-commit-hash> is the hash of the commit that comes right before the faulty commit. Then, git add that file and create a new reverse commit yourself.

I want to change the content and/or message of the most recent commit

There are many roads to Rome:

  • Stage the missing changes (in case you want to change the commit), then run one of the following commands:
    • git commit --amend -m "some message" to update the commit message (and change its content, if you staged any changes)
    • git commit --amend --no-edit to only update the content of the commit (and re-use its already-present commit message)
  • Create a new commit, then squash the two most recent commits, as explained above in section “I want to squash the N most recent commits”
  • Run git reset --soft HEAD~1 which deletes the most recent commit, and stages its changes. Then update the index (if you want to change the commit content), then create a new commit. See the Git reset section of my Git guide to learn more about git reset.

Variants 1 and 2 keep the meta-data of the existing commit (such as author name/email or creation timestamp), while variant 3 does not.

I want to change the content or message of an older commit (not the most recent one)

Use git rebase --interactive <ref> where <ref> is the hash of a commit that comes before the commit you want to change. In the editor dialog that shows up, replace pick with edit (or reword) for the commit you want to change, save the editor’s content and close the editor. If you chose edit, after closing the editor dialog, follow the instructions that Git printed to the terminal (git commit --amend followed by git rebase --continue).

Note: unfortunately, it is not possible to edit the very first commit of your repository.

I want to discard changes I made to a specific file in the working directory

To discard the changes and replace a file at some/path/to/file (relative to your repository’s root), run git checkout some/path/to/file which replaces the file’s content with the content the file has in the checked-out commit.

I want to move the N most recent not-yet-pushed commits to a different branch

It is a very common oversight to start working on something, make a series of commits, and then realize that you have been working in the wrong branch the whole time. You will most likely discover this oversight when you are about to push, which is the perfect point of time to fix this mistake.

This mistake can easily be solved as follows:

  1. “Copy” the commits to the correct branch. How you do this depends on whether the correct branch already exist:
    • If not, simply create it, via git branch <branch-name>
    • If yes, cherry-pick the commits that are on the wrong branch into the correct branch. I explained cherry-picking in the Git guide.
  2. “Remove” the commits from the wrong branch: you do so by hard-resetting the branch pointer of the wrong branch so that it points to an earlier commit: the one that comes right before your first incorrect commit. For instance, if you accidentally made 3 commits to the wrong main branch, run git reset --hard HEAD~3 (while the main branch is currently checked out)

I want to fix a spelling mistake in a branch name

Branches cannot really be renamed! However, you can simply create a new branch from the old branch (git checkout -b <new-branch-name>), run git push (if applicable), then delete the old (local) branch (git branch -D <old-branch-name>). To also delete the corresponding remote branch, run “git push --delete <remote-name> <old-branch-name>“, e.g. “git push --delete origin great-feture

Note: there is a convenience function, “git branch -m <old-branch-name> <new-branch-name>“, which renames your local branches only. You still need to manually delete the incorrectly-spelled remote branch yourself.

I want to recover an accidentally-deleted local branch

Whenever you accidentally deleted a local branch (that has not been pushed yet), it is possible to recover it. As explained in Garbage collection section of my Git guide, commits that have become “orphaned” (because they are no longer reachable from any branch or tag) are not immediately deleted by Git. Using the git reflog command, you can determine their commit hash:

  1. Determine the hash of the most recent commit, by looking for the most recent line in the reflog where you switched to the (now-deleted) branch (e.g. “8bab258 HEAD@{6}: checkout: moving from main to foo” in my Git guide).
  2. From there, track any newer commits you made to that branch (e.g. “c18778e HEAD@{5}: commit: some message“) and then run git checkout <commit-hash> followed by git checkout -b <branch-name> to restore the local branch.

I want to reset everything locally so that it exactly matches the remote / server state

Sometimes things go wrong, and your local branch is in a weird state. You might no longer be sure whether your local branch really matches the corresponding remote branch.

As an alternative to nuking the entire working directory and cloning again, you can run the following commands:

git reset --hard origin/<branchname> resets your local commit history of that branch. It ensures that any local commits are deleted, but it leaves files and folders not tracked by Git in the working directory. To also get rid of these, run
git clean -d -n (which is only a dry run), or git clean -d -f (actually deletes the files) to remove changed files and directories in your working directory (-d enables removing dirs). Adding “-x” to the “git clean” command also removes files/directories ignored by the “.gitignore” file, which would otherwise be kept in the working directory.

I want to know who deleted a file

If some file seems to be missing, you want to determine which commit deleted it. Use git log --follow -- <filename> to determine it.

Git uses the wrong line separators when checking out a repository

The line separators in textual files are historically different between Microsoft Windows (which uses two consecutive characters, CR and LF) and UNIX-based operating systems (which use only LF). The default behavior of Git is to automatically convert line separators between your working directory and the index/commits: in the index or commits, line separators are stored in UNIX format, whereas the working directory uses the platform-specific line separation character.

This is problematic if there are files that must preserve a specific line ending, irrespective of the platform on which they are checked out. Examples include a “.ps1” PowerShell script that should always use CRLF, or a UNIX “.sh” bash script that should always use LF (or these scripts won’t work!). To avoid problems, put the following lines into your .gitattributes file:

* text=auto

*.sh text eol=lf

*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlfCode language: plaintext (plaintext)

This “hard-codes” our customized line separators so that they are no longer platform-native. Now, run
git add --renormalize .
to update the newline separators only in your index, potentially creating a new commit.

After committing and pushing these changes to an existing repository that other users have already checked out, you should know that Git won’t magically replace the newline separators in their working directory, just because there is a new .gitattributes file. You need to instruct others to either completely re-clone the project, or run these commands (on a clean checkout – otherwise, stash your changes first!):

git rm --cached -r .
git reset --hardCode language: Bash (bash)

See this excellent post for a lot of background information on normalizing line endings in Git.

Leave a Comment