Collaboration

Sharing project work within your team via GitLab

1 Context: Using Git for Version Control

Git is a version control system that records changes to files over time. It lets teams:

  • Work in parallel without overwriting each other’s work.
  • Review history of changes (who changed what, when, and why).
  • Create branches to try out ideas safely and merge them back.
  • Recover earlier versions if something breaks.
Caution

Always work inside a project folder that contains all files of one project (e.g., ~/projects/myproject/). Never initialize or connect Git in your home or root directory or in hub-public.

1.1 How Git + GitLab work (mental model)

  • Local repository (on JupyterHub or your own computer):
    This is your project folder. It contains your files and a hidden history (the “repo”). You make changes here and take snapshots of them.
  • Central repository (on GitLab):
    This is the shared, authoritative copy your whole team uses. Everyone pushes their snapshots to GitLab and pulls the latest snapshots from it.

Think of it as (Figure 1):

  • You download a copy of an entire remote repository to start working (clone).
  • You edit files locally and save a snapshot (commit) with a short message.
  • You share your snapshots with teammates (push to GitLab).
  • You receive teammates’ snapshots (pull from GitLab).
  • Optionally, you develop on branches and create Merge Requests (MRs) for review before changes land on main (not necessarily required for your project)
Figure 1: How Git works

1.2 How this looks in JupyterHub

JupyterHub provides a Git panel/extension so you can work with buttons instead of commands:

  • Clone / Initialize
    • Clone brings an existing GitLab project into a new project folder on JupyterHub.
    • Initialize (Init) turns your current project folder into a new repo that you can connect to GitLab.
  • Changes (Unstaged / Staged)
    • The panel shows which files changed since the last snapshot.
    • Move files from Unstaged to Staged to include them in your next snapshot.
  • Commit
    • Write a short, meaningful message (e.g., “Add EDA notebook; update README”) and click Commit.
    • A commit is a durable snapshot of your staged changes.
  • Pull / Push
    • Pull fetches and integrates your teammates’ latest commits from GitLab into your local project folder.
    • Push uploads your commits to GitLab so others can see/use them.
Figure 2: The Git panel in JupyterHub

In our courses, one teammate creates the GitLab repository; everyone else clones it into their own JupyterHub project folder and uses the Git panel.

2 FH Münster GitLab

2.1 Login

  1. Open the FH Münster GitLab site: https://git.fh-muenster.de.
  2. Use your university credentials or the configured single sign-on method.
Figure 3: Login-screen of the FH GitLab server: login using your FH credentials or the FH Münster SSO

2.2 Creating a repository

  1. Click New Project (Figure 4), then Create blank project (Figure 5).
  2. Project name: short, hyphenated (e.g., my-project) (Figure 6).
  3. Visibility: choose Private (recommended for coursework).
  4. Initialize repository with a README: (makes the first commit).
  5. Create the project.
  6. After creation, GitLab shows HTTPS and SSH clone URLs. For beginners, use HTTPS. The clone URL is under “Code”. You’ll need it in JupyterHub (Figure 7).
Figure 4: Create a new project/repository in GitLab by clicking on “+” or “New project”
Figure 5: Select blank project to create a new repository
Figure 6: Give your project a name and make it private
Figure 7: Copy the HTTPS path to clone the repository to JupyterHub later

2.3 Inviting collaborators to a Git repository

  1. Open your project page in GitLab.
  2. Go to Manage → Members.
  3. Add teammates by their name or FH username (Figure 8)
  4. Role: Developer (can push to branches) is appropriate for team members.
  5. Set an optional expiration date (for time-boxed courses), then Invite.

Only the Repo Owner (or a Maintainer) can invite members. For coursework, Developer access is typically sufficient.

Figure 8: Invite team members to your repository with a specified role

2.4 The .gitignore file

A .gitignore file tells Git which files and folders not to track. This keeps your repository clean (no secrets, no huge files, no machine-specific clutter) and avoids merge noise.

