Git Mistakes and FAQs


Scope

This document outlines common mistakes and questions that can come up while using Git.

Git Mistakes and FAQs Overview

Git can be challenging to learn and mistakes often happen or questions come up for a developer at any level. Figuring out how to fix them however, becomes almost impossible without knowing what to look for in the first place.

Here are a few fairly frequent scenarios and how to fix them.

Undoing in Git

Many of the scenarios outlined below have to do with a user wanting to undo something they have already done in Git.

A few concepts are important to many of the answers below: HEAD, the working tree, and the index.

HEAD (which can be abbreviated @) is a name for the tip of the current branch (i.e. the last commit made on this branch).

The working tree refers to repository's files in the state that they are on your file system. If you open up a file in your editor and change it, you're changing the working tree's copy.

The index is a staging area for what will be committed. When you make changes to files in the repo, your working tree and the index differ. They are brought in sync with git add.

For more details and different wording, see click here.

I need to change my last commit message

git commit --amend

amend will modify the most recent commit. Will not work if the changes have been pushed.

I already committed and need to make additional changes.

This happens way too often. I make a commit before realizing I forgot to include a minor tweak. Instead of committing again, I can do

git add <file>   
git commit --amend

Reversing the last commit

There are two ways to do this:

git reset HEAD~ --soft

This will undo the last commit on Git, but leave the file changes intact.

git reset HEAD~ --hard

This will undo the last commit and reset any changed files to before the commit

Renaming my branch

For when you accidentally misname your branch. Switch to the branch via git checkout <branch> and run

git branch -m <new_name>

Removing Unintentionally Added Files

I accidentally added a file I don't want to commit

For when you accidentally added a new file with git add but have not yet committed, and ou want to undo the git add.

git rm --cached FILE

This removes FILE from the index. The --cached tells Git to operate only on the index and leave files in the working tree alone.

I added and committed a file (but did not push) accidentally and want to get rid of the last new commit.

The fix suggested below will only work if you have not already pushed your changes to the remote repository.

git reset --keep @~

Tacking ~ onto @ is a way to refer to the current commit's parent (or, actually, "first parent", since a commit can have more than one). Passing @~ to git reset --keep moves the tip of the branch to the parent of the current commit. It also sets working tree and index to the state recorded in the parent of the current commit, but keeps any uncommitted changes.

See git reset Documentation and git revisions Documentation for more details.

Removing a specific file from my last commit (did not push)

The fix suggested below will only work if you have not already pushed your changes to the remote repository.

In this scenario, you unintentionally included a file along with the other changes you wanted. You are looking to remove the one file from the commit, while keeping the other parts

git reset @~ -- FILE
git commit --amend --no-edit

Unlike the previous reset, this doesn't move the tip of the current branch. Instead it makes the index entry for FILE match what is in @~ (the parent of the current commit). Committing with --amend replaces the current commit with an updated one that also includes the staged changes.

See git reset Documentation and git commit Documentation for more details.

Removing a specific file from a few commits prior (did not push)

# find the commit you want to remove (e.g., with `git log`)
$ git rebase -i COMMIT~
# An editor will pop up.  Delete the line that begins with COMMIT.
# Save and exit.

The above starts an interactive rebase. Note that ~ after COMMIT, which says to use the commit's parent as the starting point for the rebase, operating on all of its children up to the tip of the current branch.

Note that, if the rebase fails for some reason, you can abort and return to the previous state with git rebase --abort.

See git rebase Documentation for more details.

I accidentally added a file and pushed to a branch others may be working on

You can do any of the steps above, but 1) others may have already pulled in your changes and 2) it will rewrite history, leading to potential confusion.

At this point, it's worth discussing the next step with others. If having the file in your repo's history isn't problematic (e.g., it's not sensitive information or not so large that it makes the repo hard to work with), then it makes sense to just make a new commit that removes the file. If you do need to rewrite the branch, you can push it to the remote with git push --force-with-lease ....

Discarding Changes to Tracked Files

I made some changes to a file I no longer want to keep

To remove changes you made to a file that you no longer want to keep, execute:

git checkout @ -- FILE

This overwrites FILE in the working tree and index with the file's state in the tip of the current branch (HEAD or @)

See git checkout Documentation for more details.

I made changes to multiple tracked files I no longer want to keep

git reset --hard

This resets the working tree and index to the tip of the current branch.

See git reset Documentation for more details.

I have made some changes I likely want to keep, but need to pause and work on something else. How do I save those changes and get to a clean state?

git stash push -u -m "some message"

This will create a snapshot with all your changes. You can drop the -u if you don't want to include untracked files in the stash. To see previous stashes, run git stash list. Apply a stash's changes with git stash pop N, where N is taken from the list output (e.g., stash@{0}).

An alternative to creating a stash would be to put your changes into a dedicated branch.

See git stash Documentation for more details.

Bringing in Changes from Remote

I tried to push my changes, but they were rejected as a "non-fast-forward"

git fetch REMOTE
# See the commits that are in the remote branch but not yours.
git log ..REMOTE/BRANCH
# Assuming nothing looks strange there, merge those changes into your
# own branch
git merge REMOTE/BRANCH
# Then push.
git push REMOTE BRANCH:BRANCH

