glabs uses two configuration layers:
- Main config (global, ~/.glabs.yaml)
- Course configs (per course and assignment)
Configuration is merged from top to bottom: main config → course config → assignment overrides → defaults.
If you are migrating from the previous startercode structure, use migration.md.
Default file: ~/.glabs with any Viper-supported extension (for example .yaml, .yml, .json)
gitlab:
host: https://gitlab.example.org
token: <personal-access-token>
sshprivatekey: /path/to/private/key
coursesfilepath: /absolute/path/to/course-configs
courses:
- mpd
- vss| Key | Purpose | Required | Default |
|---|---|---|---|
gitlab.host |
Base URL of your GitLab instance | Yes | — |
gitlab.token |
Personal access token with API scope | Yes | — |
sshprivatekey |
Path to unencrypted SSH key for git operations | No | System default SSH key |
coursesfilepath |
Directory containing course config files | Yes | — |
courses |
List of course file basenames to load | Yes | — |
- sshprivatekey: Only needed if default SSH key doesn't work
- coursesfilepath: Must be absolute path
- courses: Filenames without extension (glabs adds .yaml/.yml/etc)
Create course files in coursesfilepath, for example mpd.yaml:
mpd:
coursepath: mpd/semester # GitLab subgroup path
semesterpath: ob-26ss # Optional semester identifier
useCoursenameAsPrefix: false # Add course name to project names
useEmailDomainAsSuffix: true # Add domainname to repo name
students:
- alice@example.org
- bob.student@university.edu
groups:
team-a:
- alice@example.org
- charlie@example.org
team-b:
- bob.student@university.edu
blatt01:
assignmentpath: blatt-01
description: Assignment 1
per: student
accesslevel: developer
startercode: ...
blatt02:
# Overrides
students:
- alice@example.org| Key | Purpose | Default | Notes |
|---|---|---|---|
coursepath |
GitLab subgroup path for course | — | Required |
semesterpath |
Additional grouping level | — | Optional |
useCoursenameAsPrefix |
Prepend course name to project names | false |
Example: mpd-blatt01 |
useEmailDomainAsSuffix |
Use full email (with _at_...) as repo suffix |
true |
If false, only the part before @ is used (e.g. alice instead of alice_at_example.org). |
students |
List of course-wide students | — | Can be overridden per assignment |
groups |
Dict of group → student lists | — | For per: group assignments |
Students and groups can be specified by:
- Email:
alice@example.org - Username:
alice - User ID:
12345
You can mix formats. By default, if using emails, the @ is replaced with _at_ in project names (filesystem compatibility), e.g. mpd-blatt01-alice_at_example.org.
If you set useEmailDomainAsSuffix: false, only the part before the @ is used, e.g. mpd-blatt01-alice.
When running commands, you can filter:
# Filter by regex pattern
glabs generate mpd blatt01 'a.*'
# Multiple patterns
glabs generate mpd blatt01 alice bobPatterns are regular expressions, so:
alicematches exactly "alice"a.*matches "alice", "bob" (any name with 'a')^amatches names starting with 'a'
<course>:
<assignment>:
assignmentpath: blatt-01
description: Assignment 1
per: student
accesslevel: developer
containerRegistry: false
mergeRequest:
mergeMethod: merge # merge | semi_linear | ff
squashOption: default_off # never | always | default_off | default_on
startercode:
...
seeder:
...
clone:
...
release:
...| Key | Purpose | Default | Notes |
|---|---|---|---|
assignmentpath |
GitLab path segment for repos | — | Required |
per |
student or group |
student |
Determines repo naming and access |
description |
GitLab project description | generated by glabs |
Visible in UI |
accesslevel |
Initial project access (see below) | developer |
Can be changed later with setaccess |
containerRegistry |
Enable container registry | false |
Auto-enabled if release.dockerImages set |
students |
Override/add assignment-specific students | — | Merged with course-level |
groups |
Override/add assignment-specific groups | — | Merged with course-level |
| Level | Value | Permissions |
|---|---|---|
| guest | 10 | View public projects, comment on issues |
| reporter | 20 | Create issues, cannot push code |
| developer | 30 | Push code, merge pull requests (default) |
| maintainer | 40 | Full admin access including settings |
Example:
blatt01:
accesslevel: reporter # Students can only view and commentChange after creation:
glabs setaccess mpd blatt01 -l maintainerClone from a starter repository to each student repo:
startercode:
url: git@gitlab.example.org:mpd/startercode/blatt-01.git
fromBranch: template # Clone this branch
toBranch: main # Into this branch
additionalBranches: [] # Push starter/<branch> to repo/<branch>| Key | Purpose | Default | Notes |
|---|---|---|---|
url |
SSH URL of starter repo | — | Required if startercode block exists |
fromBranch |
Source branch in starter | main |
Must exist in starter repo |
toBranch |
Target branch in new repos | main |
Usually production branch |
additionalBranches |
Additional branches mirrored from starter repo | [] |
Each x maps starter/x -> repo/x |
Important behavior:
- Only branches listed in
startercode.additionalBranchesare mirrored from starter with branch-specific content. - All other generated branches are created from the state of
startercode.toBranch. - This means that branches from the
branches:block that are not inadditionalBranchesstart identical totoBranch.
Define branch creation, protection and default branch independently from startercode:
branches:
- name: main
protect: true
allowForcePush: false
default: false
- name: develop
mergeOnly: true
codeOwnerApprovalRequired: true
default: true| Key | Purpose | Default | Notes |
|---|---|---|---|
name |
Branch name | — | Required |
protect |
Maintainer-only push/merge | false |
Same semantics as classic protected branch |
mergeOnly |
Developers may merge but cannot push | false |
Sets push=No one, merge=Developers |
default |
Set as project default branch | false |
If none is set, first branch becomes default |
allowForcePush |
Allow force-push on protected branch | false |
Passed through to GitLab protected branch settings |
codeOwnerApprovalRequired |
Require Code Owner approval | false |
Passed through to GitLab protected branch settings |
mergeOnly: truetakes precedence overprotect: truefor that branch- Branches are created for generated projects based on this list
- Branch creation base is
startercode.toBranch(orseeder.toBranchwhen seeder is used) - If a branch name is also listed in
startercode.additionalBranches, the mirrored starter branch content is used for that branch glabs protectapplies these branch rules to existing repositories
Configure issue replication separately from startercode:
issues:
replicateFromStartercode: true
issueNumbers: [1, 3]| Key | Purpose | Default | Notes |
|---|---|---|---|
replicateFromStartercode |
Copy issues from starter project | false |
Requires startercode |
issueNumbers |
Which issue numbers to copy | [1] |
Used only when replication is enabled |
Example: Merge-only development branch
branches:
- name: main
protect: true
- name: develop
mergeOnly: true
default: trueResult in GitLab UI:
- Allowed to merge: Developers and Maintainers
- Allowed to push and merge: No one
Execute custom code to seed repositories:
seeder:
cmd: python
args:
- /path/to/generator.py
- generate
- "%s"
name: Generator Bot
email: generator@example.org
toBranch: main
protectToBranch: false
signKey: <optional-gpg-key>| Key | Purpose | Default | Notes |
|---|---|---|---|
cmd |
Command to run | — | Required if seeder block exists |
args |
Arguments to pass | — | %s replaced with repo path |
name |
Git author name | — | For commits |
email |
Git author email | — | For commits |
toBranch |
Branch to commit to | main |
|
protectToBranch |
Protect after seeding | false |
|
signKey |
GPG private key | — | Optional; you'll be prompted for passphrase |
seeder:
cmd: python3
args:
- /home/instructor/generators/coursework.py
- "%s"
name: Course Generator
email: generator@hm.eduThe script receives the repo path and can generate files, directories, build scripts, etc.
Configuration for glabs clone:
clone:
localpath: /tmp/work
branch: main
force: false| Key | Purpose | Default | Notes |
|---|---|---|---|
localpath |
Where to clone | . |
Current directory |
branch |
Checkout branch | main |
|
force |
Remove existing before clone | false |
Configure release workflow (merge requests, Docker builds):
release:
mergeRequest:
source: develop # Branch with new features
target: main # Production branch
pipeline: true # Wait for CI to pass
dockerImages:
- myapp/service
- myapp/frontend| Key | Purpose | Default | Notes |
|---|---|---|---|
mergeRequest.source |
Feature branch | develop |
Where students work |
mergeRequest.target |
Production branch | main |
Where code goes live |
mergeRequest.pipeline |
Wait for CI | false |
Require passing checks |
dockerImages |
Images to build | [] |
Creates container registry entries |
Notes:
- Container registry is auto-enabled when
dockerImagesis set - Enables GitLab Flow workflow for releases
- Works with CI/CD pipeline to build/deploy
Control how merge requests are merged in every generated project:
<assignment>:
mergeRequest:
mergeMethod: merge # merge | semi_linear | ff
squashOption: default_off # never | always | default_off | default_on
pipeline: false # Require successful pipeline before merge
skippedPipelinesAreSuccessful: false
allThreadsMustBeResolved: false
statusChecksMustSucceed: false
approvals:
settings:
preventApprovalByMergeRequestCreator: true
preventApprovalsByUsersWhoAddCommits: false
preventEditingApprovalRulesInMergeRequests: false
requireUserReauthenticationToApprove: false
whenCommitAdded: removeAllApprovals
rules:
- name: review-main
branch: main
usernames: [myusername]
groups: []
requiredApprovals: 1
- name: review-release
branches: [release, stable]
usernames: [release-owner]
groups: [mpd/tutors]
requiredApprovals: 2| Key | Purpose | Default | Notes |
|---|---|---|---|
mergeRequest.mergeMethod |
Merge strategy for all MRs | merge |
Applied at project creation |
mergeRequest.squashOption |
Squash-on-merge setting | default_off |
Applied at project creation |
mergeRequest.pipeline |
Pipelines must succeed | false |
Maps to GitLab merge check |
mergeRequest.skippedPipelinesAreSuccessful |
Treat skipped pipelines as successful | false |
Only relevant when mergeRequest.pipeline=true |
mergeRequest.allThreadsMustBeResolved |
All threads must be resolved | false |
Maps to GitLab merge check |
mergeRequest.statusChecksMustSucceed |
Status checks must succeed | false |
Maps to GitLab merge check |
mergeRequest.approvals |
Approval configuration block | {} |
Contains settings and rules |
mergeRequest.approvals.settings |
Project-level approval settings | {} |
Maps to GitLab approval settings API |
mergeRequest.approvals.settings.preventApprovalByMergeRequestCreator |
Prevent approval by MR creator | unset | Inverted and mapped to GitLab merge_requests_author_approval |
mergeRequest.approvals.settings.preventApprovalsByUsersWhoAddCommits |
Prevent approval by committers | unset | Maps to GitLab merge_requests_disable_committers_approval |
mergeRequest.approvals.settings.preventEditingApprovalRulesInMergeRequests |
Prevent editing rules in MRs | unset | Maps to GitLab disable_overriding_approvers_per_merge_request |
mergeRequest.approvals.settings.requireUserReauthenticationToApprove |
Require user re-authentication to approve | unset | Maps to GitLab require_reauthentication_to_approve |
mergeRequest.approvals.settings.whenCommitAdded |
Approval reset mode on push | unset | keepApprovals | removeAllApprovals | removeCodeOwnerApprovalsIfTheirFilesChanged |
mergeRequest.approvals.rules |
Project approval rules (per protected branch) | [] |
Applied by generate and protect |
mergeRequest.approvals.rules[].name |
Rule name in GitLab | auto (glabs-approval-<branch>) |
For multiple branches in one rule, branch name is appended |
mergeRequest.approvals.rules[].branch |
Single target branch | — | Convenience alias for one branch |
mergeRequest.approvals.rules[].branches |
Target branches | [] |
Each branch becomes one project approval rule |
mergeRequest.approvals.rules[].usernames |
Approvers (GitLab usernames) | [] |
@ prefix is allowed and removed automatically |
mergeRequest.approvals.rules[].groups |
Approver groups (full path or numeric ID) | [] |
Example full path: mpd/tutors |
mergeRequest.approvals.rules[].multiMemberGroupsOnly |
Only apply rule for groups with more than one member | false |
Skipped for per: student and one-person groups |
mergeRequest.approvals.rules[].requiredApprovals |
Required number of approvals | 0 |
0 + empty usernames/groups removes an existing rule |
Note: Email addresses and numeric user IDs are not accepted in mergeRequest.approvals.rules[].usernames and result in an explicit error. Use usernames only.
Note: Approval rules for branches that are not protected in the target project are skipped with a warning; other valid approval rules are still applied.
Requirements for approval rules:
- Each target branch must exist in the repository.
- Each target branch must be protected (configured with
protect: trueormergeOnly: true). - Configured approvers must have project/group access in GitLab so they are eligible approvers.
- Rules with
multiMemberGroupsOnly: trueare only applied forper: groupprojects with more than one member.
| Value | GitLab UI name | Description |
|---|---|---|
merge |
Merge commit | Always create a merge commit (GitLab default) |
semi_linear |
Merge commit with semi-linear history | Require a linear history; fast-forward commits are rebased before merge |
ff |
Fast-forward merge | No merge commits; only fast-forward merges allowed |
| Value | GitLab UI name | Description |
|---|---|---|
never |
Do not allow | Squashing is disabled; the checkbox is hidden |
always |
Require | Every MR is squashed automatically; the checkbox is hidden |
default_off |
Allow | Squash checkbox visible but unchecked by default (GitLab default) |
default_on |
Encourage | Squash checkbox visible and checked by default |
Warning: Although the GitLab UI sometimes displays squash settings in the context of individual branch rules, squash options are always project-wide. Changing the squash setting in any branch rule or merge request rule will affect all branches in the project. There is currently no way to configure squash behavior per branch. This is a known limitation of GitLab, even if the UI suggests otherwise.
Example:
blatt01:
mergeRequest:
mergeMethod: ff # Students must keep a linear history
squashOption: always # All commits squashed into one on merge
pipeline: true # Block merge until latest pipeline succeeds
skippedPipelinesAreSuccessful: false
allThreadsMustBeResolved: true
statusChecksMustSucceed: true
approvals:
settings:
preventApprovalByMergeRequestCreator: true
preventApprovalsByUsersWhoAddCommits: false
preventEditingApprovalRulesInMergeRequests: false
requireUserReauthenticationToApprove: false
whenCommitAdded: removeAllApprovals
rules:
- name: review-main
branch: main
usernames:
- instructor
requiredApprovals: 1
- name: review-release
branches: [release]
usernames:
- release-owner
groups:
- mpd/tutors
multiMemberGroupsOnly: true
requiredApprovals: 2
- name: no-review-next
branch: next
usernames: []
groups: []
requiredApprovals: 0# ~/.glabs.yaml
gitlab:
host: https://gitlab.lrz.de
token: glpat-XXXXXXXXXXXXX
coursesfilepath: ~/courses
courses:
- mpd
- vss# ~/courses/mpd.yaml
mpd:
coursepath: teaching/2024ss/mpd
semesterpath: semester-1
useCoursenameAsPrefix: false
students:
- alice.mueller@hm.edu
- bob.schmidt@hm.edu
groups:
team-1:
- alice.mueller@hm.edu
- charlie.weber@hm.edu
blatt01:
assignmentpath: blatt-01
per: student
accesslevel: developer
description: Assignment 1 - Data Structures
startercode:
url: git@gitlab.lrz.de:teaching/2024ss/mpd/starter-blatt01.git
fromBranch: template
toBranch: main
branches:
- name: main
protect: true
- name: develop
mergeOnly: true
default: true
issues:
replicateFromStartercode: true
issueNumbers: [1]
blatt02:
assignmentpath: blatt-02
per: group
startercode:
url: git@gitlab.lrz.de:teaching/2024ss/mpd/starter-blatt02.git