Branches in Git

Amir Ebrahimi Fard
Data Management for Researchers
5 min readJul 26, 2021

--

Photo by Ron Whitaker on Unsplash

When a Git repository is initialised within a project directory, it creates a structure analogous to a tree trunk that records a chronological history of commits to the main working directory.

In some situations, for example working in parallel with collaborators or on experimental features, it is helpful to create one or more branches on this trunk. The new branch inherits all the commits up to the branching point, and from there it continues independently with its own commit history separate from the trunk. Later on, the contents of the branch and its history of commits can be merged to the main trunk again, for example if an experimental feature is developed to the point where it should become part of the main project.

Figure 1: A Git “tree” with multiple branches. (Source of the figure: “File:Git branches two forks and merge.svg” by Bunyk is licensed under CC BY-SA 4.0)

The git branch <branch_name> command is used to create a new branch. In order to switch to this new branch (or any other existing ones) we use git checkout <target_branch>. Those two commands are combined in git checkout -b <branch_name> , which makes it easy to automatically create a branch and switch to it in one step. It is also possible to go back and branch out from a past commit using git branch <branch_name> <branch_out_SHA>.

Sometimes branches need to be merged (e.g., when a couple of independent features are developed in separate branches and should be merged into the main application on the master branch) in which case we use git merge <branch_name>. The underlying assumption for this command is that the branch specified in <branch_name> will be added to the branch we are currently in.

There are two kinds of merges: (i) a divergent merge, and (ii) a fast-forwarding merge. In a divergent merge, both branches have been continued after the branching point, while in a fast-forwarding merge, the current branch pointer (HEAD¹) is an ancestor of the commit that is being merged. Figures 2 and 3 show the difference between divergent and fast-forward-merges. In the former, merging two branches creates a new commit, and in the latter, instead of constructing a merge commit, Git just moves the branch pointer to point at the incoming commit. This commonly occurs when doing a git pull² in the absence of any local changes. To avoid this, we can use git merge feature - — no-ff which creates a new commit object [1](Figure 3).

Figure 2: A divergent merge.
Figure 3: A fast-forwarding merge.
Figure 3: A fast-forwarding merge with an extra null commit.

Attempting to merge branches is not always successful and may lead to an error called a merge conflict. Conflicts generally arise when two branches change the same lines in a file, or if one branch deleted a file while another modified and saved it. In these cases, Git cannot automatically determine what is correct. Conflicts only affect the branch conducting the merge — the rest of the team (working on other branches) is unaware of the conflict. Git will mark the file as being conflicted and halt the merging process. It is then the developer’s responsibility to manually resolve the conflict.

When a merge conflict happens, Git leaves some guiding markers within the contents of the conflicted file. The ======= line shows the “center” of the conflict. All the content between the center and the <<<<<<< HEAD line exists in the current branch to which the HEAD ref is pointing. Alternatively, all content between the center and >>>>>>> <new_branch_to_merge_later> is content that is present in the merging branch [2]. To resolve the conflict, the content in between the markers has to be manually edited. Figure 4 illustrates this process. After editing the file, running git add <file_name> and then git commit <file_name> -m <commit_message>, resolves the conflict and records this as a new commit.

Figure 4: An example of a conflict resolution process.

After merging a branch into “main,” we may want to remove it from the project. This can be done by running the command git branch -d <branch_name>. Git does not allow deletion of a branch in two cases: (i) if you are in a branch while trying to remove it and (ii) when you attempt to delete a branch with commits that are not on any other branches³. One very useful command for avoiding these issues is git log --oneline --graph --all. When working with multiple branches, this command displays the history of commits in a tree-shaped diagram. If the commit history is like Figure 5, the output of this command would not look like a tree (it looks like we have only one single branch).

Figure 5: An example of a Git tree with linear visualisation.

Footnotes

  1. In the referencing section, we will talk about HEAD.
  2. In the remote repository section, we will explain working with the git pull command.
  3. In other words, the branches that are unmerged. In this case the branch could be deleted if it is forced: git branch -D <branch_name>

--

--

Amir Ebrahimi Fard
Data Management for Researchers

Postdoc Researcher on AI Explainability - Interested in the intersection of data, algorithm, and society.