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 status
reports 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>