Git Branch

Concepts

Terminology

  • Git objects
  • Branch
  • Merge
    • fast-forward merge
    • 3-way merge
    • merge conflict
  • Remote branch

Git Objects

There are 4 types of objects in Git.

  • Files, called blobs in Git context
  • Directories, or trees in Git context
  • Commits
  • Tags

Get a Test Repo

On Github, fork a test repo:

https://github.com/cn330/git_branch.git

Then clone your forked repo, e.g. to a local repo named aa

git clone https://github.com/xxxxxxxxxx/git_branch.git aa

A Commit And Its Tree

A commit and its tree

Note: Commit IDs are abbreviated

Check Git's Log

RUN:

git log --oneline

OUTPUT:

636e44c5 (HEAD -> main, origin/main) Add .gitattributes
ed7759c4 (tag: v0.1) Add .gitignore
4aebcc12 (tag: v0.0) Initial commit

Check Latest Commit Object

Use Git's plumbing (low-level) command:

  • git cat-file -p to print contents of a Git object
  • latest commit from test repo is 636e44c5

RUN:

git cat-file -p 636e44c5

OUTPUT:

tree 04406b139f533d4133902c606399c1163753a6f0
parent ed7759c44067dccd820c22a3216d7ec193676ea8
author xxxxxxxxxx <xxxxxxxxxx@cn330> 1662039770 +0700
committer xxxxxxxxxx <xxxxxxxxxx@cn330> 1662039770 +0700

Add .gitattributes

A Commit Object

A commit object contains some meta data:

  • root tree => starting directory of this snapshot

  • parent commit => previous commit

    • one parent commit for normal commit
    • two parent commits for merge commit
    • multiple commits (separate meta data) for octopus merge commit
  • author

  • committer

  • commit message

Check Root tree of A Commit

For the commit 636e44c5:

  • object ID (SHA-1) of the root tree is 04406b1

RUN:

git cat-file -p 04406b1

OUTPUT:

100644 blob 176a458f94e0ea5272ce67c36bf30b6be9caf623    .gitattributes
100644 blob 1d74e21965c4f858f5f818a270e64e1bfad7d843    .gitignore
100644 blob e94ad8ec6cc44dd273a82c88402d2fbb721a2129    README.md
040000 tree 3ac016e418089a765e99924c8341102c43cf6fc9    dir_a
040000 tree c238a9a592088fba675b5394fd2f298b68e65508    dir_b

A Blob Object

A blob object simply contains contents of a file.

Check content of file README.md

RUN:

git cat-file -p e94ad8e

OUTPUT:

# README

A readme file.

A Tag Object

There are 3 kinds of tag:

  • lightweight tag,

    • a simple label
  • annotated tag

    • similar to a commit
  • signed tag

    • similar to a annotated tag with added public key

Check Contents of A Tag

Check content of tag v0.1, which is an annotated tag.

RUN:

git cat-file -p v0.1

OUTPUT:

object ed7759c44067dccd820c22a3216d7ec193676ea8
type commit
tag v0.1
tagger xxxxxxxxxx <xxxxxxxxxx@cn330> 1662059984 +0700

First working version

A Branch Object

A branch object is simply a pointer/label to a commit

  • cannot print a branch object using cat-file -p
  • can see its contents by looking at files in .git
    • e.g. .git/refs/heads/master

The Three Areas

Working with 3 areas

Test Repo

Initial commit graph

3 Areas With Test Repo

3 areas with test repo

Branch

Initial Test Repo

Check log:

RUN:

git log --oneline --graph

OUTPUT:

* 636e44c5 (HEAD -> main, origin/main) Add .gitattributes
* ed7759c4 (tag: v0.1) Add .gitignore
* 4aebcc12 (tag: v0.0) Initial commit

Initial Test Repo (Graph)

Initial test repo

Create A New Branch

Create a new branch named testing.

  • this simply add a new reference pointing to the current commit

RUN:

git branch testing

The command does not print out anything.

Create A New Branch (2)

List current local branches.

RUN:

git branch

OUTPUT:

  testing
* main

Create A New Branch (3)

Check log:

RUN:

git log --oneline --graph

OUTPUT:

* 636e44c5 (HEAD -> main, origin/main, testing) Add .gitattributes
* ed7759c4 (tag: v0.1) Add .gitignore
* 4aebcc12 (tag: v0.0) Initial commit

