15 minute read

One of the greatest advantages of open-source is the ability to view, modify, and share the source code. This means diagnosing and fixing problems, or adding new features or documentation. In order to contribute these fixes/improvements to the project’s developers, you need to send them back your changes in an adequate manner. This can be done by creating and sending a patch file, or by doing a pull request. Scroll down for more details about how to do that.

Git For the version control system we use Git, a free and open-source application that automates the process of keeping an annotated history of the project, allowing reversion of code changes, easy branching and merging, sharing, and change tracking. The GNSS-SDR’s GitHub repository hosts all the data files needed for version control, being the central “truth” repository resource of GNSS-SDR’s source code. Such a reference repository is usually referred to as upstream.

A repository is simply a place where the history of the work is stored. The distributed nature of Git allows users to clone this repository, that is, to obtain an exact replica of the original repository at their own local hard drive. Then, users can build and use the software, modify the source code, share their work, and contribute back to GNSS-SDR. Git is a fantastic but complex source code management system - it has a steep learning curve, but it worth it. A good reference is the freely available Git Pro book, but there are plenty of good Git tutorials out there. If you have never used it, start from the Git Basics or this free online course.

Git branches in GNSS-SDR

Branching in Git is one of its many great features. A branch represents an independent line of development or, more accurately, a directed acyclic graph of development. If you have used other version control systems, it is probably helpful to forget most of what you think about branches - in fact, it may be more helpful to think of them practically as contexts since that is how you will most often be using them. When you checkout different branches, you change the contexts that you are working in, and you can quickly context-switch back and forth between several different branches. Every time you switch to a new branch, you will actually see different files in your local folder.

In GNSS-SDR, the source code comes in two flavors: “main” and “next” (these are in fact the names of the Git branches). While “main” is the reference, most stable branch that contains the latest release, “next” is where all the development is happening, the most bleeding-edge code out there. Once you have cloned the main repository, you can easily switch between these two branches (or any other created by you or fetched from other users) by going to your git-cloned repository and issuing the git checkout command with the name of the desired branch name, like this:

$ git checkout main   # now you are in the main branch
$ git checkout next   # now you are in the next branch

If you do not know in which branch you are, pay attention to the first line of this command’s output:

$ git status

Next figure shows the Git branching model followed in GNSS-SDR’s upstream repository:

GNSS-SDR's Git branching model A graphical representation of GNSS-SDR’s Git branching model, where vertical lines represent branches, colored dots represent commits, from older (top) to newer (bottom), and arrows across branches represent merges. Feature branches can be either in the upstream repository or in any other fork. This model is a modified version from V. Driessen.

Note: “master” to “main” transition. In June 2020, GitHub announced it would start to remove references to the term “master” from GitHub services and replacing it with a more neutral term like “main,” a change already adopted by many other communities (see some media reports here and here). Moving to use “main” felt an appropriate way to honor our own code of conduct, so we implemented this change starting on GNSS-SDR v0.0.15. If you forked and cloned this repository before, you can update your own repository by doing:

  1. Fetch from the upstream repository:
    $ git fetch https://github.com/gnss-sdr/gnss-sdr/
    
  2. Checkout the newly fetched main branch and change the HEAD pointer to it:
    $ git checkout main
    $ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main
    

    If you only cloned the repository from upstream, then you are good to go.

  3. If you forked from upstream and then cloned your own repo, then there is some more extra work to do. Push the local main branch to your own repo:
    git push -u origin main
    
  4. Change the default branch name to main on your forked GitHub repository. This is the only step that requires you to leave the Terminal and navigate in your browser to your GitHub repository https://github.com/YOUR_USERNAME/gnss-sdr/. From there, click “Settings” “Branches” on the left rail, and then change the default branch to main.
  5. Delete your master branch:
    $ git push origin --delete master
    

    Now main is your new default branch. Please remember to branch-off from next if you are planning to make changes and to submit a Pull Request.

How to work with the source code

If you still not have one, please sign up on GitHub and create your personal account (it’s free).

The first thing you need to do in order to submit your changes to the GNSS-SDR repository is to fork from it. This can be done either by following this link, by clicking in the following button:

Fork GNSS-SDR from GitHub

or by browsing to https://github.com/gnss-sdr/gnss-sdr and clicking in the Fork button at the upper-right corner of the screen: Fork
button

Once you have forked the repository, open a terminal, go to your favorite working folder, and type:

$ git clone https://github.com/YOUR_USERNAME/gnss-sdr

where YOUR_USERNAME is your GitHub user name. Now, you have a local copy of your fork of the GNSS-SDR repository into a directory called gnss-sdr. Change to that directory:

$ cd gnss-sdr

and (if you have not done so yet) configure Git with some basic information, such as your identity:

