7. Branches#

Branches are incredibly important to why git is cool and powerful.

They are an easy and cheap way of making a second version of your software, which you work on in parallel, and pull in your changes when you are ready.

import os

try:
    from google.colab import drive  # type: ignore

    drive.mount("/content/drive")
    drive_dir = "/content/drive/MyDrive"
except ImportError:
    print("Not running on colab")
    drive_dir = os.path.join(os.getcwd(), "drive", "MyDrive")
    os.makedirs(drive_dir, exist_ok=True)

print(f"Drive dir: {drive_dir}")

git_dir = os.path.join(drive_dir, "learning_git")
working_dir = os.path.join(git_dir, "git_example")

if os.path.exists(working_dir):
    print(f"Git example directory: {working_dir}")
    os.chdir(working_dir)
else:
    print("Start from the beginning")
Not running on colab
Drive dir: /mnt/nvme1n1p2/home/yj.lee/workspace/projects/lecture/book/lectures/softeng/vcs/drive/MyDrive
Git example directory: /mnt/nvme1n1p2/home/yj.lee/workspace/projects/lecture/book/lectures/softeng/vcs/drive/MyDrive/learning_git/git_example
%%bash
git branch # Tell me what branches exist
* main
%%bash
git checkout -b experiment # Make a new branch
Switched to a new branch 'experiment'
%%bash
git branch
* experiment
  main
%%writefile Wales.md
Mountains In Wales
==================

* Pen y Fan
* Tryfan
* Snowdon
* Glyder Fawr
* Fan y Big
* Cadair Idris
Overwriting Wales.md
%%bash
git commit -am "Add Cadair Idris"
[experiment 4b4edc1] Add Cadair Idris
 1 file changed, 2 insertions(+), 1 deletion(-)
%%bash
git checkout main # Switch to an existing branch
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
%%bash
cat Wales.md
Mountains In Wales
==================

* Pen y Fan
* Tryfan
* Snowdon
* Fan y Big
* Glyder Fawr
%%bash
git checkout experiment
Switched to branch 'experiment'
!cat Wales.md
Mountains In Wales
==================

* Pen y Fan
* Tryfan
* Snowdon
* Glyder Fawr
* Fan y Big
* Cadair Idris

Publishing branches#

To let the server know there’s a new branch use:

%%bash
git push -u origin experiment
remote: 
remote: Create a pull request for 'experiment' on GitHub by visiting:        
remote:      https://github.com/chu-aie/github-example/pull/new/experiment        
remote: 
To https://github.com/chu-aie/github-example.git
 * [new branch]      experiment -> experiment
branch 'experiment' set up to track 'origin/experiment'.

We use --set-upstream origin (Abbreviation -u) to tell git that this branch should be pushed to and pulled from origin per default.

If you are following along, you should be able to see your branch in the list of branches in GitHub.

Once you’ve used git push -u once, you can push new changes to the branch with just a git push.

If others checkout your repository, they will be able to do git checkout experiment to see your branch content, and collaborate with you in the branch.

%%bash
git branch -r
  origin/experiment
  origin/main

Local branches can be, but do not have to be, connected to remote branches They are said to “track” remote branches. push -u sets up the tracking relationship. You can see the remote branch for each of your local branches if you ask for “verbose” output from git branch:

%%bash
git branch -vv
* experiment 4b4edc1 [origin/experiment] Add Cadair Idris
  main       cc360cb [origin/main] Add Glyder

Find out what is on a branch#

In addition to using git diff to compare to the state of a branch, you can use git log to look at lists of commits which are in a branch and haven’t been merged yet.

%%bash
git log main..experiment
commit 4b4edc115e87629b853fc5084931350348909663
Author: Young Joon Lee <entelecheia@hotmail.com>
Date:   Mon Sep 18 03:50:16 2023 +0900

    Add Cadair Idris

Git uses various symbols to refer to sets of commits. The double dot A..B means “ancestor of B and not ancestor of A”

So in a purely linear sequence, it does what you’d expect.

%%bash
git log --graph --oneline HEAD~9..HEAD~5
* 54fa40f Add wales
* 5524925 Add Scotland
* 4fa39c8 Add Helvellyn
* d2b4434 Include lakes in the scope

But in cases where a history has branches, the definition in terms of ancestors is important.

%%bash
git log --graph --oneline HEAD~5..HEAD
* 4b4edc1 Add Cadair Idris
* cc360cb Add Glyder
* e5451fd Add another Beacon
* b101602 Translating from the Welsh
* ac80a2f Add a beacon

If there are changes on both sides, like this:

%%bash
git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
%%writefile Scotland.md
Mountains In Scotland
==================

* Ben Eighe
* Cairngorm
* Aonach Eagach
Overwriting Scotland.md
%%bash
git diff Scotland.md
diff --git a/Scotland.md b/Scotland.md
index 9613dda..bf5c643 100644
--- a/Scotland.md
+++ b/Scotland.md
@@ -3,3 +3,4 @@ Mountains In Scotland
 
 * Ben Eighe
 * Cairngorm
+* Aonach Eagach
%%bash
git commit -am "Commit Aonach onto main branch"
[main 7202261] Commit Aonach onto main branch
 1 file changed, 1 insertion(+)

Then this notation is useful to show the content of what’s on what branch:

%%bash
git log --left-right --oneline main...experiment
< 7202261 Commit Aonach onto main branch
> 4b4edc1 Add Cadair Idris

Three dots means “everything which is not a common ancestor” of the two commits, i.e. the differences between them.

Merging branches#

We can merge branches, and just as we would pull in remote changes, there may or may not be conflicts.

%%bash
git branch
git merge experiment
  experiment
* main
Merge made by the 'ort' strategy.
 Wales.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
%%bash
git log --graph --oneline HEAD~3..HEAD
*   4fb2ed8 Merge branch 'experiment'
|\  
| * 4b4edc1 Add Cadair Idris
* | 7202261 Commit Aonach onto main branch
|/  
* cc360cb Add Glyder

Cleaning up after a branch#

%%bash
git branch  # list branches
  experiment
* main
%%bash
git branch -d experiment  # delete a branch
Deleted branch experiment (was 4b4edc1).
%%bash
git branch # current branch
* main
%%bash
git branch --remote  # list remote branches
  origin/experiment
  origin/main
%%bash
git push --delete origin experiment
# Remove remote branch. Note that you can also use the GitHub interface to do this.
To https://github.com/chu-aie/github-example.git
 - [deleted]         experiment
%%bash
git branch --remote  # list remote branches
  origin/main
%%bash
git push
To https://github.com/chu-aie/github-example.git
   cc360cb..4fb2ed8  main -> main
%%bash
git branch  # current branch
* main

A good branch strategy#

  • A production or main branch: the current working version of your code

  • A develop branch: where new code can be tested

  • feature branches: for specific new ideas

  • release branches: when you share code with others

    • Useful for applying bug fixes to older versions of your code

Grab changes from a branch#

Make some changes on one branch, switch back to another, and use:

git checkout <branch> <path>

to quickly grab a file from one branch into another. This will create a copy of the file as it exists in <branch> into your current branch, overwriting it if it already existed. For example, if you have been experimenting in a new branch but want to undo all your changes to a particular file (that is, restore the file to its version in the main branch), you can do that with:

git checkout main test_file

Using git checkout with a path takes the content of files. To grab the content of a specific commit from another branch, and apply it as a patch to your branch, use:

git cherry-pick <commit>