oppy: How To’s (for developers)
These documents act as a guideline for general tasks with oppy.
- Documentation fo the oppy module
- Unittests for the oppy module
- How to use the Results and Options classes for new methods in oppy
- How you should use git while working with oppy
Documentation for the oppy module
We will split this HowTo into two main parts:
- Writing the docstrings inside the oppy module.
- Compiling the docs and creating
.html
-files using thesphinx
API.
Writing the documentation
In this part, we will give some basic rules and an easy guideline on how to document your class,
method, etc. inside the oppy
module.
Docstrings in general and how to create them
Docstrings are released within a webpage for any good module. They provide the interface between the developers and the user. They do not only document the module itself, they are also key if a new user wants to learn how to use the module.
If you have implemented a function my_function.py
and you want to create a docstring for it, spyder provides
and easy quick start. Just move your cursor in the line after def my_function():
and type """
, spyder will
ask you to automatically create a first version of the docstring. Spyder will basically add every input
and output variable inside the docstring and you just have to add a description and the type of the variables.
If you use PyCharm instead of Spyder you can do as follows. Place the caret somewhere within the function you want to document. Press Alt + Enter to show the available intention actions and choose Insert documentation string stub.
Pleas check the documentation if you use another IDE.
The numpy guideline and an example docstring
In the settings of your IDE (e.g. Spyder, PyCharm, etc) you can also set the docstring guide, you want to use.
Important: We are using the numpy docstrings style.
On this website you can also find the most important rules and an easy guideline on what and how to document. Please
read it carefully and follow the instructions. Here you can also find a
minimal docstring example. We copied the file
example.py
into this document:
"""Docstring for the example.py module.
Modules names should have short, all-lowercase names. The module name may
have underscores if this improves readability.
Every module should have a docstring at the very top of the file. The
module's docstring may extend over multiple lines. If your docstring does
extend over multiple lines, the closing three quotation marks must be on
a line by itself, preferably preceded by a blank line.
"""
from __future__ import division, absolute_import, print_function
import os # standard library imports first
# Do NOT import using *, e.g. from numpy import *
#
# Import the module using
#
# import numpy
#
# instead or import individual functions as needed, e.g
#
# from numpy import array, zeros
#
# If you prefer the use of abbreviated module names, we suggest the
# convention used by NumPy itself::
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# These abbreviated names are not to be used in docstrings; users must
# be able to paste and execute docstrings after importing only the
# numpy module itself, unabbreviated.
def foo(var1, var2, *args, long_var_name='hi', **kwargs):
r"""Summarize the function in one line.
Several sentences providing an extended description. Refer to
variables using back-ticks, e.g. `var`.
Parameters
----------
var1 : array_like
Array_like means all those objects -- lists, nested lists, etc. --
that can be converted to an array. We can also refer to
variables like `var1`.
var2 : int
The type above can either refer to an actual Python type
(e.g. ``int``), or describe the type of the variable in more
detail, e.g. ``(N,) ndarray`` or ``array_like``.
*args : iterable
Other arguments.
long_var_name : {'hi', 'ho'}, optional
Choices in brackets, default first when optional.
**kwargs : dict
Keyword arguments.
Returns
-------
type
Explanation of anonymous return value of type ``type``.
describe : type
Explanation of return value named `describe`.
out : type
Explanation of `out`.
type_without_description
Other Parameters
----------------
only_seldom_used_keywords : type
Explanation.
common_parameters_listed_above : type
Explanation.
Raises
------
BadException
Because you shouldn't have done that.
See Also
--------
numpy.array : Relationship (optional).
numpy.ndarray : Relationship (optional), which could be fairly long, in
which case the line wraps here.
numpy.dot, numpy.linalg.norm, numpy.eye
Notes
-----
Notes about the implementation algorithm (if needed).
This can have multiple paragraphs.
You may include some math:
.. math:: X(e^{j\omega } ) = x(n)e^{ - j\omega n}
And even use a Greek symbol like :math:`\omega` inline.
References
----------
Cite the relevant literature, e.g. [1]_. You may also cite these
references in the notes section above.
.. [1] O. McNoleg, "The integration of GIS, remote sensing,
expert systems and adaptive co-kriging for environmental habitat
modelling of the Highland Haggis using object-oriented, fuzzy-logic
and neural-network techniques," Computers & Geosciences, vol. 22,
pp. 585-588, 1996.
Examples
--------
These are written in doctest format, and should illustrate how to
use the function.
>>> a = [1, 2, 3]
>>> print([x + 3 for x in a])
[4, 5, 6]
>>> print("a\nb")
a
b
"""
# After closing class docstring, there should be one blank line to
# separate following codes (according to PEP257).
# But for function, method and module, there should be no blank lines
# after closing the docstring.
pass
The docstrings also support LaTeX-commands. To be able to render them correctly, one needs an r
in front of the
"""
, so the line after the definition of the function should start with r"""
.
Compiling the docs using sphinx
We are currently building the documentation via the sphinx tool.
Compiling the docs, if you already have the .rst
and .html
-files
If you already have the precompiled docs and you just want to update the documentation, you can execute the shell script
bash make_html.sh
You can use the flags -c
(copy) and -d
(delete), in order to extended
the functionality of the script, e.g.
- Copy the jupyter notebooks into the into the
./Notebooks_Web
folder:
bash make_html.sh -c
- Remove the
./build
folder to get a cleaner update:
bash make_html.sh -d
- Delete the
./build
folder and copy the jupyter notebooks into./Notebooks_Web
:
bash make_html.sh -d -c
or (the order of arguments is not relevent)
bash make_html.sh -c -d
To execute this script under Windows you need to install git for windows.
You can find an introduction/tutorial for sphinx under documenting your project with sphinx.
The restructed text format
All of the files generated by sphinx are in the so called reStructuredText (.rst)-format. You can find a short introduction
under Quick reStructuredText. We also like the page
reST and sphinx Cheatsheet. Inside the documentation of the
oppy package, we also use intersphinx
and so-called cross references (you can refer in the docstring of your function to another
function, class, package that you have written or is elsewhere implemented and documented properly). To organize cross references
sphinx uses so-called domains, you can find the basic markup and further information under
sphinx domains.
Unittests for the oppy module
What are (unit) tests and why should I start writing (unit)tests?
Unit tests or tests in general provide a framework to automatically test written code in a specific programming language. Tests make sure that already written code work properly, e.g., tests spot bugs, coding mistakes and help the developer to check if functions or classes still work after changes in the source code. Tests are really important guarantee functional and reliable code, or, as Julien Danjou, the author of the book Serious Python, says: Writing code that is not tested is fundamentally useless.
What is the unittest
package in python?
The unittest
python package provides a framework to write tests, run them automatically and parallelize them, in case of large libraries.
The main resource for this package is the
unittest documentation. This package supports tests for function outputs,
class attributes, raised warnings and exceptions and a lot more.
How do set up a unit test?
There are two standards on where to locate and how to name test methods. We decided to build the structure of unit test as the following. Assuming a library structured as:
oppy_package
|oppy
| |submodule1
| | |method1.py
|docs
| |...
|...
Then one creates a submodule tests
and in the first step we copy the structure of the module your_module
. For each method
or class like method1.py
, the corresponding test gets name as test_method1.py
, such that the resulting structure is of the
form:
oppy_package
|oppy
| |submodule1
| | |method1.py
| |tests
| | |submodule1
| | | |test_method1.py
|docs
| |...
|...
The unit test class: unittest.TestCase
Now, as we know where to locate our unit test, we want to know how we can test our methods and classes. First, we have to
import the unittest
module and setup a test case:
import unittest
class TestYourMethod(unittest.TestCase):
pass
There are several functions provided by the unittest
framework, which will help you, to setup your unittest. You can use the
setUpClass(self)
method, which will be called once before all test methods. You can also use the setUp(self)
method, which
will be called before each test. To tidy up after the last test, you can use the tearDown(self)
method, which will be
executed after all test methods. Now your class could look like this:
import unittest
class TestYourMethod(unittest.TestCase):
@classmethod
def setUpClass(self):
# this method will be called before all tests
pass
def setUp(self):
# this method runs before each test (so overall multiple times)
pass
# YOUR TEST METHODS BELONG HERE, for example
def test_my_method(self):
# test whether the output is correct
pass
def tearDown(self):
# executed after all tests
pass
# execute the test
if __name__ == "__main__":
unittest.main()
All of the three methods are documented in unittest TestCase.
We added the last two lines, to be able to execute the test and to allow the unittest
framework to automatically run all tests
in the long run (for this task we also have a function called test_oppy_all.py
located in the oppy/tests
folder).
Methods to test functions, classes, warnings, etc.: assert()
As we now know how to set up the basic structure of a test, we want to take a look at methods to test specific things, e.g. if a method raises a warning based on a specific input. All basic functions to check for attributes, return values and class types are listed under assert functions. We will now take a closer look at this example function:
import warnings
import numpy as np
def my_sqrt(x):
if x>=0:
return np.sqrt(x)
else:
warnings.warn("x has to be non-negative, None is returned.")
return None
For simplicity we did not include a docstring and we just assume that the argument x
is always a scalar, to avoid try
and catch
blocks. In our first test, we want to check whether the function computes the right values, if we put in correct input values (in this
case values that do not provoke any errors or warnings). We expand our test with two methods. The method test_my_sqrt_correct_input
checks whether our method yields correct resutls, of the input data is valid. Therefore we use the self.assertEqual
function, which
checks if the two inputs are the same (in a high precision numerical sense). The second function self.assertAlmostEqual
is for our cases more practical, as we can set the comparison tolerance manually by changing the default argument delta
.
We can now also see how the setUpClass
method is used. Here you should define relevant constants (e.g. the comparison precision, the
number of test cases, relevant messages if a method fails a test, etc.). You can find all of these methods in the
unittest documentation.
import unittest
# we want to use math.sqrt, to check whether our function works
import math
import numpy as np
def TestMySqrt(unittest.TestCase):
@classmethod
def setUpClass(self):
# we set this number e.g. globally, as we would want to use this number in every test
# with an input case
self.test_size = 100
# for almost equal tests, we have to set a desired tolerance
self.tolerance = 10e-12
# here we can define some strings that will be shown if the method fails a test
self.msg_unequal = "Own sqrt function failed to give the right result."
self.msg_not_almost_equal = "Own sqrt function failed to yield results in the desired tolerance"
# test the method for correct input values and check, if the method gives the right result
def test_my_sqrt_correct_input(self):
# this only produces numbers in the intervall [0,1], in a real test, one would need
# to expand this interval
x = np.random.rand(self.test_size)
for i in range(0, self.test_size):
own_sqrt = my_sqrt(x[i])
perfect_sqrt = math.sqrt(x[i])
self.assertEqual(own_sqrt, perfect_sqrt, msg=self.msg_unequal)
self.assertAlmostEqual(own_sqrt, perfect_sqrt, delta=self.tolerance, msg=self.msg_not_almost_equal)
# in this case we put a negative value into the function and check, whether a warning is raised
def test_my_sqrt_warnings(self):
x = -1
with self.assertWarns(Warning):
result = my_sqrt(x)
if __name__ == "__main__":
unittest.main()
Side note: We know that in this case it is not really useful to test the np.sqrt
function against the
math.sqrt
function and that the np.sqrt
function has already built in warnings, but we wanted to simulate
an easy example. To test own solvers of linear equation systems one could e.g. use the scipy
solver, define the right-hand
side based on the already given solution manually or use equation systems, where the solution is already known. Here you
can be relly creative and most of the times many ways work.
How we test our classes and methods in oppy
We have no definite rules for what and how we test oppy, but here are a few rules of thumb:
- Test every method, every class method and every constructur your file/package has.
- Test at least two different outputs with valid inputs. For a solver or an optimization algorithm we usually test the solver with default options for one problem, then we use the same problem, change the options and go for a solution with the maximum precision. In both cases we e.g. set upper bounds for the number of steps the algorithm should take and compare them to literature values. If a value is set once, it is a good indicator for improved version of this algorithm (the new version should in most cases not take more steps than before).
- Test every possible warning and exception. Here you have to be really creative. Think of possible wrong inputs and check how your method or algorithm performs with this type of nonsene - Is a warning raised? Is the text of this warning really useful? etc.
Include your method and/or subpacke inside the test_oppy_all
method
In order to run all tests at once, we need to embed our subpackage and methods in the test_oppy_all
framework. We distinguish
- New subpackage: Create a file named
run_tests_YourSubpackage.py
. The structure of the file should be similar to the following file:
"""Run all Tests."""
import unittest
# import your test modules
from oppy.tests.YourSubpackage import test_method_1, test_method_2
def test_oppy_YourSubpackage(show=False):
"""
Run all YourSubpackage unittests at once.
Parameters
----------
show : boolean, optional
Flag whether additional information should be shown. The default is
False.
Returns
-------
result : Test result information
Holds all information about the unittest (e.g. number of tests run,
collection of failures, etc.)
"""
# initialize the test suite
loader = unittest.TestLoader()
suite = unittest.TestSuite()
# add tests to the test suite
suite.addTests(loader.loadTestsFromModule(test_method_1))
suite.addTests(loader.loadTestsFromModule(test_method_2))
# initialize a runner, pass it your suite and run it
if show is False:
# run without show anything
# runner = unittest.TextTestRunner(verbosity=0)
# run with some dots running
runner = unittest.TextTestRunner(verbosity=1)
else:
# show some details
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
return result
# execute the test
if __name__ == "__main__":
test_oppy_YourSubpackage()
Inside the method test_oppy_YourSubpackage
you should include all unittests of your subpackage, such that all of these tests are executed when running
the method test_oppy_YourSubpackage()
. Now, you need to extend the test_oppy_all
file.
"""test_oppy_all"""
from oppy.tests.conOpt import run_tests_conOpt
from oppy.tests.itMet import run_tests_itMet
from oppy.tests.linOpt import run_tests_linOpt
from oppy.tests.multOpt import run_tests_multOpt
from oppy.tests.options import run_tests_options
from oppy.tests.results import run_tests_results
from oppy.tests.unconOpt import run_tests_unconOpt
# IMPORT YOUR METHOD HERE
from oppy.tests.YourMethod import run_tests_YourSubpackage
def test_oppy_all(show=False):
"""
Run all oppy tests at once.
Method to run all implemented unittests inside the oppy package at once.
Parameters
----------
show : boolean, optional
Flag to decide, wether one wants to see intermediate steps of the tests
or not. The default is ``show=False``.
Returns
-------
None.
"""
# execute all itMet tests
run_tests_conOpt.test_oppy_conOpt(show)
run_tests_itMet.test_oppy_itMet(show)
run_tests_linOpt.test_oppy_linOpt(show)
run_tests_multOpt.test_oppy_multOpt(show)
run_tests_options.test_oppy_options(show)
run_tests_results.test_oppy_results(show)
run_tests_unconOpt.test_oppy_unconOpt(show)
# RUN ALL OF YOUR TESTS HERE
run_tests_YourSubpackge.test_oppy_YourSubpackge(show)
# execute the test
if __name__ == "__main__":
test_oppy_all()
Finally, we can execute all tests at once by running the test_oppy_all()
function.
- New method: Include the unittest of your method in the corresponding
run_tests_package
of the method’s package.
.. _execute-all-tests:
Execute tests automatically
Inside the oppy package we provide two additional functions to simplify our unittest framework. First, we have the
oppy/tests/test_oppy_all.py
function, which executes all unittests of the oppy module.
We also added the oppy.test
function inside the __init__
-file of the package, which calls the test_oppy_all
method and further simplifies the process of running all unittests at once.
Second, we implemented the oppy/tests/run_all_tests_in_dir.py
method. This function basically behaves as a wrapper
for the unittest discover
method. By calling this function inside a directory, the unittest
framework automatically runs
all unittests inside this directory and all subdirectories. This can be useful, if you are only interested in running
tests for a single subpackage of the module.
How to use the Results
and Options
classes for new methods in oppy
Based on the idea of the
scipy OptimizeResult class, we decided to
use classes for the Options
and Results
of all optimization algorithms and solvers of the oppy package. In this HowTo
you will learn how these two classes basically work and how use them when including new algorithms into the oppy
package.
What are the Results
and Options
classes and what framework do they provide?
The Options
class
The Options
class, located under oppy/options/options/Options.py
provided a simple but powerful framework to
manually change the options for any method or algorithm inside the oppy package. The constructor of the method is really
generic. In this method all the internal dictionaries are created, that will later be used to set the default options
and check for additional constraints. You can manually set options in two ways.
First, just use an empty constructor and set the attributes of the class via the class.attribute
relationship:
from oppy.options import Options
example_options = Options()
example_options.nmax = 10000
example_options.initstep = 1
Or you can use the built in **kwargs
, to set the options directly inside the constructor call:
from oppy.options import Options
example_options = Options(nmax=10000, initstep=1)
Up to this point, the Options are still very generic and the class does not know yet, inside which method the class will be used.
The core method of the Options
class is the ops
method. This method is called inside the currently used function and
gets the so called caller_method
from the internal stack and completes, based on the methods name, the class.
Inside this method missing attributes (if the user does not set all options manually) are set and additional mathematical
constraints (e.g. the maximum number of iterations have to be positive) are checked.
The Results
class
The Results
class follows a similar idea and is located under oppy/results/results/Results.py
and can be imported
via:
from oppy.results import Results
The constructor of this class gets called directly inside each
method and needs as an input variable
the methods name. An optional argument is the options.results
variable, where the user can choose between three different
output types and one custom output option. The output options are the following:
sol: This is an abbreviation for solution (only). Here the user should only get the solution vector of the optimization process or the linear solver. Nothing else will be returned.
normal (default): The normal mode is the default option. Here the method should return the solution vector, the number of iterations and a list with the norm of the residuals.
all: Using this option, the user should get any possible information. Possible are for example internal linesearch status flags, a list of all intermediate solution vectors and a list with all step width the linesearch used. As a developer you can be really creative, just include anything that could be interesting.
list: This is the already mentioned custom options. Using this option, the user can pass a list with names of variables he wants to be returned. This list has to be a subset of the set of all possible outputs.
In all cases an instance of the class Results
will be returned containing the desired output variables as class attributes.
The core method of the Results
-class is the update_results(update_dict)
method which is called inside each iteration
of a method and updates the attributes (e.g. the list containing the norm of the gradients) of the Results
-instance.
We will explain this method in further details in the next section
How can I bind in a new method and/or subpackage?
Options
New subpackage: First, you should add a list with the names of all methods included in this subpackage as a self
-attribute
inside the __init__
file of the Options
-class. Second, you should add the desired common options (the same for all methods of the subpackage)
and the desired special options (individual options for each method) as a new elif
-case inside the ops
-method. If you want some options
to satisfy specific constraints (e.g. an option should be greater to zero), you need to define a common_options_constraints
and a
special_options_constraints
dict.
If you need further constraint for the options of the subpackage, you can extend the self.constraints_dict
and the self.map_constraints_to_attributes
dicts.
We want to give a short example on how these dicts work. Let us further assume, all of our new methods have a common option tolerance
which should be greater
to zero. A simple way to automatically check this constraint, is to define the keyword 'greater_zero'
in both of the dicts
self.constraints_dict = {'greater_zero': lambda x: self.__dict__[x] > 0}
self.map_constraints_to_attributes = {'greater_zero' : lambda x: [x]}
The first dict is used to iterate over all the constraints and in each iteration create a checker_func
. The second function turns a constraint back into a
list of iterable class attribute, so it can be set to default if the user’s choice is invalid. For further details see the check_constraints
method.
If you need further flags, dummy functions, etc. which should be carried as a self
-attribute inside the class construction and are irrelevant for the user,
please include them inside the self.repr_ignore
list. Hence, they are internally available but will not be shown in the representation of the class.
For all methods inside the subpackage, follow the guide below. If you just want to include a single method inside an already existing subpackage, the guide below is also sufficient.
New method: Your new method should be contained in a subpackage list. Besides the common options of the subpackage, you can customize the options of
your new method inside the self.special_options
dict. Here, you need to create a key with the name of your method. The corresponding entry should be a dict
containing keys with name of the individual option and their default value. Be careful: the name of an option you choose inside this dict will be the name of the
class attribute later.
After you have set all the options inside the constructor of the class, you can finally include the options directly into your method. You simply shift all options (in general all arguments which are not necessarily needed) into the options class and include
from oppy.options import Options
def my_new_method(A, x, options=None):
"""
My personal docstring
"""
if not options:
options = Options()
if not options.cache:
options.ops()
# How to get the options:
my_option = options.my_option
another_option = options.another_option
# the code of your method can stay the same
after the docstring of your method.
Results
New subpackage: First, make a list including all methods inside this subpackage. Second, clarify which variables should be part of the Results
class inside this
subpackage. In a next step, compare your list to the existing ones – maybe the new subpackage has the same attributes as an already existing subpackage (e.g. the
unconstrained and constrained optimization problems share the same Results
attributes). If you need a new case, you have to define the possible_returns_with_init
and the results_options_dict
dictionaries. The first dict contains all possible attributes of the Results
class and their corresponding default value, while the
second one specifies the concrete realization of the Results
class in each of the four cases ('sol'
, 'normal'
(default), 'all'
, 'list'
).
Now you can execute the following steps for each method inside your subpackage.
New method: First, we need to create an instance of the Results
class via
from oppy.options import Options
from oppy.results import Results
def my_new_method(A, x, options=None):
"""
My personal docstring
"""
if not options:
options = Options()
if not options.cache:
options.ops()
# How to get the options:
my_option = options.my_option
another_option = options.another_option
# create an instance of the Results class with the flag set by the user
results = Results('my_new_method', options.results)
# add the options as additional into the return class
results.update_results({'additional': options.get_ops()})
# the code of your method can stay the same
inside our function. Second, we can call the results.update_results(dict)
in each of the iteration loops (or just one time, if your method does not have an
iteration process). We assume you want your method to have the current solution x
, a list with the solution iterates X
, an iteration counter iteration
and a list with the norm of the residuals res
. In this case, your update process is of the form
from oppy.options import Options
from oppy.results import Results
def my_new_method(A, x, options=None):
"""
My personal docstring
"""
if not options:
options = Options()
if not options.cache:
options.ops()
# How to get the options:
my_option = options.my_option
another_option = options.another_option
# create an instance of the Results class with the flag set by the user
results = Results('my_new_method', options.results)
# add the options as additional into the return class
results.update_results({'additional': options.get_ops()})
# the code of your method can stay the same
while my_iteration_condition:
results.update_results({'X': x, # append the current solution
'iteration': True, # increase the iteration counter
'res': res[-1]}) # append the norm of the current residual
# your code is here
# after the loop
results.update_results({'x': x, # solution found by your algorithm
'X': x, # append the solution at the end of the iteration list
'iteration': True, # increase the iteration counter
'res': res[-1]}) # append the current residual
# call the finalize method to remove internal variables
results.finalize()
return results
Before returning the results
instance, we call results.finalize()
to remove internal variables which are irrelevant for
the users output.
How you should use git while working with oppy
This How to is split into two sections. First, you need to set up your system with git. In the second section we provide practical ‘best-practice’ tips on how to use git with oppy.
Install and set up git
First we need to install and set up git. Depending on your system you need to do different things.
Windows
On Windows you need to install git for windows. To do so go to
https://git-scm.com/download/win
and afterwards choose 32-bit or 64-bit. Now follow the setup instructions. You can leave the default settings (we recommend that). As default editor you can choose (after installing) Notepad++ which is a great editor. You can find it here:
https://www.heise.de/download/product/notepad-26659
After the installation process is done you need to configure your git environment. To do so, open a Git Bash and type into the terminal
git config --global user.name "your_git_username"
git config --global user.email "your_email_address"
where you replace “your_git_username” by (usually) Vorname Nachname and “your_email_address” by vorname.nachname@uni-konstanz.de. You can also save your password (we do not recommend that as it is saved in plaintext on your machine and everybody could read it). To do so type in a Git Bash
git config --global credential.helper store
then
git pull
provide a username and password and those details will then be remembered later. The credentials are stored in a file on the disk, with the disk permissions of “just user readable/writable” but still in plaintext.
If you want to change the password later
git pull
Will fail, because the password is incorrect, git then removes the offending user+password from the ~/.git-credentials file, so now re-run
git pull
to provide a new password so it works as earlier. Found on stack overflow at:
https://stackoverflow.com/questions/35942754/how-to-save-username-and-password-in-git
Ubuntu
On Ubuntu, open a terminal and run
sudo apt install git-all
After the installation process is done you need to configure your git environment. To do so, open a Git Bash and type into the terminal
git config --global user.name "your_git_username"
git config --global user.email "your_email_address"
where you replace “your_git_username” by (usually) Vorname Nachname and “your_email_address” by vorname.nachname@uni-konstanz.de.
If you want to save your password you can use a ssh-key. There are many instructions available online. Just follow one of them (depending on your system). Finally, add the new ssh-key into your gitlab account.
Mac
First, we need to install Git. You can find a guide under Git installation. Afterwards open the terminal and type
git config --global user.name "your_git_username"
git config --global user.email "your_email_address"
where you replace “your_git_username” by (usually) Vorname Nachname and “your_email_address” by vorname.nachname@uni-konstanz.de.
To make your life easier, we highly recommend using a ssh-key, as you do not have to enter your password every time.
Use git in your daily work
First time
If you are using git for the very first time, please first create a folder where you want to save your oppy code. Then open a terminal in this folder and use:
git clone git@gitlab.inf.uni-konstanz.de:ag-volkwein/oppy.git
This will clone all codes, Notebooks, the doumentation, etc. from oppy’s master branch into your folder. Before you get the other branches (just to be sure) we do:
git pull
To get the other branches (e.g. dev
) use:
git checkout -b dev origin/dev
To see all branches use
git branch -a
Now you are ready to create your own branch
git checkout -b [name of your new branch]
and push it to git
git push origin [name of your new branch]
Daily work
Let’s assume you want to work in oppy. First check that your system is up to date. With
git status
you can see which branch you are using. Execute
git pull
on your branch and on the dev branch. Then merge the code from dev into your branch via
git checkout [name of your new branch]
git merge dev
git push
Now you can work on your code. If you are done, first upload it on your branch
git add .
git commit -m 'your commit message'
git push
And now switch back to dev and merge your branch into dev.
git checkout dev
git pull
git merge [name of your new branch] -m 'your merge message‘
git push
Now you are done. Maybe (to be safe for the next time) you checkout into your branch again:
git checkout [name of your new branch]
Advanced git setting
If you also use git for your personal projects, it is possible, to run Gitlab and Github at the same time, using different accounts/email addresses. You can find a really good instruction under Different Accounts for Github and Gitlab and a shorter version under SSH key config for git.