Using Git
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.
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:
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:
- Fetch from the upstream repository:
$ git fetch https://github.com/gnss-sdr/gnss-sdr/
- Checkout the newly fetched
main
branch and change theHEAD
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.
- 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
- 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 repositoryhttps://github.com/YOUR_USERNAME/gnss-sdr/
. From there, click “Settings” “Branches” on the left rail, and then change the default branch tomain
. - Delete your
master
branch:$ git push origin --delete master
Now
main
is your new default branch. Please remember to branch-off fromnext
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:
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:
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:
-
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
-
Check out your fork’s local
next
branch:$ git checkout next
-
Merge the changes from
upstream/next
into your localnext
branch. This brings your fork’snext
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:
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 currentnext branch
in theupstream
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:
- Fork from the upstream repo.
- Clone your forked repo in your computer.
-
Create a new branch from
next
. - Work on that new branch, commit your (signed) work.
- Create a pull request.
-
Once the pull request is accepted, update your
next
branch by pulling from the upstreamnext
branch. - Start over from step 3 for a new pull request.
Leave a comment