Key points

  • Git only ignores files that are not yet tracked. If you already committed a file, Git will keep tracking it until you remove it from the index.
  • Patterns apply relative to the repo root (where .gitignore lives).
  • You can have multiple .gitignore files (one at the root, plus optional ones in subfolders).
  • For personal, machine-local ignores, use global ignores instead of polluting project repos.

How to create a .gitignore file for your project

  1. In your GitLab project, create a new file (Figure 9).

  2. Name it .gitignore and select “Python” in the template dropdown, once it appears after typing in the file name (Figure 10) or add the following content:

    .gitignore
    # Byte-compiled / optimized / DLL files
    __pycache__/
    *.py[cod]
    *$py.class
    
    # C extensions
    *.so
    
    # Distribution / packaging
    .Python
    build/
    develop-eggs/
    dist/
    downloads/
    eggs/
    .eggs/
    lib/
    lib64/
    parts/
    sdist/
    var/
    wheels/
    share/python-wheels/
    *.egg-info/
    .installed.cfg
    *.egg
    MANIFEST
    
    # PyInstaller
    #  Usually these files are written by a python script from a template
    #  before PyInstaller builds the exe, so as to inject date/other infos into it.
    *.manifest
    *.spec
    
    # Installer logs
    pip-log.txt
    pip-delete-this-directory.txt
    
    # Unit test / coverage reports
    htmlcov/
    .tox/
    .nox/
    .coverage
    .coverage.*
    .cache
    nosetests.xml
    coverage.xml
    *.cover
    .hypothesis/
    .pytest_cache/
    
    # Translations
    *.mo
    *.pot
    
    # Django stuff:
    *.log
    local_settings.py
    db.sqlite3
    
    # Flask stuff:
    instance/
    .webassets-cache
    
    # Scrapy stuff:
    .scrapy
    
    # Sphinx documentation
    docs/_build/
    
    # PyBuilder
    target/
    
    # Jupyter Notebook
    .ipynb_checkpoints
    
    # IPython
    profile_default/
    ipython_config.py
    
    # pyenv
    .python-version
    
    # celery beat schedule file
    celerybeat-schedule
    
    # SageMath parsed files
    *.sage.py
    
    # Environments
    .env
    .venv
    env/
    venv/
    ENV/
    env.bak/
    venv.bak/
    
    # Spyder project settings
    .spyderproject
    .spyproject
    
    # Rope project settings
    .ropeproject
    
    # mkdocs documentation
    /site
    
    # mypy
    .mypy_cache/
    .dmypy.json
    dmypy.json
    
    # Pyre type checker
    .pyre/
    # pytype static type analyzer
  3. Click “Commit changes” twice.

Figure 9: Create the .gitignore file in GitLab before cloning to JupyterHub
Figure 10: Create the .gitignore file in GitLab before cloning to JupyterHub
Create only one repository per project team
  1. One teammate (repo Owner) creates the private GitLab repo (with README + Python .gitignore) and invites others as Developer.
  2. Everyone else clones that single repo into a project folder in JupyterHub (not the home directory), following Section 3.
  3. Daily loop: Pull, work, stage & Commit, pull, push. (Section 4.3 for details).

2.5 Alternatives: GitHub

You can perform the same Git workflow on GitHub. The steps (create a repo, clone, commit, push, invite collaborators) are conceptually identical. When you use GitLab for coursework you can simply use your FH Münster credentials and won’t have to create a separate GitHub account.

3 Integrating a GitLab Repository in JupyterHub