Create A New Branch (Graph)

After adding branch testing

Switch Branch

Change to branch testing

RUN:

git checkout testing

OUTPUT:

Switched to branch 'testing'

Since version 2.23, git has new command git switch.

RUN:

git switch testing

Switch Branch (2)

List current local branches.

RUN:

git branch

OUTPUT:

  main
* testing

Switch Branch (3)

Check log.

RUN:

git log --oneline

OUTPUT:

* 636e44c5 (HEAD -> testing, origin/main, main) Add .gitattributes
* ed7759c4 (tag: v0.1) Add .gitignore
* 4aebcc12 (tag: v0.0) Initial commit

Switch Branch (Graph)

After switching to branch testing

Note HEAD now points to testing.

Create a Commit on testing

Edit README.md, add change and make a commit.

RUN:

echo -e "\nMessage in a bottle" >> README.md
git add README.md
git commit -m "Add a message to README.md"

OUTPUT:

[testing 45b92d47] Add a message to README.md
 1 file changed, 2 insertions(+), 1 deletion(-)

Create a Commit on testing (2)

Check log.

RUN:

git log --oneline --graph

OUTPUT:

* 45b92d47 (HEAD -> testing) Add a message to README.md
* 636e44c5 (origin/main, main) Add .gitattributes
* ed7759c4 (tag: v0.1) Add .gitignore
* 4aebcc12 (tag: v0.0) Initial commit

Note where testing, main and HEAD are.

Create a Commit on testing (Graph)

After creating a new commit on testing

Switch Back to main

Switch back to branch main.

RUN:

git checkout main

OUTPUT:

Switched to branch 'main'
Your branch is up to date with 'origin/main'.

Switch Back to main (2)

Check log.

RUN:

git log --oneline --graph

OUTPUT:

* 636e44c5 (HEAD -> main, origin/main) Add .gitattributes
* ed7759c4 (tag: v0.1) Add .gitignore
* 4aebcc12 (tag: v0.0) Initial commit

Note:

  • By default, git log shows only commit in current branch.

Switch Back to main (3)

Check log for all branches.

RUN:

git log --oneline --graph --all

OUTPUT:

* 45b92d47 (testing) Add a message to README.md
* 636e44c5 (HEAD -> main, origin/main) Add .gitattributes
* ed7759c4 (tag: v0.1) Add .gitignore
* 4aebcc12 (tag: v0.0) Initial commit

Switch Back to main (Graph)

After switching back to branch main

Note:

  • Switching branches changes files in your working directory

Create a Commit on main

Edit README.md, add change and make a commit.

RUN:

echo -e "Be happy" >> README.md
git add README.md
git commit -m "Add a second message to README.md"

OUTPUT:

[main cc2a2e6f] Add a second message to README.md
 1 file changed, 1 insertion(+), 1 deletion(-)

Create a Commit on main (2)

Check log.

RUN:

git log --oneline --graph

OUTPUT:

* cc2a2e6f (HEAD -> main) Add a second message to README.md
* 636e44c5 (origin/main) Add .gitattributes
* ed7759c4 (tag: v0.1) Add .gitignore
* 4aebcc12 (tag: v0.0) Initial commit

Create a Commit on main (3)

Check log for all branches.

RUN:

git log --oneline --graph --all

OUTPUT:

* cc2a2e6f (HEAD -> main) Add a second message to README.md
| * 45b92d47 (testing) Add a message to README.md
|/
* 636e44c5 (origin/main) Add .gitattributes
* ed7759c4 (tag: v0.1) Add .gitignore
* 4aebcc12 (tag: v0.0) Initial commit

Create a Commit on main (Graph)

After creating a new commit on main

Delete A Branch

Will create a new branch and then delete it.

Create a new branch named fix_x.

RUN:

git branch fix_x

Delete A Branch (2)

List current local branches.

RUN:

git branch

OUTPUT:

  fix_x
* main
  testing

Delete A Branch (3)

Check log for all branches.

RUN:

git log --oneline --graph --all

OUTPUT:

* cc2a2e6f (HEAD -> main, fix_x) Add a second message to README.md
| * 45b92d47 (testing) Add a message to README.md
|/
* 636e44c5 (origin/main) Add .gitattributes
* ed7759c4 (tag: v0.1) Add .gitignore
* 4aebcc12 (tag: v0.0) Initial commit

