OpenDLV
Introduction

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 the ls 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.