Introduction to Git
Git is a software that allows joint software development, by providing ways of
keeping track of changes to all files in a shared folder. The folder is called
a repository in Git and it can either be shared between computers in a local
network, or over the internet. It was originally developed by Linus Torvalds for
Linux development. There are other similar systems such as for example
Mercurial, but Git has lately been the most popular option. Git is commonly used
directly from the terminal by using the git
command. First of all, you need
to make sure to install the software on your system. To do so, open a terminal
and use the following commands:
Note! If you are using OpenDLV Desktop, this is already included.
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install git
This will install the needed command line tools on the computer. Before Git can be used it also needs some settings. In the terminal, run the following commands to set your name and email address (replace the examples with your details) so that your later code changes will be associated with you as a person:
git config --global user.email "your.email@example.com"
git config --global user.name "Your Name"
There are also graphical interfaces, both for local and remote use, and especially well-known are the two web-based interfaces GitHub and GitLab which act as remote repository servers. It is especially convenient to use Git with a remote server as it allows easy collaboration between several software developers. Therefore, GitLab will be used in this tutorial and it is assumed that you have a personal account at a GitLab instance, such as gitlab.com. If not, go ahead and create an account if you want to continue with this tutorial.
Tip! It is possible to host a private GitLab instance on your own server. Universities and companies might provide such environments for student and employees.
Create a new repository for the code
When logged in at your GitLab account, the first is to create a new code
repository where the prime checker can be added. This is done by clicking the
Menu button, then Projects, and Create new project. This time, choose
Blank project. Then start by typing a name for the project using the default
OpenDLV naming scheme, in this case opendlv-logic-primechecker
. Under
Project URL you should select your own username in order to create a private
repository. The Visibility level should be Private, and you can go ahead
and select Initialize repository with a README, before clicking
Create project.
When done, you will be redirected to the repository file view, where a single
file README.md
is present. Next, it is time to use the Git software on the
local computer to download, or clone, the just created remote repository. To
do so, run the following commands, where you replace USERNAME with your own
user name:
cd
git clone https://gitlab.com/USERNAME/opendlv-logic-primechecker.git
The first command, cd
without a destination, brings the terminal to the user
folder (top folder), and the second connects to the remote server to clone the
latest repository state.
Tip! The link could also have been copied from the Clone button on the repository page.To paste in a terminal, use Ctrl+Shift+c
.
Now that the project is cloned to the local computer, proceed by changing directory into the newly created folder with the following:
cd opendlv-logic-primechecker
Inside the folder, try the command ls
to see the files present, and currently
the only file should be the README.md
file as seen on the online page. The
first thing to do is to edit the README.md
file to give some more useful
information about the microservice. Go ahead and edit the file
(gedit README.md
) and change it into:
# opendlv-logic-primechecker
A highly useful microservice.
Save and close the file. The changes that you made are currently only stored locally, and when using a remote repository it is important that any changes are uploaded and shared. To do so, the change needs to be included in a so called commit corresponding to a change (delta) to the content of the repository. To create a commit for our change and to upload it to the remote server, run the following commands:
git status
git add README.md
git status
git commit -m "Improved documentation"
git push
The first command shows the changes since the latest recorded commit, here only
the change to the file README.md
. The next command registers the change as
part of a new potential commit. The third command shows that the file was now
successfully added. The forth command creates the actual commit, with a
descriptive commit message that will be stored in the repository commit logs.
Even after the commit, the change is still only local and could have been done
without any Internet access. In order to finally publish the change the fifth
command is used to push the commit to the remote repository. If another user
would now run the command git pull
, your latest commit would be downloaded
to their local version of the repository. The two git status
commands may be
omitted, but they are usually carried out to make sure that all details about
the new commit are correct.
Now, if you go back to the repository page at GitLab you will see that the front
page changed, now showing our content of the README.md
file. Remember, at any
time, another developer that you work with (if you share the repository) can
push their own commit deltas to the repository and by that change the content
of the files. Therefore, make it a habit to often run the following command
from your local repository folder:
git pull
This will instruct Git to connect to the repository located at the remote repository in order to check for any new updates. By doing this often, and by pushing your own changes often, the team may avoid many file content conflicts (where team members change content on the same rows in the same file, at the same time).
Populating the repository with code
It is now time to populate the repository with the code from the prime checker microservice. Copy the first file using the following commands:
cd
cp data/mytest/CMakeLists.txt opendlv-logic-primechecker
The first command takes the terminal back to the user home folder, and the
second (cp
) is used to copy a file into a directory
(opendlv-logic-primechecker
; the local code repository).
Tip! When typing a filename, or a command, in the terminal, the Tab key can be used to autocomplete the input. The terminal is smart enough to know what you are intending to type based on the possible options. If there are several options available, you can even press the Tab key twice to get a list of options. Using this terminal feature will greatly increase your work speed.
Now continue by copying the rest of the files, this time in one go, according to:
cp data/mytest/helloworld.cpp data/mytest/prime-checker.hpp \
data/mytest/prime-checker.cpp data/mytest/catch.hpp \
data/mytest/test-prime-checker.cpp data/mytest/cluon-complete.hpp \
data/mytest/messages.odvd opendlv-logic-primechecker
Note! The \
symbol simply means break line in the terminal. It can very well be omitted and everything can be written on a single line.
Tip! When moving many files, the file browser might of course also be a very convenient tool, so do not be shy using it. However, an expert terminal user is often quicker in only the terminal compared to a user that switches between different tools.
When all files are copied, the next step is to compile and test the program once again, to check that all files are successfully copied. In order to do so, please run the following commands:
cd opendlv-logic-primechecker
mkdir build
cd build
cmake ..
make
make test
This should give you the message that everything compiled properly, and that the automated testing was successful. The next step is to prepare the commit so that the files can be tracked in the repository. Like before, start by checking the status of the repository with the following command:
cd ..
git status
Looking carefully, one can spot a problem here. It turns out that the build
folder is also listed as an untracked file, ready to be added to the repository.
Since the build folder is generated from the source files, can be generated
again at any time, and it to some degree is specific to the current computer
(not general) it should never be part of the repository. One option is of course
to avoid ever adding it using git add
, but there is an even more safe way to
make sure the folder is never added by mistake. The build
folder can be added
to a file called .gitignore
inside the repository folder. Create such file by
using gedit .gitignore
and add the following:
Note! The dot (.
) in front of the filename means that the file will be considered as hidden in a Linux system. It will normally not be shown when listing files with thels
command, except if the-a
flag is given (ls -a
).
build
This simply means that a file or folder with the name build
inside the
repository will always be ignored by Git. Now, proceed to prepare the commit
by using the following commands (and note that build
is not considered
anymore):
git status
git add -A
git status
In the second command, the flag -A
is used this time. This tells Git to add
all untracked files to the repository, which is useful when there are many files
to add. However, it is important to then first check the list of untracked files
using the git status
command to avoid surprises. If everything looks good,
then go ahead and crate the commit and push it to the remote repository by
using:
git commit -m "Initial version"
git push
Now go back to GitLab and refresh the site to check that all files are present.
Modifying code in branches
The initial version of the prime checker is now safely stored in the code
repository as a formal commit. It may be ensuring to know that one can always
go back to this version if needed in the future. Therefore, further changes to
the code can now safely be made. However, when working with changes in the code
it makes sense to do so inside individual so called branches of the
repository. A branch is a set of commits that can develop in parallel to commits
in other branches. They are useful in the case when developers are preparing an
alternative version of the program, at the same time as the previous version
should stay unaffected in the repository until the new version is properly
developed and tested. Then, the new branch is typically merged into the
original branch, adding all commits into a single sequence of changes (deltas).
The original branch in a Git repository is most often called main
, and any
other branch (often called feature branches) is typically named to indicate
the code change that they are aiming for.
Now, create a new branch using the following command:
git checkout -b feature.autochecker
git status
This checks out a new branch called feature.autochecker
from the repository.
The git status
command now shows that the repository currently points at the
new branch. One can easily move between branches, if there are no unsaved
changes in the current branch, as follows:
git checkout main
git status
git checkout feature.autochecker
git status
Note that each branch may contain different content, even though they shared the
same content when the feature branch was initially created. Next, changes can
safely be made to the code without affecting the content of the main
branch.
The change should, as indicated by the name, be to improve the microservice to
not rely on user input. Typically, a microservice intended to run inside a
cyber-physical system should not rely on manual input. Open the helloworld.cpp
file and change it into the following:
#include <chrono>
#include <iostream>
#include <random>
#include "cluon-complete.hpp"
#include "prime-checker.hpp"
#include "messages.hpp"
int32_t main(int32_t, char **) {
PrimeChecker pc;
std::cout << "Hello world = " << pc.isPrime(43) << std::endl;
cluon::OD4Session od4(111,
[](cluon::data::Envelope &&envelope) noexcept {
if (envelope.dataType() == 2001) {
MyTestMessage1 receivedMsg = cluon::extractMessage<MyTestMessage1>(std::move(envelope));
PrimeChecker pc;
std::cout << receivedMsg.myValue() << " is"
<< (pc.isPrime(receivedMsg.myValue()) ? " " : " not ")
<< "a prime." << std::endl;
}
});
uint32_t const maxnum{1000};
std::random_device r;
std::mt19937 rg(r());
std::uniform_int_distribution<uint16_t> dist(1, maxnum);
while (od4.isRunning()) {
uint16_t value = dist(rg);
MyTestMessage1 msg;
msg.myValue(value);
od4.send(msg);
std::this_thread::sleep_for(std::chrono::duration<double>(3.0));
}
return 0;
}
The first change is the inclusion of the standard random
library that is used
for generating random number. Then the random number generator is set up using
the random_device
, the random-number engine mt19937
, and the uniform
distribution uniform_int_distribution
. In the latter, the type of integers
are selected by adding <uint16_t>
in the declaration. Also note that the
distribution is limited to a range from 0
to the value of the constant
maxnum
that was defined using uint32_t const maxnum{1000}
. Any maximum
number can of course be picked (as long as it fits inside a 16 bit value), so
please go ahead and change it to any number you like. Finally, the request for
input using std::cin
was removed and the random number was picked using
dist(rg)
. In the end of the while
loop, the sleep was re-introduced so that
the program can run in a controlled way. Now compile your program by using the
now very familiar commands:
cd build
make
From two different terminals, go to the build
folder and run:
./helloworld
The programs will directly start by generating numbers and passing them on to be checked via UDP multicast, inside Libcluon messages. Each program will listen for incoming numbers to check, and print the result in the terminal.
When seeing that the results of the change is what was originally intended it is time to save the changes to the feature branch as a commit. Stop the programs and run the following commands:
git status
git add -u
git status
There is one new argument here, the -u
flag. It is almost like the -A
flag,
but instead of adding all new files it only adds any known files where changes
were detected. Now, go ahead and commit and push the changes:
git commit -m "Removed the need for manual input"
git push
During the push, you were now presented with a message stating that the branch
did not yet exist on the remote server. This is true, as it was so far only
created and known locally (from the commands seen so far, only pull
and push
actually connects to the remote repository). It is generally a good idea to
push all feature branches so that they get known on the remote repository, so
follow the recommendation and run the following command:
git push --set-upstream origin feature.autochecker
Please also go to the online repository page and check that there are now two
branches, main
and feature.autochecker
where the content of the files are
different. Now, it is time to merge the branches to replace the old version in
main
. This is done with the following commands:
git checkout main
git merge feature.autochecker
git push
The first command moves back into the main
branch, and from there the changes
from feature.autochecker
is merged in to the code. The merge automatically
creates a new commit to register the change. Finally, the new commit is pushed
to the remote repository and the change is made permanent.