Skip to content

Fork based workflow

Tomas Torsvik edited this page Sep 20, 2022 · 4 revisions

Fork-based workflow - general introduction

Developers should mainly use a fork-based workflow for contributions to BLOM/iHAMOCC, as this encourages regular code review of all changes. The layout we aim for is described in this coderefienry instruction page. Contributors are strongly encouraged to read through these instructions, and note in particular the respective roles of the central repository, the fork and the local clone.

HINT: Rename your fork

 When forking a repository, it can sometimes be an advantage to 
 give your personal fork a distinct name derived from the central 
 repository, e.g. "BLOM-<github user name>", to distinguish the 
 fork from the upstream source. 
 HOWTO: In your personal BLOM fork, open the "Settings" tab, 
        and change entry in "Repository name".
 NOTE:  This will change the URL of your fork, so do this before 
        you create a local clone. 

Working with a fork (origin) and central repository (upstream)

When cloning your personal fork to your local computer, you will only be able to access your own work space. Running

$ git remote [-v, --verbose]

should produce an output

 origin	https://github.com/<personal gitHub account>/<BLOM-fork-name>.git (fetch)
 origin	https://github.com/<personal gitHub account>/<BLOM-fork-name>.git (push)

Make your local clone aware of changes in the central repository (upstream) by running

$ git remote add upstream https://github.com/NorESMhub/BLOM.git

Now you should be able to see the main repository as well

 $ git remote -v
 origin	https://github.com/<personal gitHub account>/<BLOM-fork-name>.git (fetch)
 origin	https://github.com/<personal gitHub account>/<BLOM-fork-name>.git (push)
 upstream	https://github.com/NorESMhub/BLOM.git (fetch)
 upstream	https://github.com/NorESMhub/BLOM.git (push)

Create a new feature branch, and create pull request

  • Create and checkout new_feature branch in local clone

$ git checkout -b new_feature

  • After making changes and committing to new_feature, push changes to origin (BLOM fork)

$ git push -u origin new_feature

  • The '-u' flag is only needed when pushing a new branch to origin. When the branch is present in origin, any further changes can be pushed by git push from the new_feature branch.
  • In BLOM fork, create pull request from new_feature branch to BLOM repository (master by default, or select an appropriate feature branch if it already exist).

HINT: Why not merge new_feature with master in local clone or fork?

This will cause your local/fork `master` branch to diverge from upstream.
You should aim to keep an identical history for local/fork branches that 
exist in the upstream repository, as this will allow you to do a 
"fast-forward" merge with upstream without creating a new merge commit.
If you get conflicts when doing the pull request, try to update your 
local clone repository from upstream, and apply fixes to the 
`new_feature` branch that removes the conflict.

Create a local branch that tracks an existing remote branch

Sometimes the feature branch you want to work on already exist on the remote side (origin or upstream)

  • Create a local branch that tracks changes in an existing remote branch (2 methods): keep name existing_feature locally, or change name to mybranch locally

$ git checkout --track origin/existing_feature
$ git checkout -b mybranch origin/existing_feature

  • Normally you do not wish to track upstream in this way, as you should not push directly to upstream but use pull requests. To check out an existing upstream branch without tracking

$ git checkout --no-track upstream/existing_feature

Update your local clone and fork from upstream

If you follow the advice to always commit your work in local feature branches, your master branch should remain clean and not contain commits other than those from upstream. In this case the "merge" should automatically be "-fast-forward". Otherwise, a separate "merge commit" will be created when you update your local branch.

  • Fetch all changes from upstream

$ git fetch upstream

  • Update your local branch, e.g. master, from upstream.

$ git checkout master
$ git merge upstream/master

  • Now push changes to your fork (again, assuming fork branches are clean)

$ git push origin master

HINT: Why not update the gitHub fork from upstream directly?

 In principle it is possible to pull changes from upstream to a fork in 
 gitHub, by doing a "reverse" pull request. However, this is almost always
 a bad idea. The pull request will create a separate merge commit that exist 
 only on the fork, hence the fork history will diverge from upstream, and
 you will get a message that the fork is ahead of the upstream by 1 commit.

HELP! My local clone/fork has diverged from upstream. How do I fix it?