Delete A Branch (Graph)

After creating branch fix_x

Delete A Branch (5)

Delete branch fix_x.

RUN:

git branch -d fix_x

OUTPUT:

Deleted branch fix_x (was cc2a2e6f).

Delete A Branch (6)

List current local branches.

RUN:

git branch

OUTPUT:

* main
  testing

Merge

Clone A Forked Test Repo

From your forked of test repo, e.g.

Clone your forked repo, e.g. to a local repo named bb

RUN:

git clone https://github.com/xxxxxxxxxx/git_branch.git bb

Create a New Branch feature_x

Create a new branch named feature_x.

RUN:

git checkout -b feature_x   # or  git switch -c feature_x

OUTPUT:

Switched to a new branch 'feature_x'

This is equivalent to:

git branch feature_x
git checkout feature_x    # or  git switch feature_x

Create a New Branch feature_x (Graph)

After creating and switching to feature_x

Create a Commit on feature_x

Edit README.md, add change and make a commit.

RUN:

echo -e "\nAdd a new feature x" >> README.md
git add README.md
git commit -m "Add feature x"

OUTPUT:

[feature_x 2a5c6158] Add feature x
 1 file changed, 2 insertions(+), 1 deletion(-)

Create a Commit on feature_x (2)

Check log.

RUN:

git log --oneline --graph --all

OUTPUT:

* 2a5c6158 (HEAD -> feature_x) Add feature x
* 636e44c5 (origin/main, origin/HEAD, main) Add .gitattributes
* ed7759c4 (tag: v0.1) Add .gitignore
* 4aebcc12 (tag: v0.0) Initial commit

Note where feature_x, main and HEAD are.

Create a Commit on feature_x (Graph)

After creating a new commit on feature_x

Switch Branch to main

RUN:

git checkout main   # or  git switch main

OUTPUT:

Switched to branch 'main'
Your branch is up to date with 'origin/main'.

Note:

  • if there is any change in working directory or staging area (index), git will not let you switch branch
  • it is best to have clean working directory before switching branch

Switch Branch to main (Graph)

Switch back to main after a commit in feature_x

Create a New Branch hotfix

Create and switch to a new branch hotfix.

RUN:

git checkout -b hotfix   # or  git switch -c hotfix

OUTPUT:

Switched to a new branch 'hotfix'

Create a New Branch hotfix (Graph)

After creating and switching to hotfix

Create a Commit on hotfix

Add a new file note.md to repo.

RUN:

echo "# Note" > note.md
git add note.md
git commit -m "Add note.md"

OUTPUT:

[hotfix 148e070d] Add note.md
 1 file changed, 1 insertion(+)
 create mode 100644 note.md

Create a Commit on hotfix (Graph)

After creating a new commit on hotfix

Merge: Switch to main

Switch to branch main.

RUN:

git checkout main  # or  git switch main

OUTPUT:

Switched to branch 'main'
Your branch is up to date with 'origin/main'.

Merge: Switch to main (Graph)

Switch to main after a commit on hotfix

Merge: Fast-Forward

Merge commit from hotfix onto current branch (main).

RUN:

git merge hotfix

OUTPUT:

Fast-forward
 note.md | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 note.md

Merge: Fast-Forward (2)

Check log.

RUN:

git log --oneline --graph --all

OUTPUT:

* 148e070d (HEAD -> main, hotfix) Add note.md
| * 2a5c6158 (feature_x) Add feature x
|/
* 636e44c5 (origin/main, origin/HEAD) Add .gitattributes
* ed7759c4 (tag: v0.1) Add .gitignore
* 4aebcc12 (tag: v0.0) Initial commit

Note that there is no new commit created.

git simply updates main to another commit.

Merge: Fast-Forward (Graph)

After merging hotfix onto main

Merge: Fast-Forward (3)

  • since commit from branch hotfix has been merged into branch main, if there is no further use on branch hotfix, it can safely be deleted.

RUN:

git branch --delete hotfix

OUTPUT:

* 148e070d (HEAD -> main) Add note.md
| * 2a5c6158 (feature_x) Add feature x
|/
* 636e44c5 (origin/main, origin/HEAD) Add .gitattributes
* ed7759c4 (tag: v0.1) Add .gitignore
* 4aebcc12 (tag: v0.0) Initial commit