This means that the branch on the remote end has advanced, and you don't have the new commits. Try to fetch and bring in changes from the remote (likely named "origin"). Inspect those changes to make nothing looks off. The merge them into your current branch. Now you should be able to push.

I tried to merge in the remote branch, but I have conflicts

# See which files have a conflict.
git status
git diff
# Open those files with an editor (e.g, in Rstudio) and search for the
# start of the conflict markers ("<<<<<<<").  Edit each spot to remove
# the markers and resolve the conflict with the text you think should be there.
git add <all files with conflicts>
git merge --continue

If your commits touch the same code as the changes you're trying to merge in, you need to tell Git which to take. After inspecting the conflicts with git status and git diff, edit the files to replace the marked text with the resolution. After that, you can add the files and complete the merge.

If you want to stop the merge and regroup, call git merge --abort.

See git merge Documentation for more details on resolving conflicts.

By default, Git shows only the two conflicting endpoints but not what the text looked like before those changes (i.e. their common starting point). You can configure Git to show the original text with $ git config --global merge.conflictstyle diff3.

I tried to merge in the remote branch, but I have a conflict due to a removed file

# Resolve the conflict by either keeping the file...
git add FILE
# ... or removing the file.
git rm FILE
# Then finish the merge.
git merge --continue

If a file is deleted on one side and modified on the other, git statusreports unmerged paths that are "deleted by us" or "deleted by them". There are no within-file conflicts to resolve in this case. The choice between the two options is made by using either git add and git rm.

I tried to merge in the remote branch, but I have a conflict due to a binary file

# Keep the file from the current branch with...
git checkout --ours -- FILE
# ... or the file from the branch being merged in with
git checkout --theirs -- FILE
# Then add the file and finish the merge.
git add FILE
git merge --continue

If both sides of a merge change a file that Git considers binary, there's no meaningful within-file conflicts to present. Which version of the file to use can be selected using the --ours (version from current branch being merged into) and --theirs (version from branch being merged in) options of git checkout.

If you're not sure of which file to take and you want to compare the versions? For example, say it's an .xlsx file, and you want to look at things in Excel before deciding. One option is to write those files to a temporary location so that you can inspect them however you'd like:

git show :2:foo.xlsx >/some/temp/location/ours.xlsx
git show :3:foo.xlsx >/some/temp/location/theirs.xlsx

See git checkout Documentation and git merge Documentation for more details.

Inspection

How do I show just the changes in a particular file when the diff for a change is too big/noisy?

git show COMMIT -- FILE

Many Git commands take an optional path that restricts their operation/output. This can be a specific file or directory. It can also be a pattern. For example, git show – '*.md' will show all changes in the last commit that touched files ending with .md. (Note the single quotes; those ensure that the pattern is interpreted by git show rather than expanded by the shell.)

See the pathspec entry in git glossary Documentation.

Why are no changes shown for a merge commit?

Merges can bring in a lot of changes, so by default commands like git log -p and git show will filter out "uninteresting" changes. To get a fuller view, pass -c (short for --diff-merges=combined) to override the default behavior (--cc). To get an even fuller view, pass -m (short for --diff-merges=on).

See the --diff-merges description in git show Documentation.

The line-based diff is too noisy and hard to review for prose changes. Can I see word-level changes?

Try passing --word-diff or --word-diff=color to any command that accepts diff options (such as git show, git log).

Performance

Why is git status so slow?

A likely reason is that Git is calculating the object ID(s) for a lot of data. Commands like git status try to avoid calculating object IDs and instead use cached stat information to decide if a file is modified, but there are times when Git doesn't yet have that information or doesn't trust it. This slow down should be a one-off. After an initial run, commands like git status should take advantage of the faster stat comparison to decide whether a file is modified.

One action that will often lead to git status needing to re-compute the object ID is switching to a branch where, compared to the starting branch, a large file has been added or modified.

Miscellaneous

git status tells me HEAD is detached at <abcd>. How do I fix it?

# See if you have commits in HEAD that are not in the branch you want
# to switch to.
git log BRANCH..
git checkout BRANCH
# If `git log` did show changes, then merge them in:
git merge HEAD@{1}

HEAD normally points to a branch. A detached head is when HEAD instead points directly to a commit. You can still create commits and advance the tip of HEAD, but those commits aren't part of a regular, named branch. Once you switch away from it, there's no convenient way to refer to it.

To get out of that state, you can check out the branch you want to be on. Before doing that, first make sure that that branch has all the commits leading up to the current head. If there are new commits in HEAD, merge those commits in after to switching to the branch. Appending @{1} to a ref name (HEAD in this case) is a way to refer to the last value of that ref (i.e. HEAD pointed to before you called git checkout BRANCH).

See git glossary Documentation, git reflog Documentation, and git revisions Documentation for more details.

I made too many mistakes

This command is a last resort for when everything has gone wrong.

git reflog

This will show you a list of all the commands you ran with Git. You can then travel back to any point by reseting on an index via

git reset HEAD~{<index>}

Resetting Everything

If all else fails or you're tired of trying different solutions, it might just be best to delete your repository and create it again via

git clone <url>