Sometimes you may commit to the wrong branch by accident, or commit several changes to a branch that you later realize should be clean. How can you recover a clean state?

  1. Undo last commits in local clone, reset to commit with checksum SHA, but keep changes in files from deleted commits

$ git reset SHA

  1. Undo last commits in local clone, reset to commit with checksum SHA, and delete changes in files from deleted commits

$ git reset --hard SHA

  1. Local clone and fork has diverged from upstream/master in a bad way. Force branches to reset from upstream/master. Deletes local changes.

$ git fetch upstream
$ git checkout master
$ git reset --hard upstream/master
$ git push origin master --force

  1. Reset master to upstream/master, but keep your local changes in a new_feature branch.
  • First create a new_feature branch (see earlier section) from your local master branch that has diverged from upstream/master, and push this to origin/new_feature.
  • Then do a hard reset on master (see #3). You should now have master synced with upstream/master and your local changes in new_feature branching off master at their point of divergence in history.
  • (optional) Merge changes from master to new_feature, or rebase new_feature on top of master (see below)

More git reset options are discussed in this blog post about undoing git mistakes.

Merge or rebase your feature branches, and deal with conflicts

Git provides two main options for integrating changes between branches, merging and rebasing.

Update a feature branch from master by merging (the messy but safe way)

Assuming that you have just updated your local clone from upstream/master, and want to merge changes to your feature branch.

$ git checkout new_feature
$ git merge master
$ git push origin new_feature

Update a feature branch from master by remerging (the clean but dangerous way)

The git rebase command allows changing of the branch point for a branch relative to its base. This is done by reapplying a series of changes from a branch to a different base, and reset the HEAD of that branch to the result.

WARNING: git rebase changes the commit history. Do not use git rebase on a repository that is shared among multiple users!

Assuming that you have just updated your local clone from upstream/master, and want to rebase changes to your feature branch.

$ git checkout new_feature
$ git rebase master
$ git push --force origin new_feature

In this case you need to force the push to origin (BLOM fork) since you are re-writing the commit history on the remote side. Only use rebase and push --force with forks that you do not share with others, as this will break history for any other clones of the fork than the one that you have pushed from.

Resolve conflicts in local clones

It is usually best to resolve any conflicts in the local clone before pushing to origin and committing a pull request. The pull request should give feedback if the merging to upstream can be done without conflicts.

Deleting branches after merging with upstream

After a pull request has been accepted and merged, you will sometimes get the option to delete the branch used for the pull request. Deleting merged branches is safe, the commit history will be preserved, only the branch label will be removed. This may be a good option for single issue feature branches, e.g. bug fixes, but may not be appropriate in all cases.

If you decide to delete the branch, this will only affect the origin (BLOM fork), not the local clone. To update the clone, do

$ git fetch upstream
$ git checkout master
$ git merge upstream/master
$ git branch -d new_feature
$ git fetch -p
$ git push

The '-p' (prune) flag in fetch removes any remote-tracking references that no longer exist on the remote. The last git push is needed to update the master branch on origin, as this is not done as part of the pull request.

Merge the master branch into a feature branch

This can be done directly in the github web interface:

  1. Switch to the feature branch <feature>
  2. Under the "contribute" button, click the "compare" button

This opens up a new web page where you can review the changes between <feature> and master. At the top of the page is an option to merge <feature> into master, but this sequence can be reversed.

  1. Reverse the suggested pull request
  • base: master <- compare: feature
  • base: feature <- compare: master
  1. Create the pull request

Merge master into a feature branch: conflicts

When comparing branches under github, the system will check if there are potential conflicts. When no conflicts are detected, a green check mark with the text "Able to merge" will appear at the top of the comparison web page. If instead you get a conflict at this stage, you may want to do the merge locally first, resolve the conflicts, and then create a pull request from your locally merged branch.

  1. From your local clone, check out the master branch
  2. Create a new merge branch <merge> on top of master
  3. Merge the <merge> branch into the feature branch <feature>
  4. Resolve conflicts
  5. Create a pull request from <merge> to <feature> in the github repository
  6. After completing the pull request, update the local <feature> branch with changes from github, and delete the now obsolete <merge> branch.