Content from Introduction
Last updated on 2025-11-05 | Edit this page
Overview
Questions
- What is a Git branch and why is it useful in collaborative development?
- When should I create a new branch in my project?
- What are the differences between fast-forward merge, 3-way merge, rebase, and squash and merge?
- How does Git handle merging when branches have diverged?
Objectives
- Understand the purpose and benefits of using Git branches, especially the feature branch workflow in collaborative projects.
- Compare Git merging strategies (fast-forward, 3-way merge, rebase, squash and merge) and understand when to use each.
- Gain familiarity with intermediate Git features, including cherry-picking, stashing, and resetting.
Basic Git training usually covers the essential concepts, such as adding files, committing changes, viewing commit history, and checking out or reverting to earlier versions. But for RSEs working in collaborative, code-intensive projects, that is just the tip of the iceberg. More detailed topics like branching and merging strategies, and understanding merge conflicts are critical for managing code across teams and maintaining clean, reproducible development workflows.
In this lesson we will explore branching and feature branch workflow, a popular method for collaborative development using Git, along with some intermediate Git features (merging, rebasing, cherry-picking) and handling merge conflicts that can help streamline your development workflow and avoid common pitfalls in collaborative software development.
Feature Branch Workflow
Git Branches
You might be used to committing code directly, but not sure what
branches really are or why they matter? When you start a new Git
repository and begin committing, all changes go into a branch — by
default, this is usually called main (or
master in older repositories). The name main
is just a convention — a Git repository’s default branch can technically
be named anything.
Why not just always use main branch? While it is
possible to always commit to main, it is not ideal when
you’re collaborating with others, you are working on new features or
want to experiment with your code, and you want to keep main clean and
stable for your users and collaborators.
Feature Branch
Creating a separate branch (often called a “feature” branch) allows you to add or test code (containing a new “feature”) without affecting the main line of development, work in parallel with collagues without worrying that your code may break something for the rest of the team and review and merge changes safely after testing using pull/merge requests.
How do you decide when to use a new branch? You should consider starting a new branch whenever you are working on a distinct feature or fixing a specific bug. This allows you to collect a related set of commits in one place, without interfering with other parts of the project.
Branching helps separate concerns in your codebase, making development, testing, and code review much easier. It also reduces the chance of conflicts during collaborative work, especially when multiple people are contributing to the same repository.
This approach is known as the feature branch workflow. In this model of working, each new feature or fix lives in its own branch. Once the work is complete and has been tested, the branch is reviewed by project collaborators (other than the code author), any merge conflicts addressed and the new work merged back into the main branch. Using feature branches is an efficient way to manage changes, collaborate effectively, and keep the main branch stable and production-ready.
Merging Strategies
Merging
When you are ready to bring the changes from your feature branch back into the main branch, Git offers you to do a merge - a process that unifies work done in 2 separate branches. Git will take two (or more - you can merge more branches at the same time) commit pointers and attempt to find a common base commit between them. Git has several different methods of finding the base commit - these methods are called “merge strategies”. Once Git finds the common base commit it will create a new “merge commit” that combines the changes of the specified merge commits. Technically, a merge commit is a regular commit which just happens to have two parent commits.
Each merge strategy is suited for a different scenario. The choice of strategy depends on the complexity of changes and the desired outcome. Let’s have a look at the most commonly used merge strategies.
Fast Forward Merge
A fast-forward merge occurs when the main branch has not diverged from the feature branch - meaning there are no new commits on the main branch since the feature branch was created.
A - B - C [main]
\
D - E [feature]
In this case, Git simply moves the main branch pointer to the latest commit in the feature branch. This strategy is simple and keeps the commit history linear - i.e. the history is one straight line.
After a fast forward merge:
A - B - C - D - E [main][feature]
3-Way Merge with Merge Commit
A fast-forward merge is not possible if the main and the feature branches have diverged.
A - B - C - F [main]
\
D - E [feature]
If you try to merge your feature branch changes into the main branch and other changes have been made to main - regardless of whether these changes create a conflict or not - Git will try to do a 3-way merge and generate a merge commit.
A merge commit is a dedicated special commit that records the combined changes from both branches and has two parent commits, preserving the history of both lines of development. The name “3-way merge” comes from the fact that Git uses three commits to generate the merge commit - the two branch tips and their common ancestor to reconstruct the changes that are to be merged.
A - B - C - F - "MergeCommitG" [main]
\ /
D - E [feature]
In addition, if the two branches you are trying to merge both changed the same part of the same file, Git will not be able to figure out which version to use and merge automatically. When such a situation occurs, it stops right before the merge commit so that you can resolve the conflicts manually before continuing.
Rebase & Merge
In Git, there is another way to integrate changes from one branch into another - a rebase.
Let’s go back to an earlier example from the 3-way merge, where main and feature branches have diverged with subsequent commits made on each (so fast-forward merging strategy is not an option).
A - B - C - F [main]
\
D - E [feature]
When you rebase the feature branch with the main branch, Git replays each commit from the feature branch on top of all the commits from the main branch in order. This results in a cleaner, linear history that looks as if the feature branch was started from the latest commit on main.
So, all the changes introduced on feature branch (commits D and E) are reapplied on top of commit F - becoming D’ and E’. Note that D’ and E’ are rebased commits, which are actually new commits with different SHAs but the same modifications as commits D and E.
A - B - C - F [main]
\
D' - E' [feature]
At this point, you can go back to the main branch and do a fast-forward merge with feature branch.
Fast forward merge strategy is best used when you have a short-lived feature branch that needs to be merged back into the main branch, and no other changes have been made to the main branch in the meantime.
Rebase is ideal for feature branches that have fallen behind the main development line and need updating. It is particularly useful before merging long-running feature branches to ensure they apply cleanly on top of the main branch. Rebasing maintains a linear history and avoids merge commits (like fast forwarding), making it look as if changes were made sequentially and as if you created your feature branch from a different point in the repository’s history. A disadvantage is that it rewrites commit history, which can be problematic for shared branches as it requires force pushing.
Rebase and merge location
Note that you are rebasing into the feature branch and merging into the main branch.
Here is a little comparison of the three merge strategies we have covered so far.
| Fast Forward | Rebasing | 3-Way Merge |
|---|---|---|
| Maintains linear history | Maintains linear history | Non-linear history (commit with 2 parents) |
| No new commits on main | New commits on main | New commits on main |
| Avoids merge commits | Avoids merge commits | Uses merge commits |
| Only works if there are no new commits on the main branch | Works for diverging branches | Works for diverging branches |
| Does not rewrite commit history | Rewrites commit history | Does not rewrite commit history |
Squash & Merge
Squash and merge command (git merge --squash) squashes
all the commits from a feature branch into a single regular commit (and
not a “3-way merge” commit) before merging into the main branch. This
strategy simplifies the commit history, making it easier to follow. This
strategy is ideal for merging feature branches with numerous small
commits, resulting in a cleaner main branch history.
Handy Git Features for Managing Local Changes
As your projects grow, you will occasionally need to manage your local code history more precisely. Git offers a few useful features to help you do just that — especially when you are not quite ready to commit or want to isolate specific changes.
Git Stash: Setting Changes Aside for Later
Imagine you are halfway through some code changes and suddenly need
to switch tasks or pull updates from the remote branch. Committing is
not ideal yet — so what do you do? Use git stash command to
safely store your uncommitted changes in a local “stash”. This lets you
clean your working directory and avoid conflicts, without losing any
work. When you are ready, you can bring those changes back using the
git stash pop command.
Git Cherry-Pick: Pulling in a Specific Commit
Sometimes, you want to take just one specific commit (say, from
another branch) and apply it to your current branch — without merging
the whole branch. That is where git cherry-pick command
comes in. It applies the changes from the chosen commit directly on top
of your current branch, as if you’d made them there all along.
Git Reset: Rewinding Your Commit History
Made a commit too soon? git reset command allows you to
undo commits locally. It moves your branch pointer back to an earlier
commit, turning those “undone” changes into uncommitted edits in your
working directory. It is handy for rewriting local history before
sharing code — but be careful using it on shared branches, as it alters
commit history.
Practical Work
In the rest of this session, we will walk you through the feature branch workflow, different merging strategies and handling conflicts before merging.
- A Git branch is an independent line of development; the default is
conventionally called
main(but all branches are equal and the main branch can be renamed). - Branches help you manage change, collaborate better, and avoid messy mistakes on main.
- Feature branches let you develop and test code without affecting the main branch and support collaborative and parallel development.
- Fast-forward merges are used when the main branch has not changed since the feature branch was created, resulting in a linear history.
- 3-way merges occur when both branches have diverged; Git creates a merge commit to preserve both histories.
- Rebasing replays feature branch commits on top of the main branch for a cleaner, linear history—but it rewrites history and should be used with care.
- Squash and merge compresses all changes from a feature branch into a single commit, simplifying history.
- Understanding different merge strategies and when to use them is crucial for maintaining clean and manageable project histories.
Content from Example Code
Last updated on 2025-10-28 | Edit this page
Overview
Questions
- What are Git “branches”?
- Why should I separate different strands of code work into “feature branches”?
- How should I capture problems with my code that I want to fix?
Objectives
- Obtain example code used for this lesson
- List the issues with the example code
- Describe the limitations of using a single branch on a repository
- Create issues on GitHub that describe problems that will be fixed throughout the lesson
Creating a Copy of the Example Code Repository
For this lesson we’ll need to create a new GitHub repository based on the contents of another repository.
- Once logged into GitHub in a web browser, go to https://github.com/UNIVERSE-HPC/git-example.
- Select
Use this template, and then selectCreate a new repositoryfrom the dropdown menu. - On the next screen, ensure your personal GitHub account is selected
in the
Ownerfield, and fill inRepository namewith “git-example”. - Ensure the repository is set to
Public. - Select
Create repository.
You should be presented with the new repository’s main page. Next, we
need to clone this repository onto our own machines, using the Bash
shell. So firstly open a Bash shell (via Git Bash in Windows or Terminal
on a Mac). Then, on the command line, navigate to where you’d like the
example code to reside, and use Git to clone it. For example, to clone
the repository in our home directory (replacing
github-account-name with our own account), and change
directory to the repository contents:
Examining the Code
Let’s first take a look at the example code on GitHub, in the file
climate_analysis.py.
PYTHON
SHIFT = 3
COMMENT = '#'
climate_data = open('data/sc_climate_data_10.csv', 'r')
def FahrToCelsius(fahr):
"""COnverts fahrenehit to celsius
Args:
fahr (float): temperature in fahrenheit
Returns:
float: temperature in Celsius
"""
celsius = ((fahr - 32) * (5/9))
return celsius
def FahrToKelvin(fahr):
kelvin = FahrToCelsius(fahr) + 273.15
return kelvin
for line in climate_data:
data = line.split(',')
if data[0][0] != COMMENT:
fahr = float(data[3])
celsius = FahrToCels(fahr)
kelvin = FahrToKelvin(fahr)
print('Max temperature in Celsius', celsius, 'Kelvin', kelvin)
If you have been through previous Byte-sized RSE episodes, you may have already encountered a version of this code before!
It’s designed to perform some temperature conversions from fahrenheit to either celsius or kelvin, and the code here is for illustrative purposes. If we actually wanted to do temperature conversions, there are at least three existing Python packages we would ideally use instead that would do this for us (and much better). Similarly, this code should also use a library to handle the CSV data files, as opposed to handling them line by line itself.
There are also a number of other rather large issues (which should be emphasised is deliberate!):
- The code is quite untidy, with inconsistent spacing and commenting which makes it harder to understand.
- It contains a hardcoded file path, as opposed to having them within a separate configuration file or passed in as an argument.
- Function names are capitalised - perhaps we need to change these to be lower case, and use underscores between words - a naming style also known as snake case.
- The code is also some comments (known as docstrings) describing the
function and the script (or module) itself. For those that haven’t
encountered docstrings yet, they are special comments described in a
particular format that describe what the function or module is supposed
to do. You can see an example here in the
FahrToCelsiusfunction, where the docstring explains what the function does, its input arguments, and what it returns. - An incorrect function name
FahrToCels, which should beFahrToCelsius. This will cause it to fail if we try to run it.
Another thing to note on this repository is that we have a single main branch (also used to be called a master branch which you may see in older repositories). You’ll also notice some commits on the main branch already. One way to look at this is as a single “stream” of development. We’ve made changes to this codebase one after the other on this main branch, however, it might be that we may want to add a new software feature, or fix a bug in our code later on. This may take, maybe, more than a few commits to complete and make it work, perhaps over a matter of hours or days. Of course, as we make changes to make this feature work, the commits along the way may well break the “working” nature of our repository and after that, users getting hold of our software by cloning the repo, also get a version of the software that then isn’t working. This is also true for developers as well: for example, it’s very hard to develop a new feature for a piece of software if you don’t start with software that is already working. The problem would also become compounded if other developers become involved, perhaps as part of a new project that will develop the software. What would be really helpful would be to be able to do all these things whilst always maintaining working code in our repository. Fortunately, version control allows us to create and use separate branches in addition to the main branch, which will not interfere with our working code on the main branch. Branches created for working on a particular feature are typically called feature branches.
Create Example Issues
Before we look into branches, let’s create a few new issues on our repository, to represent some work we’re going to do in this session.
One thing that might be good to do is to tidy up our code. So let’s add issues to fix that script function naming bug, changing our function names to use snake case, and add the missing docstrings.
Let’s create our first issue about using snake case:
- Go to your new repository in GitHub in a browser, and select
Issuesat the top. You’ll notice a new page with no issues listed at present. - Select
New issue. - On the issue creation page, add something like the following:
- In the title add: Functions should use snake case naming style
- In the description add: Naming of functions currently is using capitalisation, and needs to follow snake case naming instead.
- We can also assign people to this issue (in the top right), and for
the purposes of this activity, let’s assign ourselves, so select
Assign yourself. - Select
Createto create the issue.
Adding Work for Ourselves
Repeat the process of adding issues on your GitHub repository for the following two issues in this order:
- “Add missing docstrings to function and module”
- “Script fails with undefined function name error”
We’ll refer back to these issues later!
- Using Git branches helps us keep different strands of development separated, so development in one strand doesn’t impact and confuse development in the others
- Branches created to work specifically on a particular code feature are called feature branches
- GitHub allows us to capture, describe and organise issues with our code to work on later
Content from Feature Branch Workflow
Last updated on 2025-10-27 | Edit this page
Overview
Questions
- How do I use Git to create and work on a feature branch?
- How do I push my local branch changes to GitHub?
Objectives
- Create and use new a feature branch in our repository to work on an issue
- Fix issue and commit changes to the feature branch
- Push the new branch and its commits to GitHub
Create new feature branch to work on first issue
We’ll start by working on the missing docstring issue. For the purpose of this activity, let’s assume that the bug which causes the script to fail is being tackled by someone else.
So let’s create a feature branch, and work on adding that docstring, using that branch. But before we do, let’s take a look and see what’s already there.
Examining Existing Repository Branches
We’ve already checked out our new repository, and can see what branches it currently has by doing:
OUTPUT
* main
And we can see we have only our main branch, with an asterisk to indicate it’s the current branch we’re on.
We can also use -a to show us all branches:
OUTPUT
* main
remotes/origin/HEAD -> origin/main
remotes/origin/main
Note the other two remotes/origin branches, which are
references to the remote repository we have on GitHub. In this case, a
reference to the equivalent main branch in the remote
repository. HEAD here, as you may know, refers to the
latest commit, so this refers to the latest commit on the main branch
(which is where we are now). You can think of origin as a
stand-in for the full repository URL. Indeed, if we do the following, we
can see the full URL for origin:
OUTPUT
origin git@github.com:steve-crouch/git-example2.git (fetch)
origin git@github.com:steve-crouch/git-example2.git (push)
If we do git log, we can see only one commit so far:
OUTPUT
commit be6376bb349df0905693fdaad3a016273de2bdeb (HEAD -> main, origin/main, origin/HEAD)
Author: Steve Crouch <s.crouch@software.ac.uk>
Date: Tue Apr 8 14:47:05 2025 +0100
Initial commit
Creating a new Branch
So, in order to get started on our docstring work, let’s tell git to create a new branch.
When we name the branch, it’s considered good practice to include the issue number (if there is one), and perhaps something useful about the issue, in the name of the feature branch. This makes it easier to see what this branch was about:
Now if we use the following, we can see that our new branch has been created:
OUTPUT
issue-2-missing-docstrings
* main
However, note that the asterisk indicates that we are still on our
main branch, and any commits at this point will still go on this main
branch and not our new one. We can verify this by doing
git status:
OUTPUT
On branch main
Your branch is up-to-date with 'origin/main'.
nothing to commit, working tree clean
Switching to the New Branch
So what we need to do now is to switch to this new branch, which we can do via:
OUTPUT
Switched to branch 'issue-2-missing-docstrings'
Now if we do git branch again, we can see we’re on the
new branch. And if we do git status again, this verifies
we’re on this new branch.
Using git status before you do anything is a good habit.
It helps to clarify on which branch you’re working, and also any
outstanding changes you may have forgotten about.
Now, one thing that’s important to realise, is that the contents of
the new branch are at the state at which we created the branch. If we do
git log, to show us the commits, we can see they are the
same as when we first cloned the repository (i.e. from the first
commit). So any commits we do now, will be on our new feature branch and
will depart from this commit on the main branch, and be separate from
any other commits that occur on the main branch.
Work on First Issue in New Feature Branch
Now we’re on our feature branch, we can make some changes to fix this
issue. So open up the climate_analysis.py file in an editor
of your choice.
Then add the following to the FahrToKelvin function
(just below the function declaration):
PYTHON
"""Converts fahrenheit to kelvin
Args:
fahr (float): temperature in fahrenheit
Returns:
float: temperature in kelvin
"""
Then save the file.
Now we’ve done this, let’s commit this change to the repository on our new branch.
Notice we’ve added in the issue number and a short description to the commit message here. If you’ve never seen this before, this is considered good practice. We’ve created an issue describing the problem, and in the commit, we reference that issue number explicitly. Later, we’ll see GitHub will pick up on this, and in this issue, we’ll be able to see the commits associated with this issue.
Now we’ve also got a module docstring to add as well, so let’s add that. Open up our editor on this file again, and add the following to the top of the file:
Then, add and commit this change:
So again, we’re committing this change against issue number 2. Now let’s look at our new branch:
OUTPUT
commit 6bfc96e2961277b441e5f5d6d924c4c4d4ec6a68 (HEAD -> issue-2-missing-docstrings)
Author: Steve Crouch <s.crouch@software.ac.uk>
Date: Tue Apr 8 15:40:47 2025 +0100
#2 Add missing module docstring
commit 20ea697db6b122aae759634892f9dd17e6497345
Author: Steve Crouch <s.crouch@software.ac.uk>
Date: Tue Apr 8 15:29:37 2025 +0100
#2 Add missing function docstring
commit be6376bb349df0905693fdaad3a016273de2bdeb (origin/main, origin/HEAD, main)
Author: Steve Crouch <s.crouch@software.ac.uk>
Date: Tue Apr 8 14:47:05 2025 +0100
Initial commit
So, as we can see, on our new feature branch we now have our initial commit inherited from the main branch, and also our two new commits.
Push New Feature Branch and Commits to GitHub
Let’s push these changes to GitHub. Since this is a new branch, we need to tell GitHub where to push the new branch commits, by naming the branch on the remote repository.
If we just type git push:
OUTPUT
fatal: The current branch issue-2-missing-docstrings has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin issue-2-missing-docstrings
We get a suggestion telling us we need to do this, which is quite helpful!
OUTPUT
Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 20 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 805 bytes | 805.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), completed with 1 local object.
remote:
remote: Create a pull request for 'issue-2-missing-docstrings' on GitHub by visiting:
remote: https://github.com/steve-crouch/git-example2/pull/new/issue-2-missing-docstrings
remote:
To github.com:steve-crouch/git-example2.git
* [new branch] issue-2-missing-docstrings -> issue-2-missing-docstrings
Branch 'issue-2-missing-docstrings' set up to track remote branch 'issue-2-missing-docstrings' from 'origin'.
So here, we’re telling git to push the changes on the new branch to a
branch with the same name on the remote repository. origin
here is a shorthand that refers to the originating repository (the one
we cloned originally). You’ll notice a message suggesting we could
create a pull request to merge the changes with the main branch.
- A branch is one version of your project that can contain its own set of commits
- Feature branches enable us to develop / explore / test new code features without affecting the stable main code
- Use
git branchto create a new branch in Git - Use
git switchto change to and use another branch - Add an issue number, e.g.
#1to a Git commit message so GitHub registers those commits under that issue - Use
git push --set-upstream origin branch-nameto push the commits on a new branch to a GitHub repository
Content from Creating a Pull Request
Last updated on 2025-10-27 | Edit this page
Overview
Questions
- How can I organise a set of changes together so they can be merged later?
Objectives
- Describe what is meant by a pull request
- Describe the benefits of using pull requests
- Create a pull request using GitHub to group together and propose a set of changes to be merged to another branch
How to Merge our Changes with main?
We’ve essentially fixed the docstring issue now, so next we need to somehow merge these changes on our feature branch with the main branch.
Test Code Before you Push it!
Now before we this, ordinarily we should test the changes and ensure the code is working properly. To save time, we haven’t done that here, but that’s definitely something worth noting:
- Before pushing any changes, always manually test your code first.
- If you have any unit tests, run those too to check that your changes haven’t broken anything.
- Particularly if this was a change implementing a new feature, consider writing a new unit test to test that feature.
And we’ll do that now by using what’s known as a pull request. A pull request is a way to propose changes on a particular Git branch, and request they be merged with another branch. They’re really useful as a tool in teams, since they provide a way to check the changes before doing a merge. They allow you to see the changes to files across all the commits in the branch, and look at how these commits will change the codebase. And you can assign a number of reviewers to review the pull request, and submit a review with their thoughts on whether to accept the pull request, or make changes, and so on. Really useful! So we could create the pull request on the command line, but we’ll use the GitHub interface to do it. Which frankly, is much clearer and gives you a far better view of what’s going on.
So let’s go back to our repository on GitHub. You may see a message displayed at the top about protecting the main branch. We may come back to this later, so no need to worry about this for now.
If we select the dropdown where it says main, it gives
us a list of branches. We can see all branches by selecting that option
at the bottom. Now, we can see we have our new branch that has appeared,
which is separate from our main branch. If we select that, we can see
the state of the repository within this branch, including the new latest
commits here - on our climate-analysis.py file.
Create a Pull Request
Let’s create the pull request now, by selecting
Compare & pull request. We could also do this from the
Pull requests tab from the top menu as well, then selecting
New pull request.
Now it shows us an interface for creating the pull request: -
Importantly, at the top, it shows us which branch will be merged with
which branch, with the source (or comparison) branch on the right, and
the destination branch on the left. This should be our new branch for
compare:, and main for base:. -
It tells us we are “able to merge” - and in this case, there are no
conflicts to worry about, which is really useful to know. So what if
there are conflicts? This is something we’ll look at later. - Below
this, it also shows us the commits associated with this branch as well
as the sum of changes to the files by these commits.
In the title, we’ll rename the PR to reference issue 2 directly,
changing it to Fixes #2 - missing docstrings. We could add
more contextual information in the description if needed. We could also
assign others as reviewers, as we did in the previous session on code
review. But for simplicity, we’ll leave those for now. But we will
assign the pull request (or PR for short) to ourselves, since it’s a
good idea to assign responsibility where we can. So let’s create the
pull request by selecting the button.
Now we get another screen describing the new PR itself. If we’d assigned any reviewers, we now wait for their reviews of this pull request. At this point, we could assume we’ve just done that, and the PR has been approved and is ready to merge.
By contributing work in PRs, and having reviews of PRs, it’s not just a number of people making changes in isolation. In collaborations around software, it’s very important to increase the flow of information between people making changes in case there are any new potential issues that are introduced. And PRs give us that discipline - an opportunity really - to make sure that the changes we are making are well considered. This then becomes part of the overall cycle of development: we write code, we have it reviewed, it gets merged. But also, we help with reviewing other code too.
Coming back to the interface, it now tells us we can merge this branch automatically, and also the list of commits involved. Interestingly, even though we have created this PR to do a merge, we could continue developing our code on this new branch indefinitely if we wanted. We could make and push new commits to this branch, which would show up here, and we then merge at a future date. This may be particularly useful if we need to have a longer discussion about the PR as it is developing. The discussion would be captured in the comments for the PR, and when ready, we then merge the PR.
How Long should PRs be Open?
Which raises the question, of how long should PRs be open, or
branches for that matter? To some degree, this depends on the nature of
the changes being made But branches in Git are designed, and should be
wherever possible, short-lived and deleted when no longer required. The
longer a branch is open, the more potential changes could be made to the
main branch. Then when it comes time to merge the branch, we may get a
lot of conflicts we need to manage. So generally, it’s a good idea to
keep your branches open for a day or two, a few days maximum, before
creating a PR and doing a merge if you can. Note that we can also see
this PR, as well as any others, by selecting the
Pull request tab.
- Always test code before you push changes to a remote repository
- Pull requests give us the opportunity to properly consider and review logical sets of changes to our codebase before they are merged
- GitHub gives us powerful tools to create and manage pull requests
- Where possible, keep Git branches short lived and merge them as soon as is convenient, to avoid increasing disparities between the feature branch and main branch
Content from Merging a Pull Request
Last updated on 2025-10-28 | Edit this page
Overview
Questions
- How do I merge changes proposed within a pull request with the main branch?
- What should I do with a branch that has been merged and is no longer required?
Objectives
- Use GitHub to approve and merge a pull request
- Delete a branch that has been merged
- View commits associated with a particular GitHub issue
- List the benefits of using a feature branch approach
How to Merge the Pull Request?
You’ll notice there’s a subtle dropdown on the
Merge pull request button, which presents options for how
to perform the merge.
You may remember from the introduction about doing a “rebase and
merge” as opposed to just doing a merge commit, since it leads to a
cleaner repository history. For example, if we did a normal merge here,
we’d end up with our two new commits and a merge commit on the
main branch. But if we do a rebase and then merge, our two
commits are essentially just added to the top of the main
branch. Let’s use this method, by selecting the third option in the
dropdown: Rebase and merge.
Note that if there had been a conflict with any commits on the main
branch, we very likely wouldn’t have been able to merge using this
method. Which in itself is a good question: even if we’d done a straight
commit directly to the main branch, what would happen if
there was a conflict? If we have time, we’ll look at this later
The Golden Rule of Rebasing
Note that you can also do rebasing with branches on the command line. But a word of warning: when doing this, be sure you know what will happen.
Rebasing in this way rewrites the repository’s history, and therefore, with rebasing, there is a GOLDEN RULE which states that you should only rebase with a local branch, never a public (shared) branch you suspect is being used by others. When rebasing, you’re re-writing the history of commits, so if someone else has the repository on their own machine and has worked on a particular branch, if you rebase on that branch, the history will have changed, and they will run into difficulties when pushing their changes due to the rewritten history. It can get quite messy, so if in doubt, do a standard merge!
Merge the Pull Request
So now let’s go ahead and select Rebase pull request. We
can add more information here if needed - but let’s
Confirm rebase and merge. Note that it says that the merge
was done successfully, and suggests we can delete the branch.
We said earlier that branches in Git should be short lived where possible, and keeping branches hanging around may cause confusion. So let’s delete it now. Now if we go the main branch on the main repository page in GitHub, we can see that the changes have been merged. And if we look at “commits”, we can see the commits we made on our feature branch have been added to the main branch.
See Commits on Issues
Now, remember those commit messages with the issue numbers in them? If we go to our issues, we can see them with the commits associated with those issues, which are listed in chronological order. This is really handy when checking on issue progress. Plus, it means the reason behind each commit is now traceable back to the originating issue. So why are there two sets of commits, when we only made one? That’s because we first made two commits to the branch, and then, using a rebase method, we applied our commits to the main branch.
Summary
So what are the benefits so far?
- By using different feature branches, as opposed to just committing directly to the main branch, we’ve isolated the “churn” of developing a feature from the main branch. This makes the work on any single branch easier to understand as a thread of work.
- It gives us the opportunity to abandon a branch entirely, with no need to manually change things back. In such a case, all we need to do is delete the branch.
- From a single developer’s perspective, we are also effectively isolated from the changes being made on other feature branches. So when a number of changes are being made, we still (hopefully!) only have to worry about our own changes.
- It gives us a process that helps us maintain a working version of
the code on
mainfor our users (which may very well include ourselves!), as long as we ensure that work on other branches is properly tested and works as expected before we merge back to themainbranch. - It also gives us a mechanism - via pull requests - to have others review our code before changes are introduced into the codebase.
So what we’ve shown is one way to use feature branch workflow, By
using feature branches directly off the main branch, and merging to
main when these changes are ready. We’ve chosen this way
for the training, since it’s more straightforward to teach in a
practical activity, but there are other “branching strategies” you can
use. Another way is to use a long-lived branch off of main, called
usually something like dev or develop:
- This
devbranch represents a general branch of development. - Feature branches are created off of the
devbranch instead ofmain, and then merged back to thedevbranch. - Later, when a release of the software is due, or at an appropriate
point after the software has been tested, the
devbranch is merged with the main branch.
This approach gives development greater distance from the main
branch, and it means you can merge and test all changes together on the
dev branch before you merge with the main
branch, to ensure it all works together first. However, it also means
when it comes to merging back to main, it can be more
difficult since the dev branch could have built up a
considerable number of changes that need to be merged. In either case,
the key is to make sure that code is tested and checked with the right
people in your team before you merge to main.
- Choose the branch merging method that is right for the situation
- If you use a rebasing merging strategy, remember the Golden Rule: only rebase with a local branch, never a public (shared) branch you suspect is being used by others
- Commits related to a particular issue (and referred to in its commit message) are viewable under that issue
Content from Resolving Merge Conflicts
Last updated on 2025-10-27 | Edit this page
Overview
Questions
- How should I manage conflicts when merging branches?
- How does GitHub help me manage merge conflicts with a pull request?
Objectives
- Locate points of merge conflict within a pull request using GitHub
- Resolve a branch merge conflict from a pull request
Work on Another Issue
Now we still have two remaining issues we can look at. Interestingly, both of them require changes that can cause a conflict when merging, so let’s look at those now.
First, let’s go to the main branch and pull the
repository changes on this branch to our local repository. Generally,
it’s good practice to use git pull to synchronise your
local repo with the GitHub one before doing any further work.
So now, again, we have those two commits on main as we would expect. Let’s create a feature branch to fix our snake-case issue.
So now, edit the climate_analysis.py file, and change
functions to use a snake case style, e.g. change
FahrToCelsius to fahr_to_celsius. Remember to
also change the one in the fahr_to_kelvin function as
well.
Note we’ve changed the call to fahrtocelsius near the
bottom. let’s commit this to our new feature branch:
Now we can commit as before
Introducing a Conflict
At this point, we could follow this advice and merge this branch’s
work into the main branch, which would be very neat and
tidy. But life is rarely like that: what happens when someone else
commits their changes in some way to the main branch? Where does that
leave us when we come to merge?
You may recall we created an issue for fixing the function call to
the FahrToCelsius function, where the call referenced the
function incorrectly. Let’s assume that a colleague has made these
changes, and updated the main branch. Let’s pretend we’re
our colleague, and we’re making this fix to the main branch. First,
let’s switch to the main branch:
Now as we can see, this main branch is completely unaware of the
commits in our new feature branch, and is at the initial state of the
repository. Let’s make the fix. Now we could (and should) create a
feature branch here, make and commit the change, then merge with the
main branch. But for expediency, we’ll commit directly to the main
branch, and assume they did it the right way. Edit the
climate_analysis.py file, and update the
FahrToCels function call to FahrToCelsius, and
save the changes.
BASH
git status
git add climate_analysis.py
git commit -m "#3 Fix incorrect function call"
git log
git push
Now - we have this extra commit on main, which we can see if we do:
Resolving a Merge Conflict
Now let’s see what happens when we create a pull request on our feature branch, as before, and try to merge. Again, let’s go to GitHub and then:
- Go to
Pull requests, and selectNew pull request. - Select the new feature branch
issue-1-use-snake-case. - Select this new branch in
compare:, and ensure thatbase:saysmain.
Note that now it says we can’t automatically perform this merge.
Essentially, it’s compared the source and destination branches, and
determined that there is a conflict, since there are different edits on
the same line for commits in the main and feature branch.
But we’ll go ahead and create the PR anyway:
- Select
Create pull request. - For the title, add “Fixes #1 - use snake case naming”.
- Assign yourself to the issue.
- Select
Create pull request.
We should now see that “This branch has conflicts that must be
resolved”. And in this case, there is only one conflicting file -
climate_analysis..py, but there could be more than one. Now
we can attempt to resolve the conflict by selecting
Resolve conflicts.
The GitHub interface is really useful here. It tells you which files
have the conflicts on the left (only climate_analysis.py in
this case), and where in each file the conflicts are. So let’s fix the
conflict. Near the bottom, we can see that our snake case naming of that
function call conflicts with the fix to it, and this has caused a
conflict.
Now importantly we have to decide how to resolve the conflict.
Fortunately, our fix for the snake_case issue resolves this issue as
well, since we’re calling the function correctly, which makes the other
fix redundant. So let’s remove the other fix, by editing out the chevron
and equals parts, and the fix we don’t want. We then select
Mark as resolved, then Commit merge. Now
unfortunately, due to the conflict commit, we can no longer rebase and
merge. So select the option to create a Merge commit, then
select Merge pull request, and Confirm merge.
And as before, delete the feature branch which is no longer needed.
Commits Highlighted in Issues
If we go to the repository’s list of commits now in the
main branch, we see that we have a “merge branch main into
issue-1-use-snake-case” commit which resolves the conflict (which
occurred on the feature branch) and also a merge pull request commit,
for when we merged the feature branch with main.
- GitHub’s interface helps us identify where conflicts exist for a pull request
- Resolving merge conflicts on a per-conflict basis is achievable from within GitHub