28. Rebase

Goals

  • To use rebase instead of the merge command.

We have reverted the style branch to the point in history before the first merge. There are two commits that are in the main branch, but not in the style branch: the new README file and that conflicting change in the index.html file. This time, we will move these changes to the style branch using the rebase command rather than merge.

01 Rebase the style branch onto main

Run

git switch style
git rebase main
git status

Result

$ git switch style
Already on 'style'
$ git rebase main
Rebasing (1/3)
Rebasing (2/3)
Auto-merging hello.html
CONFLICT (content): Merge conflict in hello.html
error: could not apply 903eb1d... Included stylesheet into hello.html
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 903eb1d... Included stylesheet into hello.html
$ git status
interactive rebase in progress; onto 85c14e9
Last commands done (2 commands done):
   pick 555372e Added css stylesheet
   pick 903eb1d Included stylesheet into hello.html
Next command to do (1 remaining command):
   pick 0ee0113 Renamed hello.html; moved style.css
  (use "git rebase --edit-todo" to view and edit)
You are currently rebasing branch 'style' on '85c14e9'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to check out the original branch)

Unmerged paths:
  (use "git restore --staged <file>..." to unstage)
  (use "git add <file>..." to mark resolution)
	both modified:   hello.html

no changes added to commit (use "git add" and/or "git commit -a")

There's a conflict again! Note that the conflict is in hello.html, not in index.html as the last time. It is because rebase was in the process of applying the style changes on top of the main branch. The file hello.html hasn't been renamed in main yet, so it still has its old name.

When merging, we would have a "reverse" conflict. During the merge, the changes of the main branch are applied on top of the style branch. The style branch has the file renamed, so the conflict would be in index.html.

File: hello.html

<!-- Author: Alexander Shvets (alex@githowto.com) -->
<html>
  <head>
<<<<<<< HEAD
    <title>Hello World Page</title>
=======
    <link type="text/css" rel="stylesheet" media="all" href="style.css" />
>>>>>>> 903eb1d (Included stylesheet into hello.html)
  </head>
  <body>
    <h1>Hello, World!</h1>
    <p>Let's learn Git together.</p>
  </body>
</html>

02 Resolve the conflict

The conflict itself can be resolved in the same way we did before. First, we edit the hello.html file to meet our expectations.

File: hello.html

<!-- Author: Alexander Shvets (alex@githowto.com) -->
<html>
  <head>
    <title>Hello World Page</title>
    <link type="text/css" rel="stylesheet" media="all" href="style.css" />
  </head>
  <body>
    <h1>Hello, World!</h1>
    <p>Let's learn Git together.</p>
  </body>
</html>

But after that, we don't need to commit the changes. We can just add the file to the index and continue the rebase process. This is why I love rebase! It allows me to fix conflicts without creating a bunch of ugly merge conflicts.

For simplicity's sake, we can add all files using ., which stands for the path of the current directory. Git interprets this as "add all files in the current directory and its subdirectories".

Run

git add .
git rebase --continue

Here, most likely, Git will open the editor again, to let us change the commit message. We can leave the message as it is. Upon saving changes, Git will finish the rebase process, and we can proceed with the following commands:

Run

git status
git log --all --graph

Result

$ git add .
$ git rebase --continue
[detached HEAD 23149b5] Included stylesheet into hello.html
 1 file changed, 1 insertion(+)
Rebasing (3/3)

Successfully rebased and updated refs/heads/style.
$ git status
On branch style
nothing to commit, working tree clean
$ git log --all --graph
* 39a1e0f 2023-11-28 | Renamed hello.html; moved style.css (HEAD -> style) [Alexander Shvets]
* 23149b5 2023-11-28 | Included stylesheet into hello.html [Alexander Shvets]
* b9e6de1 2023-11-28 | Added css stylesheet [Alexander Shvets]
* 85c14e9 2023-11-28 | Added meta title (main) [Alexander Shvets]
* ee16740 2023-11-28 | Added README [Alexander Shvets]
* 9288a33 2023-11-28 | Added copyright statement with email [Alexander Shvets]
* b7614c1 2023-11-28 | Added HTML header (tag: v1) [Alexander Shvets]
* 46afaff 2023-11-28 | Added standard HTML page tags (tag: v1-beta) [Alexander Shvets]
* 78433de 2023-11-28 | Added h1 tag [Alexander Shvets]
* 5836970 2023-11-28 | Initial commit [Alexander Shvets]

03 Merging VS rebasing

The result of the rebase command looks much like that of the merge command. The style branch currently contains all its changes, plus all the changes of the main branch. The commit tree, however, is a bit different. The style branch commit tree has been rewritten to make the main branch a part of the commit history. This makes the chain of commits linear and more readable.

04 When to use the rebase command, and when the merge command?

Use the rebase command:

  • When you fetch changes from a remote repository and want to apply them to your local branch.
  • If you want to keep the commit history linear and easy to read.

Don't use the rebase command:

  • If the current branch is public and shared. Rewriting such branches will hinder the work of other team members.
  • When the exact commit branch history is important (because the rebase command rewrites the history of commits).

Given the above recommendations, I prefer to use rebase for short-term, local branches and the merge command for branches in the public repository.