$ git config user.name "Your Name"
$ git config user.email "your@email.abc"

The email you specify should be the same one found in your GitHub email settings.

When you fork a project in order to propose changes to the original repository, you can configure Git to pull changes from the original, or upstream, repository into the local clone of your fork. If you type git remote -v in a terminal and press Enter, you will see the current configured remote repository for your fork:

$ git remote -v
origin  https://github.com/YOUR_USERNAME/gnss-sdr.git (fetch)
origin  https://github.com/YOUR_USERNAME/gnss-sdr.git (push)

We can add the original, upstream repository like this:

$ git remote add upstream https://github.com/gnss-sdr/gnss-sdr

To verify the new upstream repository you have specified for your fork, type git remote -v again. You should see the URL for your fork as origin, and the URL for the original repository as upstream:

$ git remote -v
origin    https://github.com/YOUR_USERNAME/gnss-sdr.git (fetch)
origin    https://github.com/YOUR_USERNAME/gnss-sdr.git (push)
upstream  https://github.com/gnss-sdr/gnss-sdr (fetch)
upstream  https://github.com/gnss-sdr/gnss-sdr (push)

Now, you can keep your fork synced with the upstream repository with a few Git commands:

  1. Fetch the branches and their respective commits from the upstream repository. Commits to next will be stored in a local branch, upstream/next.

    $ git fetch upstream
    
  2. Check out your fork’s local next branch:

    $ git checkout next
    
  3. Merge the changes from upstream/next into your local next branch. This brings your fork’s next branch into sync with the upstream repository, without losing your local changes:

    $ git merge upstream/next
    

Now that you are up to date, go to the next branch and create a new branch off from it:

$ git checkout next
$ git checkout -b my_feature

Whenever you want to work on something, create a branch for it. Then, do your changes, stage modified and new files, and do commits:

... (change files, compile, test) ...
$ git add file1.cc file1.h ...    # This is called file staging
$ git commit -m "adding stuff"    # Records staged files to the repository

Sign your commits!

If you plan to contribute your work back to the upstream repository in form of pull request, it is required that you sign your commits as an indication of Developer’s Certificate of Origin fulfillment. You have several ways to sign your commits:

  • The -s flag: git commit -s -m "commit message". This adds a Signed-off-by trailer by the committer at the end of the commit log message.
  • The -S flag: git commit -S -m "commit message". This will sign the commit with your GPG key (check how to generate your GPG key and how to tell Git about your signing key).
  • Preferred method: To configure your Git client to sign commits by default for a local repository (so you don’t need to add the -S flag to your commits anymore), in Git versions 2.0.0 and above, after generating your GPG key and telling Git about it, run:
    $ git config commit.gpgsign true
    

    To sign all commits by default in any local repository on your computer, run:

    $ git config --global commit.gpgsign true
    

How to do pull requests

As you edit files, Git sees them as modified, because you have changed them since your last commit. You stage these modified files and then commit all your staged changes, and the cycle repeats. The command git add puts the files into a “staging area”, an index where you get to determine what changes get shipped away in the next commit, that is, what files are going to be recorded to the repository. If you need more details about this process, check this Git tutorial on how to record changes in your repository. The next figure summarizes this workflow:

Git repository

Once you are done with your changes in your local repository, it’s time to push that changes to your GitHub repository:

$ git push origin my_feature

Then, go onto the GitHub site, visit your repository, switch to your my_feature branch, and click the Contribute Open pull request button that will do all the work for you. This can also be done by visiting a link with the form https://github.com/YOUR_USERNAME/gnss-sdr/pull/new/my_feature

Please read and then delete the welcoming message and write there the reasoning behind your proposed change.

Once a pull request is sent, interested parties can review the set of changes, discuss potential modifications, and even push follow-up commits if necessary.

How to use someone else’s branch

As a collaboration tool, Git can set up what is known as a remote to connect to other people’s repositories. Those repositories, in the Git distributed system, do not need to be on a single server but can be anywhere. You can have several of them, each of which generally is either read-only or read/write for you. Collaborating with others involves managing these remote repositories and pushing and pulling data to and from them when you need to share work.

Now, someone might be doing something interesting you care about. Say this is carlesfernandez and you want to track his work:

$ git remote add cf https://github.com/carlesfernandez/gnss-sdr.git
$ git fetch cf    # This downloads all the content from Carles' repo.
$ git branch -r   # Lists remote branches

Then you can see all your available remote branches (including those of the remote repository you just added) and their name. You can then checkout a specific remote branch by typing:

$ git checkout cf/very_cool_feature

When checking out a remote branch, you can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you are producing, you may do so (now or later) by using -b with the checkout command again:

$ git checkout -b new_branch_name

Setting up tracking branches

When you create a new local branch off from next, it diverges from your local next at that point in time:

