XClose

MPHY0021: Research Software Engineering With Python

Home
Menu

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.

In [1]:
import os
top_dir = os.getcwd()
git_dir = os.path.join(top_dir, 'learning_git')
working_dir = os.path.join(git_dir, 'git_example')
os.chdir(working_dir)
In [2]:
%%bash
git branch # Tell me what branches exist
* master
In [3]:
%%bash
git checkout -b experiment # Make a new branch
Switched to a new branch 'experiment'
In [4]:
%%bash
git branch
* experiment
  master
In [5]:
%%writefile Wales.md
Mountains In Wales
==================

* Pen y Fan
* Tryfan
* Snowdon
* Glyder Fawr
* Fan y Big
* Cadair Idris
Overwriting Wales.md
In [6]:
%%bash
git commit -am "Add Cadair Idris"
[experiment 557ac02] Add Cadair Idris
 1 file changed, 2 insertions(+)
In [7]:
%%bash
git checkout master # Switch to an existing branch
Switched to branch 'master'
In [8]:
%%bash
cat Wales.md
Mountains In Wales
==================

* Pen y Fan
* Tryfan
* Snowdon
* Glyder Fawr
In [9]:
%%bash
git checkout experiment
Switched to branch 'experiment'
In [10]:
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:

In [11]:
%%bash
git push -u origin experiment
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
---------------------------------------------------------------------------
CalledProcessError                        Traceback (most recent call last)
/tmp/ipykernel_10081/3114635562.py in <module>
----> 1 get_ipython().run_cell_magic('bash', '', 'git push -u origin experiment\n')

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/IPython/core/interactiveshell.py in run_cell_magic(self, magic_name, line, cell)
   2417             with self.builtin_trap:
   2418                 args = (magic_arg_s, cell)
-> 2419                 result = fn(*args, **kwargs)
   2420             return result
   2421 

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/IPython/core/magics/script.py in named_script_magic(line, cell)
    140             else:
    141                 line = script
--> 142             return self.shebang(line, cell)
    143 
    144         # write a basic docstring:

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/decorator.py in fun(*args, **kw)
    230             if not kwsyntax:
    231                 args, kw = fix(args, kw, sig)
--> 232             return caller(func, *(extras + args), **kw)
    233     fun.__name__ = func.__name__
    234     fun.__doc__ = func.__doc__

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/IPython/core/magic.py in <lambda>(f, *a, **k)
    185     # but it's overkill for just that one bit of state.
    186     def magic_deco(arg):
--> 187         call = lambda f, *a, **k: f(*a, **k)
    188 
    189         if callable(arg):

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/IPython/core/magics/script.py in shebang(self, line, cell)
    243             sys.stderr.flush()
    244         if args.raise_error and p.returncode!=0:
--> 245             raise CalledProcessError(p.returncode, cell, output=out, stderr=err)
    246 
    247     def _run_script(self, p, cell, to_close):

CalledProcessError: Command 'b'git push -u origin experiment\n'' returned non-zero exit status 128.

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.

In [12]:
%%bash
git branch -r

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:

In [13]:
%%bash
git branch -vv
* experiment 557ac02 Add Cadair Idris
  master     44e89d1 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.

In [14]:
%%bash
git log master..experiment
commit 557ac025d09a1aa46e2ca1f4d2400f82587aec8e
Author: Lancelot the Brave <l.brave@spamalot.uk>
Date:   Tue Dec 7 14:36:42 2021 +0000

    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.

In [15]:
%%bash
git log --graph --oneline HEAD~9..HEAD~5
* 976f55c Add Scotland
* 480039f Add wales
* a77a766 Add Helvellyn
* e95fb6e Include lakes in the scope

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

In [16]:
%%bash
git log --graph --oneline HEAD~5..HEAD
* 557ac02 Add Cadair Idris
* 44e89d1 Add Glyder
* a496f22 Add another Beacon
* 94628af Add a beacon
* 380d1f1 Translating from the Welsh

If there are changes on both sides, like this:

In [17]:
%%bash
git checkout master
Switched to branch 'master'
In [18]:
%%writefile Scotland.md
Mountains In Scotland
==================

* Ben Eighe
* Cairngorm
* Aonach Eagach
Overwriting Scotland.md
In [19]:
%%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
In [20]:
%%bash
git commit -am "Commit Aonach onto master branch"
[master 7876da4] Commit Aonach onto master branch
 1 file changed, 1 insertion(+)

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

In [21]:
%%bash
git log --left-right --oneline master...experiment
< 7876da4 Commit Aonach onto master branch
> 557ac02 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.

In [22]:
%%bash
git branch
git merge experiment
  experiment
* master
Merge made by the 'ort' strategy.
 Wales.md | 2 ++
 1 file changed, 2 insertions(+)
In [23]:
%%bash
git log --graph --oneline HEAD~3..HEAD
*   9e02157 Merge branch 'experiment'
|\  
| * 557ac02 Add Cadair Idris
* | 7876da4 Commit Aonach onto master branch
|/  
* 44e89d1 Add Glyder

Cleaning up after a branch

In [24]:
%%bash
git branch
  experiment
* master
In [25]:
%%bash
git branch -d experiment
Deleted branch experiment (was 557ac02).
In [26]:
%%bash
git branch
* master
In [27]:
%%bash
git branch --remote
In [28]:
%%bash
git push --delete origin experiment 
# Remove remote branch 
# - also can use github interface
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
---------------------------------------------------------------------------
CalledProcessError                        Traceback (most recent call last)
/tmp/ipykernel_10081/2028302425.py in <module>
----> 1 get_ipython().run_cell_magic('bash', '', 'git push --delete origin experiment \n# Remove remote branch \n# - also can use github interface\n')

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/IPython/core/interactiveshell.py in run_cell_magic(self, magic_name, line, cell)
   2417             with self.builtin_trap:
   2418                 args = (magic_arg_s, cell)
-> 2419                 result = fn(*args, **kwargs)
   2420             return result
   2421 

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/IPython/core/magics/script.py in named_script_magic(line, cell)
    140             else:
    141                 line = script
--> 142             return self.shebang(line, cell)
    143 
    144         # write a basic docstring:

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/decorator.py in fun(*args, **kw)
    230             if not kwsyntax:
    231                 args, kw = fix(args, kw, sig)
--> 232             return caller(func, *(extras + args), **kw)
    233     fun.__name__ = func.__name__
    234     fun.__doc__ = func.__doc__

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/IPython/core/magic.py in <lambda>(f, *a, **k)
    185     # but it's overkill for just that one bit of state.
    186     def magic_deco(arg):
--> 187         call = lambda f, *a, **k: f(*a, **k)
    188 
    189         if callable(arg):

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/IPython/core/magics/script.py in shebang(self, line, cell)
    243             sys.stderr.flush()
    244         if args.raise_error and p.returncode!=0:
--> 245             raise CalledProcessError(p.returncode, cell, output=out, stderr=err)
    246 
    247     def _run_script(self, p, cell, to_close):

CalledProcessError: Command 'b'git push --delete origin experiment \n# Remove remote branch \n# - also can use github interface\n'' returned non-zero exit status 128.
In [29]:
%%bash
git branch --remote

A good branch strategy

  • A production branch: code used for active work
  • A develop branch: for general new code
  • feature branches: for specific new ideas
  • release branches: when you share code with others
    • Useful for isolated bug fixes

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 master branch), you can do that with:

git checkout master 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>