A guide for astronauts (now, programmers using Git) about what to do when things go wrong.
Flight Rules are the hard-earned body of knowledge recorded in manuals that list, step-by-step, what to do if X occurs, and why. Essentially, they are extremely detailed, scenario-specific standard operating procedures. [...]
NASA has been capturing our missteps, disasters and solutions since the early 1960s, when Mercury-era ground teams first started gathering "lessons learned" into a compendium that now lists thousands of problematic situations, from engine failure to busted hatch handles to computer glitches, and their solutions.
— Chris Hadfield, An Astronaut's Guide to Life.
For clarity's sake all examples in this document use a customized bash prompt in order to indicate the current branch and whether or not there are staged changes. The branch is enclosed in parentheses, and a *
next to the branch name indicates staged changes.
Table of Contents generated with DocToc
- Editing Commits
- What did I just commit?
- I wrote the wrong thing in a commit message
- I committed with the wrong name and email configured
- I want to remove a file from the previous commit
- I want to delete or remove my last commit
- Delete/remove arbitrary commit
- I tried to push my amended commit to a remote, but I got an error message
- I accidentally did a hard reset, and I want my changes back
- Staging
- Unstaged Edits
- I want to move my unstaged edits to a new branch
- I want to move my unstaged edits to a different, existing branch
- I want to discard my local uncommitted changes (staged and unstaged)
- I want to discard specific unstaged changes
- I want to discard specific unstaged files
- I want to discard only my unstaged local changes
- I want to discard all of my untracked files
- Branches
- I want to list all branches
- Create a branch from a commit
- I pulled from/into the wrong branch
- I want to discard local commits so my branch is the same as one on the server
- I committed to master instead of a new branch
- I want to keep the whole file from another ref-ish
- I made several commits on a single branch that should be on different branches
- I want to delete local branches that were deleted upstream
- I accidentally deleted my branch
- I want to delete a branch
- I want to delete multiple branches
- I want to rename a branch
- I want to checkout to a remote branch that someone else is working on
- I want to create a new remote branch from current local one
- I want to set a remote branch as the upstream for a local branch
- I want to set my HEAD to track the default remote branch
- I made changes on the wrong branch
- Rebasing and Merging
- Stash
- Finding
- Submodules
- Miscellaneous Objects
- Tracking Files
- Configuration
- I've no idea what I did wrong
- Other Resources
Let's say that you just blindly committed changes with git commit -a
and you're not sure what the actual content of the commit you just made was. You can show the latest commit on your current HEAD with:
(master)$ git show
Or
$ git log -n1 -p
If you want to see a file at a specific commit, you can also do this (where <commitid>
is the commit you're interested in):
$ git show <commitid>:filename
If you wrote the wrong thing and the commit has not yet been pushed, you can do the following to change the commit message:
$ git commit --amend
This will open your default text editor, where you can edit the message. On the other hand, you can do this all in one command:
$ git commit --amend -m 'xxxxxxx'
If you have already pushed the message, you can amend the commit and force push, but this is not recommended.
If it's a single commit, amend it
$ git commit --amend --no-edit --author "New Authorname <authoremail@mydomain.com>"
An alternative is to correctly configure your author settings in git config --global author.(name|email)
and then use
$ git commit --amend --reset-author --no-edit
If you need to change all of history, see the man page for git filter-branch
.
In order to remove changes for a file from the previous commit, do the following:
$ git checkout HEAD^ myfile
$ git add myfile
$ git commit --amend --no-edit
In case the file was newly added to the commit and you want to remove it (from Git alone), do:
$ git rm --cached myfile
$ git commit --amend --no-edit
This is particularly useful when you have an open patch and you have committed an unnecessary file, and need to force push to update the patch on a remote. The --no-edit
option is used to keep the existing commit message.
If you need to delete pushed commits, you can use the following. However, it will irreversibly change your history, and mess up the history of anyone else who had already pulled from the repository. In short, if you're not sure, you should never do this, ever.
$ git reset HEAD^ --hard
$ git push --force-with-lease [remote] [branch]
If you haven't pushed, to reset Git to the state it was in before you made your last commit (while keeping your staged changes):
(my-branch*)$ git reset --soft HEAD@{1}
This only works if you haven't pushed. If you have pushed, the only truly safe thing to do is git revert SHAofBadCommit
. That will create a new commit that undoes all the previous commit's changes. Or, if the branch you pushed to is rebase-safe (ie. other devs aren't expected to pull from it), you can just use git push --force-with-lease
. For more, see the above section.
The same warning applies as above. Never do this if possible.
$ git rebase --onto SHA1_OF_BAD_COMMIT^ SHA1_OF_BAD_COMMIT
$ git push --force-with-lease [remote] [branch]
Or do an interactive rebase and remove the line(s) corresponding to commit(s) you want to see removed.
To https://github.com/yourusername/repo.git
! [rejected] mybranch -> mybranch (non-fast-forward)
error: failed to push some refs to 'https://github.com/tanay1337/webmaker.org.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Note that, as with rebasing (see below), amending replaces the old commit with a new one, so you must force push (--force-with-lease
) your changes if you have already pushed the pre-amended commit to your remote. Be careful when you do this – always make sure you specify a branch!
(my-branch)$ git push origin mybranch --force-with-lease
In general, avoid force pushing. It is best to create and push a new commit rather than force-pushing the amended commit as it will cause conflicts in the source history for any other developer who has interacted with the branch in question or any child branches. --force-with-lease
will still fail, if someone else was also working on the same branch as you, and your push would overwrite those changes.
If you are absolutely sure that nobody is working on the same branch or you want to update the tip of the branch unconditionally, you can use --force
(-f
), but this should be avoided in general.
If you accidentally do git reset --hard
, you can normally still get your commit back, as git keeps a log of everything for a few days.
Note: This is only valid if your work is backed up, i.e., either committed or stashed. git reset --hard
will remove uncommitted modifications, so use it with caution. (A safer option is git reset --keep
.)
(master)$ git reflog
You'll see a list of your past commits, and a commit for the reset. Choose the SHA of the commit you want to return to, and reset again:
(master)$ git reset --hard SHA1234
And you should be good to go.
(my-branch*)$ git commit --amend
Normally, if you want to stage part of a file, you run this:
$ git add --patch filename.x
-p
will work for short. This will open interactive mode. You would be able to use the s
option to split the commit - however, if the file is new, you will not have this option. To add a new file, do this:
$ git add -N filename.x
Then, you will need to use the e
option to manually choose which lines to add. Running git diff --cached
or
git diff --staged
will show you which lines you have staged compared to which are still saved locally.
git add
will add the entire file to a commit. git add -p
will allow to interactively select which changes you want to add.
This is tricky. The best I figure is that you should stash your unstaged edits. Then, reset. After that, pop your stashed edits back, and add them.
$ git stash -k
$ git reset --hard
$ git stash pop
$ git add -A
$ git checkout -b my-branch
$ git stash
$ git checkout my-branch
$ git stash pop
If you want to discard all your local staged and unstaged changes, you can do this:
(my-branch)$ git reset --hard
# or
(master)$ git checkout -f
This will unstage all files you might have staged with git add
:
$ git reset
This will revert all local uncommitted changes (should be executed in repo root):
$ git checkout .
You can also revert uncommitted changes to a particular file or directory:
$ git checkout [some_dir|file.txt]
Yet another way to revert all uncommitted changes (longer to type, but works from any subdirectory):
$ git reset --hard HEAD
This will remove all local untracked files, so only files tracked by Git remain:
$ git clean -fd
-x
will also remove all ignored files.
When you want to get rid of some, but not all changes in your working copy.
Checkout undesired changes, keep good changes.
$ git checkout -p
# Answer y to all of the snippets you want to drop
Another strategy involves using stash
. Stash all the good changes, reset working copy, and reapply good changes.
$ git stash -p
# Select all of the snippets you want to save
$ git reset --hard
$ git stash pop
Alternatively, stash your undesired changes, and then drop stash.
$ git stash -p
# Select all of the snippets you don't want to save
$ git stash drop
When you want to get rid of one specific file in your working copy.
$ git checkout myFile
Alternatively, to discard multiple files in your working copy, list them all.
$ git checkout myFirstFile mySecondFile
When you want to get rid of all of your unstaged local uncommitted changes
$ git checkout .
When you want to get rid of all of your untracked files
$ git clean -f
List local branches
$ git branch
List remote branches
$ git branch -r
List all branches (both local and remote)
$ git branch -a
$ git checkout -b <branch> <SHA1_OF_COMMIT>
This is another chance to use git reflog
to see where your HEAD pointed before the bad pull.
(master)$ git reflog
ab7555f HEAD@{0}: pull origin wrong-branch: Fast-forward
c5bc55a HEAD@{1}: checkout: checkout message goes here
Simply reset your branch back to the desired commit:
$ git reset --hard c5bc55a
Done.
Confirm that you haven't pushed your changes to the server.
git status
should show how many commits you are ahead of origin:
(my-branch)$ git status
# On branch my-branch
# Your branch is ahead of 'origin/my-branch' by 2 commits.
# (use "git push" to publish your local commits)
#
One way of resetting to match origin (to have the same as what is on the remote) is to do this:
(master)$ git reset --hard origin/my-branch
Create the new branch while remaining on master:
(master)$ git branch my-branch
Reset the branch master to the previous commit:
(master)$ git reset --hard HEAD^
HEAD^
is short for HEAD^1
. This stands for the first parent of HEAD
, similarly HEAD^2
stands for the second parent of the commit (merges can have 2 parents).
Note that HEAD^2
is not the same as HEAD~2
(see this link for more information).
Alternatively, if you don't want to use HEAD^
, find out what the commit hash you want to set your master branch to (git log
should do the trick). Then reset to that hash. git push
will make sure that this change is reflected on your remote.
For example, if the hash of the commit that your master branch is supposed to be at is a13b85e
:
(master)$ git reset --hard a13b85e
HEAD is now at a13b85e
Checkout the new branch to continue working:
(master)$ git checkout my-branch
Say you have a working spike (see note), with hundreds of changes. Everything is working. Now, you commit into another branch to save that work:
(solution)$ git add -A && git commit -m "Adding all changes from this spike into one big commit."
When you want to put it into a branch (maybe feature, maybe develop
), you're interested in keeping whole files. You want to split your big commit into smaller ones.
Say you have:
- branch
solution
, with the solution to your spike. One ahead ofdevelop
. - branch
develop
, where you want to add your changes.
You can solve it bringing the contents to your branch:
(develop)$ git checkout solution -- file1.txt
This will get the contents of that file in branch solution
to your branch develop
:
# On branch develop
# Your branch is up-to-date with 'origin/develop'.
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: file1.txt
Then, commit as usual.
Note: Spike solutions are made to analyze or solve the problem. These solutions are used for estimation and discarded once everyone gets clear visualization of the problem. ~ Wikipedia.
Say you are on your master branch. Running git log
, you see you have made two commits:
(master)$ git log
commit e3851e817c451cc36f2e6f3049db528415e3c114
Author: Alex Lee <alexlee@example.com>
Date: Tue Jul 22 15:39:27 2014 -0400
Bug #21 - Added CSRF protection
commit 5ea51731d150f7ddc4a365437931cd8be3bf3131
Author: Alex Lee <alexlee@example.com>
Date: Tue Jul 22 15:39:12 2014 -0400
Bug #14 - Fixed spacing on title
commit a13b85e984171c6e2a1729bb061994525f626d14
Author: Aki Rose <akirose@example.com>
Date: Tue Jul 21 01:12:48 2014 -0400
First commit
Let's take note of our commit hashes for each bug (e3851e8
for #21, 5ea5173
for #14).
First, let's reset our master branch to the correct commit (a13b85e
):
(master)$ git reset --hard a13b85e
HEAD is now at a13b85e