$ git checkout next
$ git checkout --track -b my_feature

If you want to keep that new branch updated with the new changes of next, you either need to pull changes through next and then rebase your branch, or remember where your merge point was.

What we have here is my_feature tracking next, and not origin/next; in other words, it is a local branch tracking another local branch. There are times when this is useful, but what if you want to track the remote one directly instead of having to pull through a local copy? The solution is to have a tracking branch.

In Git terminology, a tracking branch is a local branch that is connected to a remote branch. When you push and pull on that branch, it automatically pushes and pulls to the remote branch that it is connected with.

Checking out a local branch from a remote branch automatically creates a tracking branch. If you are on a tracking branch and type git push, Git automatically knows which server and branch to push to. Also, running git pull while on one of these branches fetches all the remote references and then automatically merges in the corresponding remote branch.

When you clone a repository, it automatically creates a main branch that tracks origin/main. That is why git push and git pull work out of the box with no other arguments: if you are on a tracking branch and type git push, Git automatically knows which server and branch to push to. However, you can set up other tracking branches if you wish - ones that do not track branches on origin and do not track the main branch.

Say you want to work off the next branch. First, you need a copy of that in your local repository - a tracking branch:

$ git fetch --all    # This downloads all available content
$ git branch -r      # Lists remote branches

Then, create a local tracking branch called my_feature from the remote branch called origin/next:

$ git checkout --track -b my_feature origin/next

Now you have a branch my_feature which is tracking origin/next. When there is an update in the upstream repository, and do a pull, you will see it updating both next, and also my_feature.

Important: Never, ever commit (write) to a local tracking branch. Always use them as a base to branch off!

Good coding practices

  • Keep your changes referred to the latest commit of upstream/next branch. In Git terminology, this is called rebasing a branch. When rebasing, you need to specify which branch you want to rebase onto:

    $ git checkout my_feature
    $ git fetch upstream
    $ git rebase upstream/next
    

    This simply reshuffles your my_feature branch patches on top of the current next branch in the upstream remote. Rebasing is a good idea while your feature branch is still in development. Check out Scott Chacon’s Git Pro book section about rebasing to find out more details about this concept.

  • Use an integrated development environment (IDE) with Git support. Most modern C++ IDEs have nice interfaces for using Git. An open-source, well-known option is Eclipse, and we love using EGit, an Eclipse Team provider for Git.

  • Before creating the patch file, please be sure that after your modifications everything compiles and runs without problems, and clean up your work. Remove any junk lines or comments you may have left while fixing the code, and make sure you follow the recommended coding style (indentation, white spaces, naming conventions, and so on). This will make other developers’ life easier.

  • Tell us about your branch! If you have significant changes, you can simply email us (again, the best way is by mailing list, so other users can get to know about your work) and tell us about your code. All we need is the link to your remote branch.

Summary

GNSS-SDR’s reference Git repository, also referred to as upstream, is hosted online by GitHub at https://github.com/gnss-sdr/gnss-sdr.

That repository holds two development branches with infinite lifetime, main and next:

  • We consider upstream/main to be the branch where the source code of HEAD (i.e., the result of the latest change) reflects the source code of the latest stable release, plus maybe some hot fixes (e.g., building issues).
  • We consider upstream/next to be the main branch where the source code of HEAD always reflects a state with the latest delivered development changes ready for the next release. Some would call this the “integration branch”, or the “current baseline” for new developments. If you are building the software from the code source, or you want to start a new improvement, this is the branch to start with.

When the source code in the next branch reaches a stable point and is ready to be released, all of the changes are merged back into main and then tagged with a release number. Check the list of GNSS-SDR releases here.

Everyone is free to clone the upstream repository (i.e., to download an exact copy of the whole source code repository content, including all development branches and associated history) and to create an infinite number of new “feature branches”, such as fixing-a-typo-in-the-docs or adding-a-new-algorithm-for-Galileo-E1b-signal-acquisition, in which specific improvements can be implemented in a nonlinear and distributed manner. The relation of Git to the PDCA (Plan-Do-Check-Act) cycles is described in the Testing the software receiver tutorial.

Once a feature branch is ready for production, it can be merged back into upstream/next and eventually included in the next upstream/main release. The acceptance of new contributions to upstream is always supervised and validated by the Developer Team, and every single change in the whole process (including the authorship and a time tag) is registered in the repository for future reference.

In summary, the required steps for contributing to the source code are:

  1. Fork from the upstream repo.
  2. Clone your forked repo in your computer.
  3. Create a new branch from next.
  4. Work on that new branch, commit your (signed) work.
  5. Create a pull request.
  6. Once the pull request is accepted, update your next branch by pulling from the upstream next branch.
  7. Start over from step 3 for a new pull request.

Leave a comment