Best practices when working with Git branches

When doing work on feature branches, it’s important to incorporate production-ready commits from master branch as soon as possible.
The below walkthtroughs explain the approach.

Covered in this article:

  • Git rebasing a feature branch
  • Git rebasing a feature branch when theres’s a conflicting change
  • Interactive rebase
  • Deleting branches
  • Visualizing commits

Very useful scenario – grabbing master branch changes onto a feature branch, in order to minimize future conflicts

Before rebase, we are on master and we are showing both branches commits and how they deviate:

/repo3 (master)
$ git log --oneline --graph --all
* 456d3f6 (HEAD -> master) add line 2 to file1
| * 8f3655e (feature1) added feature1.txt on feature1 branch
|/
* bc63714 (origin/master) initial commit

Now we switch to feature1 (git checkout feature1) and then we want to rebase feature1 to incorporate the latest change (456d3f6) from master branch

/repo3 (feature1)
$ git rebase master -v
Changes from bc637142421d0ac523630471ad8b8a8252d3cfa6 to 456d3f6ce932efa0e3dff0fd5302bee4f321bbe2:
 file1.txt | 1 +
 1 file changed, 1 insertion(+)
First, rewinding head to replay your work on top of it...
Applying: added feature1.txt on feature1 branch

As a result, we proactively pulled master commits (could be a critical bug fix etc.) onto feature branch and can continue working on the feature

/repo3 (feature1)
$ git log --oneline --graph --all
* 60296e1 (HEAD -> feature1) added feature1.txt on feature1 branch
* 456d3f6 (master) add line 2 to file1
* bc63714 (origin/master) initial commit

But what if you edited the same file? Here’s a scenario where we are rebasing while there’s a conflict:

Initial graph:

/repo3 (master)
$ git log --oneline --graph --all
* 128629c (HEAD -> master) add line 3 to file1 on master
| * 8ff6948 (feature1) add line 3 to file1 on feature1
| * 60296e1 added feature1.txt on feature1 branch
|/
* 456d3f6 add line 2 to file1
* bc63714 (origin/master) initial commit

Let’s rebase… And we get a conflict in file1.txt

/repo3 (feature1)
$ git rebase master -v
Changes from 456d3f6ce932efa0e3dff0fd5302bee4f321bbe2 to 128629c74113cf23d27fcdaeb69b3afb9c9dc661:
 file1.txt | 1 +
 1 file changed, 1 insertion(+)
First, rewinding head to replay your work on top of it...
Applying: added feature1.txt on feature1 branch
Applying: add line 3 to file1 on feature1
Using index info to reconstruct a base tree...
M       file1.txt
Falling back to patching base and 3-way merge...
Auto-merging file1.txt
CONFLICT (content): Merge conflict in file1.txt
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch' to see the failed patch
Patch failed at 0002 add line 3 to file1 on feature1
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".

Git will start showing the “rebase in progress” marker, telling us it’s on step 2 of 2:

/repo3 (feature1|REBASE 2/2)

We edit the file as necessary for out project: “line 3 from feature1” becomes “line 4 from feature1” and travels to 4th line as we allow master’s change fisrt.

line1
line2
<<<<<<< HEAD
master's line 3
=======
line 3 from feature1
>>>>>>> add line 3 to file1 on feature1

New content:

line1
line2
master's line 3
line 4 from feature1

Add to staging

/repo3 (feature1|REBASE 2/2)
$ git add .
/repo3 (feature1|REBASE 2/2)
$ git status
rebase in progress; onto 128629c
You are currently rebasing branch 'feature1' on '128629c'.
  (all conflicts fixed: run "git rebase --continue")

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   file1.txt

And continue the rebase

/repo3 (feature1|REBASE 2/2)
$ git rebase --continue
Applying: add line 3 to file1 on feature1

NOW it looks like this:

/repo3 (feature1)
$ git log --oneline --graph --all
* 3f801c0 (HEAD -> feature1) add line 3 to file1 on feature1
* 68a7309 added feature1.txt on feature1 branch
* 128629c (master) add line 3 to file1 on master
* 456d3f6 add line 2 to file1
* bc63714 (origin/master) initial commit

BEFORE rebase – for comparison:

* 128629c (HEAD -> master) add line 3 to file1 on master
| * 8ff6948 (feature1) add line 3 to file1 on feature1
| * 60296e1 added feature1.txt on feature1 branch
|/
* 456d3f6 add line 2 to file1
* bc63714 (origin/master) initial commit

Now you can see that 128629c (master) commit have been replayed on your feature branch and it retained their SHA1 IDs, but the commits from the feature branch gained new SHA1 ids and have been placed after the 128629c base. We have rebased the feature branch from 456d3f6 to 128629c commit.


Couple of days later…

Let’s say now we are done with feature1 and want to merge it into master

/repo3 (master)
$ git merge feature1
Updating 128629c..3f801c0
Fast-forward
 feature1.txt | 1 +
 file1.txt    | 1 +
 2 files changed, 2 insertions(+)
 create mode 100644 feature1.txt

We get nice line of history with feature1 changes (3f801c0, 68a7309) appearing right after the master’s changes:

/repo3 (master)
$ git log --oneline --graph --all
* 3f801c0 (HEAD -> master, feature1) add line 3 to file1 on feature1
* 68a7309 added feature1.txt on feature1 branch
* 128629c add line 3 to file1 on master
* 456d3f6 add line 2 to file1
* bc63714 (origin/master) initial commit

We can delete feature1 now since we are done with it:

/repo3 (master)
$ git branch -d feature1
Deleted branch feature1 (was 3f801c0).

Before we continue with the next useful command, let’s add some commits. New commit history is below:

/repo3 (master)
$ git log --oneline --graph --all
* 4065bfa (HEAD -> master) commit A3
* 216ab6d commit A2
* e4db335 commit A1
* 3f801c0 add line 3 to file1 on feature1
* 68a7309 added feature1.txt on feature1 branch
* 128629c add line 3 to file1 on master
* 456d3f6 add line 2 to file1
* bc63714 (origin/master) initial commit

Now let’s say we want to reduce number of commits to reduce the history noise.

Using rebase to remove some commits after 3f801c0:

git rebase -i 3f801c0

Open editor where we specify that we want to squash A3, A2 into A1.
A1 would contain all the changes:

pick e4db335 commit A1
squash 216ab6d commit A2
squash 4065bfa commit A3
# Rebase 3f801c0..4065bfa onto 3f801c0 (3 commands)

Save and quit the editor

You get prompted for a new commit comment, let’s make it “commit B1” (B1=A1+A2+A3):

    # This is a combination of 3 commits.
    # This is the 1st commit message:

    commit A1

    # This is the commit message #2:

    commit A2

    # This is the commit message #3:

    commit A3

    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.

We delete all uncommented lines and type in only one line and save the file, then quit the editor:

commit B1
/repo3 (master)
$ git rebase -i 3f801c0
[detached HEAD a5fc896] commit B1
 Date: Fri Mar 13 19:54:38 2020 -0400
 1 file changed, 2 insertions(+)
Successfully rebased and updated refs/heads/master.
/repo3 (master)
$ git log --oneline --graph --all
* a5fc896 (HEAD -> master) commit B1
* 3f801c0 add line 3 to file1 on feature1
* 68a7309 added feature1.txt on feature1 branch
* 128629c add line 3 to file1 on master
* 456d3f6 add line 2 to file1
* bc63714 (origin/master) initial commit

We can see now that the old commits after 3f801c0 are gone. In other words, three commits A1, A2, A3 (e4db335, 216ab6d, 4065bfa) are gone, and there’s a single a5fc896 instead.