Merge: Fast-Forward (Graph 2)

After deleting hotfix

Merge: 3-Way Merge - no conflict

A 3-way merge creates a new commit with 2 parents.

RUN:

git branch  # check currrent branch.

OUTPUT:

  feature_x
  hotfix
* main

Merge: 3-Way Merge - no conflict (2)

Merge new commits from branch feature_x onto current branch.

RUN:

git merge feature_x
  • git will ask for commit message if this is a non-fast-forward merge.

OUTPUT:

Merge made by the 'ort' strategy.
 README.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
  • This is a simple case where there is no merge conflict.

Merge: 3-Way Merge - no conflict (3)

Check log

RUN:

git log --oneline --graph --all

OUTPUT:

*   6b192ed1 (HEAD -> main) Merge branch 'feature_x'
|\
| * 2a5c6158 (feature_x) Add feature x
* | baa8e6ad Add note.md
|/
* 636e44c5 (origin/main, origin/HEAD) Add .gitattributes
* ed7759c4 (tag: v0.1) Add .gitignore
* 4aebcc12 (tag: v0.0) Initial commit

Merge: 3-Way Merge - no conflict (Graph)

Merge commit from feature_x - no conflict

Merge: 3-Way Merge - with conflict

Make sure current branch is main

RUN:

git switch main

OUTPUT:

Your branch is ahead of 'origin/main' by 3 commits.
  (use "git push" to publish your local commits)

Merge: 3-Way Merge - with conflict (2)

Move current branch (main) back by 1 commit

RUN:

git reset --hard HEAD~1   # or  git reset --hard HEAD@{1}
  • option --hard will replace all tracked files in working directory with the ones in the specified commit (HEAD~1 in this case)

OUTPUT:

HEAD is now at 148e070d Add note.md

Merge: 3-Way Merge - with conflict (Graph)

After reseting main back by 1 commit

Merge: 3-Way Merge - with conflict (3)

Switch to branch feature_x.

RUN:

git checkout feature_x   # or  git switch feature_x

OUTPUT:

Switched to branch 'feature_x'

Merge: 3-Way Merge - with conflict (Graph 2)

Switch to feature_x after reset in main

Merge: 3-Way Merge - with conflict (4)

Create a commit that will conflict with latest commit in main

RUN:

echo "# Note: from feature x" > note.md" > note.md
git add note.md
git commit -m "Add note.md with with feature x"

OUTPUT:

[feature_x 93880b20] Add note.md with with feature x
 1 file changed, 1 insertion(+)
 create mode 100644 note.md

Merge: 3-Way Merge - with conflict (Graph 3)

Create a new commit in feature_x

Merge: 3-Way Merge - with conflict (5)

Merge commit from main onto current branch (feature_x).

RUN:

git merge main

OUTPUT:

Auto-merging note.md
CONFLICT (add/add): Merge conflict in note.md
Automatic merge failed; fix conflicts and then commit the result.

Merge: 3-Way Merge - with conflict (6)

Check status of merge conflict.

RUN:

git status

OUTPUT:

On branch feature_x
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
    both added:      note.md

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

Merge: 3-Way Merge - with conflict (7)

Open the file that causes the conflict, note.md in this case.

<<<<<<< HEAD
# Note: from feature x
=======
# Note
>>>>>>> main
  • <<<<<<< HEAD marks the start of change on current branch
  • ======= separates the changes from two branches
  • >>>>>>> main marks the end of change from the other branch that your are trying to merge

Merge: 3-Way Merge - resolve merge conflict

To resolve conflict:

  • decide to either

    • keep changes in current branch
    • or keep changes from the other branch
    • or make new change, which may include changes from both branches
  • delete the conflict markers <<<<<<<, =======, >>>>>>>

    • then make the changes you want in the final merge
  • then add change and commit as usual

Merge: 3-Way Merge - resolve merge conflict (2)

Ddelete conflict markers and decide the final change

  • in this case, change file note.md
# Note from feature x

RUN:

git add note.md
git commit -m "Merge branch main"

OUTPUT:

[feature_x 2f7f7736] Merge branch main

Merge: 3-Way Merge - resolve merge conflict (Graph)

After resolving a merge conflict on feature_x

References