Destination: To be able to keep branches in a local repository synchronised with each other, as needed.
Motivation: While working on one branch, you might want to have access to changes introduced in another branch (e.g., take advantage of a bug fix introduced in another branch).
Lesson plan:
Lesson: Merging to Sync Branches covers that part.
Lesson: Rebasing to Sync Branches covers that part.
Lesson: Copying Specific Commits covers that part.
Merging is one way to keep one branch synchronise itself with changes introduced into another.
When working in parallel branches, you’ll often need to sync (short for synchronise) one branch with changes made in another. For example, while developing a feature in one branch, you might want to bring in a recent bug fix from another branch that your branch doesn’t yet have.
The simplest way to sync branches is to merge — that is, to sync a branch b1
with changes from another branch b2
, you merge b2
into b1
. In fact, you can merge them periodically to keep one branch up to date with the other.
gitGraph %%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'master'}} }%% commit id: "m1" branch bug-fix branch feature commit id: "f1" checkout master checkout bug-fix commit id: "b1" checkout master merge bug-fix checkout feature merge master id: "mc1" commit id: "f2" checkout master commit id: "m2" checkout feature merge master id: "mc2" checkout master commit id: "m3" checkout feature commit id: "[feature] f3" checkout master commit id: "[HEAD → master] m4"
In the example above, you can see how the feature
branch is merging the master
branch periodically to keep itself in sync with the changes being introduced to the master
branch.
Rebasing is another way to synchronise one branch with another.
Rebasing is another way to synchronise one branch with another, while keeping the history cleaner and more linear. Instead of creating a merge commit to combine the branches, rebasing moves the entire sequence of commits from your branch and "replays" them on top of another branch. This effectively relocates the base of your branch to the tip of the other branch (i.e., 're-base', hence the name), as if you had started your work from there in the first place.
Rebasing is especially useful when you want to update your branch with the latest changes from a main branch, but you prefer an uncluttered history with fewer merge commits.
Suppose we have the following revision graph, and we want to sync the feature
branch with master
, so that changes in commit m2
become visible to the feature
branch.
gitGraph %%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'master'}} }%% commit id: "m1" branch feature checkout feature commit id: "f1" checkout master commit id: "[master] m2" checkout feature commit id: "[HEAD → feature] f2"
If we merge the master
branch to the feature
branch as given below, m2
become visible to feature
branch. However, it creates a merge commit.
gitGraph %%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'master'}} }%% commit id: "m1" branch feature checkout feature commit id: "f1" checkout master commit id: "[master] m2" checkout feature commit id: "f2" merge master id: "[HEAD → feature] mc1"
Instead of merging, if we rebased the feature
branch on the master
branch, we would get the following.
gitGraph %%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'master'}} }%% commit id: "m1" checkout master commit id: "[branch: master] m2" branch feature checkout feature commit id: "f1a" commit id: "[HEAD → feature] f2a"
Note how the rebasing changed the base of the feature
branch from m1
to m2
. As a result, changes done in m2
are now visible to the feature
branch. But there is no merge commit, and the revision graph is simpler.
Also note how the first commit in the feature branch, previously shown as f1
, is now shown as f1a
after the rebase. Although both commits contain the same changes, other details — such as the parent commit — are different, making them two distinct Git objects with different SHA values. Similarly, f2
and f2a
are also different. Thus, the history of the entire feature
branch has changed after the rebase.
Because rebasing rewrites the commit history of your branch, it's important to use it carefully. You should avoid rebasing branches that you’ve already shared with others, because rewriting published history can cause confusion and conflicts for anyone else working on the same branch.
force-pushing: If you modify past commits that have been pushed to a remote repository, you'll have to force-push the modified commit to the remote repo in order to update the commits in it.
Cherry-picking is a Git operation that copies over a specific commit from one branch to another.
Cherry-picking is another way to synchronise branches, by applying specific commits from one branch onto another.
Unlike merging or rebasing — which bring over all changes since the branches diverged — cherry-picking lets you choose individual commits and apply just those, one at a time, to your current branch. This is useful when you want to bring over a bug fix or a small feature from another branch without merging the entire branch history.
Because cherry-picking copies only the chosen commits, it creates new commits on your branch with the same changes but different SHA values.
Suppose we have the following revision graph, and we want to bring over the changes introduced in m3
(in the master
branch) onto the feature
branch.
gitGraph %%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'master'}} }%% commit id: "m1" branch feature checkout feature commit id: "f1" checkout master commit id: "m2" commit id: "m3" type: HIGHLIGHT commit id: "[master] m4" checkout feature commit id: "[HEAD → feature] f2"
After cherry-picking m3
onto the feature
branch, the revision graph should look like the following:
gitGraph %%{init: { 'theme': 'default', 'gitGraph': {'mainBranchName': 'master'}} }%% commit id: "m1" branch feature checkout feature commit id: "f1" checkout master commit id: "m2" commit id: "m3" type: HIGHLIGHT commit id: "[master] m4" checkout feature commit id: "f3" commit id: "[HEAD → feature] m3a" type: HIGHLIGHT
Note how it makes the changes done in m3
available (from now on) in the feature
branch, with minimal changes to the revison graph.
Also note that the new commit m3a
contains the same changes as m3
, but it will a different Git object with a different SHA value.
Cherry-picking is another Git operation that can result in conflicts i.e., if the changes in the cherry-picked commit conflicts with the changes in the receiving branch.
At this point: Now you can bring over changes in one branch to another, in a local repository.
What's next: Tour 8: Working with Remote Branches