Using the Git Rebase Command
Traducciones al EspañolEstamos traduciendo nuestros guías y tutoriales al Español. Es posible que usted esté viendo una traducción generada automáticamente. Estamos trabajando con traductores profesionales para verificar las traducciones de nuestro sitio web. Este proyecto es un trabajo en curso.
What Does Git Rebase Do?
Rebasing takes a series of commits and reapplies them on top of another base commit. This method is a form of rewriting a branch’s commit history. Rebasing does not change the content of your commits, but it does change the commit hash that is used to track your changes. This is an important detail to remember, especially when working with collaboration branches or branches that have already been accessed by other team members.
Rebase vs. Merge
This section uses the scenario described below to explore the differences between Git’s rebase command versus Git’s merge command.
- You are working on a local feature branch named
my-feature-branch. - You forked off of the
maincollaboration branch. Themainbranch is the base of your localmy-feature-branch. - Other repository collaborators have made changes to the
maincollaboration branch. - You want to add your local
my-feature-branchcommits to theHEADof themainbranch.
Git Merge: A Git merge allows you to merge your feature branch with the main collaboration branch. This method creates a new commit that incorporates the changes from both branches. This is known as a merge commit. Merging your feature branch with the main branch can be accomplished with the following commands:
git checkout main
git pull
git checkout my-feature-branch
git merge main
When merging two branches together using a merge commit, your local branch’s commit history resembles the following:
A---B---M my-feature-branch
/ /
...D---E---F---G mainThe M represents the merge commit that ties together the two branches. Once you’ve merged the two branches, you can either keep working on your branch, or merge it back into main.
main is a direct ancestor of M. If you continue to work on your feature branch, you eventually have to make another merge with main. This makes it difficult for anyone reviewing your code to figure out exactly what changes you made.Git Rebase: A Git rebase takes the commits made in your local branch and places them on top of the latest commits pulled down from the main branch. This method updates your local feature branch with the latest changes pushed up to the collaboration branch by your teammates. Merging your feature branch with the main branch can be accomplished with the following commands:
git checkout main
git pull upstream main
git checkout my-feature-branch
git rebase main
After running the git rebase main command, your local branch’s commit history resembles the following:
A'---B'... my-feature-branch
/
...D---E---F---G mainB, in the rebase diagram and M, in the merge diagram, are both snapshots of approximately the same state. What’s different about the two commit histories is the information available in each. M and its history record what everyone did – it’s a historical record of what work was done on the project. B and its history, on the other hand, tell the story of how the project was made. Typically, your Git repository’s project leaders determine which method they prefer to use to combine changes between branches.
How Git Rebase Works
During a Git rebase, all the committed changes made in your working feature branch are saved in a temporary area. The saved commits are all the commits made since your initial fork from the base branch. When rebasing you are generally rebasing onto an updated version of the original base branch.
Then, the rebase does a hard reset to the head of the upstream branch in the local branch. This is effectively like running the git reset --hard <upstream> command. Next, the rebase applies the saved changes (stored in your commits) to the local branch. Any commits that introduce the same textual changes as a commit in the upstream branch are omitted.
The commits that are temporarily stored when rebasing are the same set of commits that are displayed when issuing the git log <upstream>..HEAD command.
These are also the same set of commits that you would get from the git log --patch --reverse <upstream>..HEAD command.
When to Use Git Rebase?
The primary reason to rebase is to keep a feature branch up to date with an upstream collaboration branch. Typically, this is done because you will eventually merge your feature branch with the upstream collaboration branch. Keeping your branch up to date with the collaboration branch achieves the following:
It eliminates the risk of merge conflicts when you finally merge two branches. When rebasing, the merge is always going to be fast-forwarded.
Rebasing often – ideally, every time someone pushes a commit – ensures that your work always incorporates the latest changes. Lots of small rebases make conflicts less likely, and much easier to resolve if they do happen.
If someone pushes another commit to the collaboration branch while you’re running your unit and integrations tests, rebase again and re-run your tests. Using the
--ff--onlyoption when you eventually merge guarantees that no changes have been pushed while you were doing your rebase.Rebasing keeps your project’s
mainbranch linear. A linear commit history makes it easier to understand each change, and makes bisecting to find a bug simpler.Finally, you can use an interactive rebase to rewrite commit messages, re-order commits to make the changes easier to understand, and squash out trivial commits that contain commit messages like “Commit everything before going home for the weekend”. A branch with a small number of commits is easier to understand and review.
How to Rebase Safely
Do Not Rebase Commits that Have Been Shared
Do not rebase a branch that someone else may have worked on. As long as the branch you are rebasing only exists locally on your computer, you should be able to rebase without negative results.
Fortunately, Git prevents you from pushing a rebased commit, unless you use the --force option. It is best to avoid using this option unless you are certain about what you are doing. It’s okay to force-push a rebased branch to a shared repository provided you’re the only person using that branch.
Create a Backup Branch
Aborting a rebase resets the branch’s commit history back to where it was prior to the rebase. However, if the rebase succeeds, but the result of the rebase is unexpected, you can perform a hard reset of the branch. This takes you back to the commit that was the head of your branch before you started. You can save a reference to that commit creating a backup branch:
git branch my-backup-branch
git rebase...
git reset --hard my-backup-branch
You can achieve the same thing with a tag. You can also use git reflog to find the commit if you forgot to make a backup branch.
Stash or Commit Your Changes
Git doesn’t prevent you from rebasing when you have uncommitted changes or untracked files. You should avoid rebasing when you have uncommitted changes. Either commit your changes or stash them. You can also stash local changes as a precaution when merging, performing a hard reset, and switching branches.
Use Git Fast Forward Only when you Merge a Rebased Branch
When you’re merging with a branch that you’ve rebased, ensure that you merge using the --ff-only option, as follows:
git checkout main
git pull
git merge --ff-only your-feature-branch
git push
With this option, the merge fails if any changes have been merged into the collaboration branch (main branch) since your last rebase. The push fails if any changes have been pushed between the git pull and the git push. If the push fails, use git pull --rebase to catch up. Remember to re-run your unit and integration tests afterward.
When to Pull Changes with a Rebase
There are two main cases when git pull --rebase is the right command to use:
Use this command when pulling a different branch, usually the one you’re eventually going to merge your feature branch with.
This method is a shortcut. Instead of checking out a branch (often the
mainbranch), pulling down the most recent changes, checking out your working branch, and rebasing, you cangit pull --rebase mainand have mostly the same effect. The only difference is that the first method updates themainbranch as well as the branch you’re working on.Use
git pull --rebasewhen someone else has made commits on the branch you’re currently working on.Pulling the
mainbranch from the branch you’re working on provides more information. A rejected non-fast-forward push means that someone has pushed changes to the collaboration branch.
Git’s Interactive Rebase
Rebasing interactively (with the -i or --interactive option) lets you modify the commits being rebased. You can rearrange them, delete them, squash them together, edit their messages, and modify files.
How Interactive Rebase Works
When starting an interactive rebase, identify the commit that you’re going to rebase the modified commits onto. Often, this is an earlier commit on your working branch; you can identify it by its hash or by tagging the commit. For example:
git log --oneline -n20 # identify the base commit
git tag interactive-rebase-base 969d539
git rebase -i interactive-rebase-base
This displays a list of the commits being rebased, starting with the oldest commit. Then, it opens your default text editor. The list of commits presented to you during an interactive rebase resembles the following:
pick 75ad226 ignore .odt files
pick 4c8cfec Push from sable Tue 07 Sep 2021 10:27:50 AM PDT
pick e06ed41 add email to $editor about merge tools
pick 6ec4a89 Move `examples/` to top level of WIP
pick d027142 Move examples repo to ../conflict-playground
pick abba541 Push from sable Fri 10 Sep 2021 04:55:08 PM PDT
pick a9f26f7 Push from sable Tue 14 Sep 2021 08:04:35 PM PDT
pick a70a5ea Push from sable Wed 15 Sep 2021 08:52:37 PM PDT
pick bf5ea02 Push from sable Thu 16 Sep 2021 04:05:16 PM PDT
pick 36619cd Start working on interactive rebase
pick e977228 slight wording fixup for clarity
pick f300b06 Add the screenshots for how-to-resolve-merge-conflicts
# Rebase 969d539..f300b06 onto f300b06 (12 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase is aborted.
#
# Note that empty commits are commented outThe comment block at the end of the file describes the commands you can put in place of pick to make modifications. You can replace the full command with its first letter.
In the example above, there are several commits that use repetitive commit messages. These commits should be squashed onto commits that have meaningful messages. You can do so using the pick command. In addition to pick, use reword, which stops to let you edit the commit message. The squash options combines the commit with the previous commit. The fixup options is like squash except that it discards the selected commit’s log message.
After deciding how to clean up your commit history using the available commands, the list might look as follows:
pick 75ad226 ignore .odt files
reword 4c8cfec Push from sable Tue 07 Sep 2021 10:27:50 AM PDT
pick f300b06 Add the screenshots for how-to-resolve-merge-conflicts
pick e06ed41 add email to $editor about merge tools
pick 6ec4a89 Move `examples/` to top level of WIP
squash d027142 Move examples repo to ../conflict-playground
pick abba541 Push from sable Fri 10 Sep 2021 04:55:08 PM PDT
fixup a9f26f7 Push from sable Tue 14 Sep 2021 08:04:35 PM PDT
fixup a70a5ea Push from sable Wed 15 Sep 2021 08:52:37 PM PDT
fixup bf5ea02 Push from sable Thu 16 Sep 2021 04:05:16 PM PDT
squash 36619cd Start working on interactive rebase
squash e977228 slight wording fixup for clarityGit stops three times to let you edit the commit message: once for the reword, and once for each set of consecutive squash and fixup commands. When you’re done, your commit history looks as follows:
b46df0d Continue working on interactive rebase
151496e Make conflict playground repo.
510ed1d add email to $editor about merge tools
bc70905 Add the screenshots for how-to-resolve-merge-conflicts
65c80d4 Finish resolving...; start working on rebase
75ad226 ignore .odt filesThis creates a much more readable log that is easier for repository maintainers to review and approve.
Additional Rebasing Commands and Options
In addition to squash, fixup, and reword, there are several other commands:
The
dropcommand removes the commit. You can get the same effect by deleting the line, but if you’re doing something complicateddropgives you the option of changing your mind later.The
editcommand keeps the commit, but stops to let you amend it – it’s likegit commit --amendexcept that the commit you’re amending doesn’t have to be the most recent commit. Among other things, you can use it to split a commit: usegit reset HEAD^to move back in history, then add and commit the different files separately.The
breakcommand interrupts the rebase before the marked commit rather than after it; you can also use it after anexecormergecommand, or to do something at the very start of the rebase. Usegit rebase --continueto process the rest of the list.The
execcommand accepts a shell command rather than a commit. Possible uses include running a code formatter likehtml_tidy, or changing the protections on a file.The
mergecommand adds a merge commit; it’s used in conjunction withlabelandresetwhen rebasing merges. See the section Rebasing Merges in the man page forgit rebase.
Troubleshooting
Solving Merge Conflicts when Running a Git Rebase
Merge conflicts can occur during a rebase operation. During a rebase a separate merge for each commit takes place. For this reason, it’s possible to encounter more than one conflict during the course of a rebase. The list below includes some options you can use when working through a merge conflict during a rebase:
Use
git add <filename>to mark the conflicts as resolved. Then, run thegit rebase --continuecommand to continue with the remaining patches for the rebase.Run the
git rebase --skipcommand to ignore the patch that caused the conflict.Use the
git rebase --abortcommand to end the rebase. Then, clean up any files that may be causing the merge conflict. When you’re done, reattempt the rebase with thegit rebase <branch>command.git rebase --quitis like –abort except that it leaves the tree and the index alone.git rebase --show-current-patchshows you the exact change that Git is trying to apply, in the form of a diff.
Use Reference Logs to Undo a Bad Rebase
Reference logs (reflogs), record when the tips of branches and other references are changed. The git reflog command is used to view the history of any reference. This command uses HEAD by default. If you want to get your branch back to where it was before your last rebase use:
git reset --hard HEAD@{1}
Follow this command with a git push --force-with-lease to restore a branch that you force-pushed by mistake. A best practice is to verify with the rest of your team that nobody pulled from the rebased branch while you were undoing your rebase.
Pushing Rebased Commits
The --force-with-lease option checks that you are not accidentally overwriting any commits made by someone else collaborating on your remote branch. This option protects all remote refs that are going to be updated by requiring their current value to match the remote tracking branch.
You can also specify a refname, which protects that ref alone, or a refname and expected value. The latter is useful if you don’t have a remote tracking branch for that ref. The full command is as follows:
git push --force-with-lease=<refname>:<expected-value>
This page was originally published on