To bring a fresh GitLab project into JupyterHub:

  1. In Jupyterhub, move to your home directory (make sure you are not in the folder hub-public). Then click on the Git symbol in the sidebar to open the Git panel (Figure 11)
  2. Click on “Clone a repository” and enter your repository’s HTTP URL https://git.fh-muenster.de/.../my_project.git (Figure 12)
  3. Provide your GitLab (FH) credentials and store them temporarily (Figure 13)
  4. When the repository is cloned, it is shown in the folder overview (Figure 14). The repo will be cloned into a new folder with the name of the repo within your home directory.
  5. (Once per project) Set your Git identity on JupyterHub if not already set
    • Open a terminal (Figure 15)
    • Enter a user name (first and last name) and your email address (FH user name ’@fh-muenster.de`) for Git (Figure 16)
    git config user.name "Your Name"
    git config user.email "ab123456@fh-muenster.de"
Figure 11: The Git panel in JupyterHub before cloning a repository
Figure 12: Cloning a repository with JupyterHub
Figure 13: Enter your credentials to clone the repository with JupyterHub
Figure 14: The repository has been successfully cloned
Figure 15: Open a terminal in JupyterHub
Figure 16: Enter a name and email address for Git

3.1 Putting an Existing JupyterHub Project Under Git

If you already have a project folder and want to put it under version control (not recommended, rather start with an empty project cloned from GitLab):

  1. Go to the project folder (not your home folder):
  2. Initialize Git by clicking on the respective button in the Git sidebar (Figure 11):
  3. Create a new empty project in GitLab.
  4. Connect your local repo to the remote and push with the respective HTTPS URL.

4 Using GitLab in JupyterHub

4.1 Commit

A commit is a named snapshot of exactly the files you select. Commit early and often so your history tells a clear story and rollbacks stay small.

In JupyterHub (Git sidebar)

  1. Make sure you are in your project folder (never your home directory).
  2. Open the Git panel. The Unstaged list shows files changed since the last commit.
  3. Move only the files you want to save into Staged (use the checkboxes, see Figure 17).
  4. Write a short, action-oriented message (imperative mood works well, see Figure 18), e.g.
    • Add EDA notebook for outlier detection
    • Refactor loading utils; fix missing dtype
  5. Click Commit. The snapshot is now part of your local history.
Figure 17: Select the files you want to commit
Figure 18: Enter a commit message
Good practice
  • Keep commits focused (one topic/change per commit).
  • Don’t stage transient or large files (check your .gitignore).
  • If you committed the wrong file, you can unstage before committing (UI: move back to Unstaged).

4.2 Push/Pull

Pull brings your teammates’ latest work from GitLab into your project folder. Push publishes your local commits to GitLab so others can use them. The safe rhythm is: pull → work/commit → pull → push.

In JupyterHub (Git sidebar)

  • Pull: Click Pull to fetch and merge the latest changes from GitLab into your local branch. Do this before you start working and again before pushing.
  • Push: After you have committed locally, click Push to upload your commits to GitLab (Figure 19, Figure 20 and Figure 21).
Figure 19: Push changes to the remote or pull from remote
Figure 20: Pushed contents can now be found in GitLab
Figure 21: Pushed notebooks can be viewed in GitLab

4.3 Recommended team routine (with JupyterHub UI)

  1. Start in your project folder (never the home directory).
  2. Pull (get the latest from the remote repository).
  3. Create/continue your branch (optional but recommended).
  4. Work on notebooks/code; save regularly.
  5. Stage relevant files in the Git panel and Commit with a clear message.
  6. Pull again (to integrate any new remote changes); resolve conflicts if prompted.
  7. Push your commit(s) so the team sees your work.
  8. Open/merge a Merge Request on GitLab when your feature is ready.

4.4 Typical pitfalls & how to avoid them

  • Wrong location: Make sure the Git panel shows your project folder path before you clone/init.
  • Committing large data: Keep large raw data out of the repo (e.g., in data/raw/) and add it to your .gitignore. Commit code, small sample data, docs, and configs.
  • Notebook conflicts: Pull frequently. Coordinate edits on the same notebook or split work into separate notebooks (e.g., eda_01.ipynb, eda_02.ipynb). Move shared functions into src/ Python modules.
  • Vague commit messages: Use short, action-oriented summaries (what you changed and why it matters).
Back to top