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.

Callout

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.

Key Points
  • 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.

  1. Once logged into GitHub in a web browser, go to https://github.com/UNIVERSE-HPC/git-example.
  2. Select Use this template, and then select Create a new repository from the dropdown menu.
  3. On the next screen, ensure your personal GitHub account is selected in the Owner field, and fill in Repository name with “git-example”.
  4. Ensure the repository is set to Public.
  5. 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:

BASH

cd
git clone https://github.com/github-account-name/git-example
cd git-example

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 FahrToCelsius function, where the docstring explains what the function does, its input arguments, and what it returns.
  • An incorrect function name FahrToCels, which should be FahrToCelsius. 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:

  1. Go to your new repository in GitHub in a browser, and select Issues at the top. You’ll notice a new page with no issues listed at present.
  2. Select New issue.
  3. 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.
  4. 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.
  5. Select Create to create the issue.
Discussion

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!

Key Points
  • 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:

BASH

git branch

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:

BASH

git branch -a

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:

BASH

git remote -v

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:

BASH

git log

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:

BASH

git branch issue-2-missing-docstrings

Now if we use the following, we can see that our new branch has been created:

BASH

git branch

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:

BASH

git switch issue-2-missing-docstrings

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.

BASH

git add climate_analysis.py
git commit -m "#2 Add missing function docstring"

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:

PYTHON

""" Performs conversions between different temperature scales."""

Then, add and commit this change:

BASH

git add climate_analysis.py
git commit -m "#2 Add missing module docstring"

So again, we’re committing this change against issue number 2. Now let’s look at our new branch:

BASH

git log

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:

BASH

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!

BASH

git push --set-upstream origin issue-2-missing-docstrings

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.

Key Points
  • 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 branch to create a new branch in Git
  • Use git switch to change to and use another branch
  • Add an issue number, e.g. #1 to a Git commit message so GitHub registers those commits under that issue
  • Use git push --set-upstream origin branch-name to 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.

Callout

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.

Callout

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.

Key Points
  • 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

Callout

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 main for 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 the main branch.
  • 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 dev branch represents a general branch of development.
  • Feature branches are created off of the dev branch instead of main, and then merged back to the dev branch.
  • Later, when a release of the software is due, or at an appropriate point after the software has been tested, the dev branch 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.

Key Points
  • 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.

BASH

git checkout main
git pull
git log

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.

BASH

git branch issue-1-use-snake-case
git checkout issue-1-use-snake-case

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:

BASH

git add climate_analysis.py
git commit -m "#1 use snake case naming"

Now we can commit as before

BASH

git push --set-upstream origin issue-1-use-snake-case

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:

BASH

git checkout main
git status
git log

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:

BASH

git log

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:

  1. Go to Pull requests, and select New pull request.
  2. Select the new feature branch issue-1-use-snake-case.
  3. Select this new branch in compare:, and ensure that base: says main.

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:

  1. Select Create pull request.
  2. For the title, add “Fixes #1 - use snake case naming”.
  3. Assign yourself to the issue.
  4. 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.

Key Points
  • 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