Git and GitFlow 101

This guide was written to lay out the basics of Git collaboration for some software projects to be done collaboratively in the CIRS lab.

To refresh your knowledge on Git usage and functionality, you can review this guide.

This guide takes inspiration from A successful Git branching model along with Conventional Commits and is also loosely based on the Git flow that we used while I was at Levita Magnetics. The one important distinction, is the use of git rebase before merging a feature branch into a release or working branch, to avoid polluting the history of the repo. This is entirely a preference of the author and open to debate.

Why?

Maintaining a standard Git flow allows for better control of the current software version, tracking development progress, seeing what each team member is working on, keeping code more organized, and improving control over integration and validation of deliverables. It also allows for other tools to be run with better results, such as git bisect or different kinds of automated tests.

How?

The workflow should mainly depend on two branches:

Branching and merging

Creating a branch off another one is very simple, to create a new branch and move to it, run git checkout -b <branch_name>.

Once you've done some changes, use git add <file1> <file2> to stage your changes. If you want to add all changes and untracked files under the current directory, you can use git add . or if you want the same but for the whole repo, (carefully) use git add *.

Then to commit those changes to the HEAD of your local branch, run git commit -m 'commit message'. To update the remote repository, run git push.

Once you are ready to open a pull request you should squash all your commits and then rebase your branch, to do this, first make sure your branch and any others are up to date by running git fetch --all --prune, then to squash your changes w.r.t. merge-base, run git rebase -i $(get merge-base HEAD @{u}), where @{u} should automatically pick up the name of the upstream branch. Then, in the text editor, for all commits except the first, replace pick with squash or s, after that is done, you can also amend the commit message with reword or remove a commit with drop. In this step, you might be prompted to solve any conflicts there might be.

After you are done, save, exit and then run git push --force. It is very important that the --force flag is given any time you push after a rebase, as we are rewriting the branch history and we want to update it in the remote too. You can now go to your branch in GitLab or GitHub and create the pull request.

While the PR is opened, any changes to the branch by yourself or other developers, should be done using git merge, to preserve the history.

Once the pull request is approved, if there were any intermediate commits, squash and rebase again, and then merge them into the upstream with git merge <upstream>.

There are three types of branches apart from develop and main:

  1. Feature branches
  2. Hotfix branches
  3. Release branches

Feature branches

These branches are used to develop repository features. Once this feature has been completed, the branch must be merged to origin/develop through a Pull Request.

They are branches from develop or another feature branch. They must merge to develop.

They don't have a particular naming convention*, but names should be descriptive and use only hyphens, for example, holonomic-control, motor-driver or pathplanning-task.

Once the last push to origin has been made, a PR must be opened from BitBucket/GitHub and at least 2 people must be marked as reviewers, who are not the author of the branch. The PR should not be merged unless there's approval from at least one reviewer. Once the PR is accepted and merged, the associated branch should be deleted from origin.

* If the repo were to be connected to an issue tracking system, it could require you to write the number of the ticket as part of the branch name so that it can track the branch.

Release branches

These branches are used to test and validate all features created up to that moment, in preparation for a field test.

They are branches from develop, and they must merge to origin/develop AND to origin/main. Release branches can include new changes to the code, so it is important that they are then merged back to develop, as to not mess up the commit history.

The naming convention is release-*. These branches could also be tagged so that a version pipeline can run over them.

Hotfix branches

These branches are used in case a critical failure is detected during a field test.

They are branches from main. They must merge to origin/develop AND to origin/main.

The naming convention is hotfix-*.

Commits

The naming convention for commits should be as follows:

These commits should be completed as follows:

<type>[scope]: <description>

[optional body]

Where type is one of the previously mentioned types. scope broadly indicates what has been changed, description can provide more details about what has been changed and body any other relevant information worth mentioning.

Cheat sheet

Here are some common commands you might want to use:

git switch <branchname> - move between branches.

git status - see files which are not staged or committed, or what their status is.

git diff | <branchname> - by itself, shows the diff of files changed but uncommitted or unstaged. If a branchname is given, it will show the difference w.r.t. its local tip.

git commit -amend - edit the commit message.

git reset --hard HEAD~1 - undo the last commit or merge and discard changes.

git reset --soft HEAD~1 - undo the last commit or merge and keep changes.

git reset --hard HEAD, then git pull - remove all uncommitted changes and then pull remote tip into the current branch.

git log - lists the commit history.

git fetch -p - sync the branch list.

bashrc settings

If you want your bash shell to show the current branch you're at, add the following in your .bashrc file:

parse_git_branch() {
     git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
}
export PS1="\[\033[01;32m\]\u@\h \[\033[34m\]\w\[\033[33m\]\$(parse_git_branch)\[\033[00m\] $ "