Distribution
Overview
divekit distribute creates individualized repositories from an existing distribution config.
In a typical run it:
- reads
.divekit/distributions/<name>/config.json - creates repositories in the configured target group(s)
- individualizes content for each UUID
- assigns members and permissions
- writes state files:
remotes.jsonandindividuals.json
If the evaluation pipeline is enabled, Divekit also creates the linked eval repositories.
Prerequisites
divekit init is a one-time setup step and does not need to be repeated before every distribution.
Before running distribution, make sure you have:
- A distribution config in
.divekit/distributions/<distribution>/config.json - Working authentication (
divekit authordivekit config hosts) - A valid members file referenced by
members.path
Repository names are derived from the distribution name by default. If config.json
does not set name, Divekit uses {{distribution}}-{{uuid}}, so a distribution
named ST2M4 creates repositories such as ST2M4-b8520e99-.... Set name only
if you need a custom repository naming pattern. The
Repository Name Templates
reference lists all supported group data, functions, and formatting options.
When the evaluation pipeline is enabled, the eval repositories use the same
repository name template as the work repositories. The target context comes
from the surrounding GitLab group/path layout, not from an automatic -eval
suffix in the project name.
Run Distribution
Use the distribution name directly:
divekit distribute --distribution <distribution-name>
Or short form:
divekit distribute -d <distribution-name>
Without -d, Divekit prompts for a distribution interactively.
Prepare Members and Groups
Divekit can create or update the configured members file from the interactive
multiline prompt, --members-file, or stdin. In the simple format, each
non-empty line creates one repository group:
tom
ada
john
ben
This produces four groups with one GitLab username each. Put comma-separated usernames on one row when several people should share one repository:
alice,bob
john,ada
This produces two groups: alice/bob and john/ada. Because the first row
does not contain a recognized header such as username or member, it is
treated as member data and is not skipped.
CSV, TSV, and Excel files can include a header row. Use username, member,
members, user, login, or provider_username for GitLab usernames.
Numbered forms such as member1,member2 or username1,username2 create
multi-member groups from one row.
A metadata-only first row such as fruits is not enough to trigger header
mode; include at least one recognized member column such as username,
member, or email.
email is only a fallback member column when no username/member column exists.
If both email and username exist, Divekit uses username for GitLab access
and keeps email as group metadata.
email,username,fruit
tom@example.com,tom,banana
ada@example.com,ada,apple
john@example.com,john,apple
This produces three groups with one GitLab username each: tom, ada, and
john. The fruit column is stored as groups[].metadata.fruit, so it can be
used in repository name templates with {{metadata "fruit"}} or
{{.Metadata.fruit}}.
Columns named label, group, group_label, repository,
repository_label, repo, repo_label, or title become the group label
instead of metadata. Rows with the same uuid or the same label are merged into
one repository group.
Resolve Different GitLab Usernames
If distribution cannot assign a person because the local member name does not match the GitLab username, set a member alias and rerun the distribution command:
divekit members alias set -d ST2M4 myname myname_extern
divekit distribute -d ST2M4
The alias command keeps the GitLab username in members.json and stores the local display name as metadata. Commands such as members list, overview, and the GUI then show both names as myname (@myname_extern), while GitLab operations continue to use myname_extern.
For details, see Member Aliases.
Exclude Members From a Distribution
If a members file was created from a previous milestone or passcheck result, you can remove individual users before or after creating repositories:
divekit members exclude -d ST2M4 alice bob
For longer exclusion lists, put one username per line into a file:
divekit members exclude -d ST2M4 --members-file excluded.txt
Preview the result first with --dry-run:
divekit members exclude -d ST2M4 --members-file excluded.txt --dry-run
members exclude shows a confirmation plan, writes a new members file, preserves the UUIDs of remaining groups, removes empty groups, and updates config.json so the distribution points to the filtered file. The original members file remains available for audit or rollback.
If the distribution already has work-target remotes, members exclude also revokes the excluded users from the matching GitLab projects after confirmation. It does not delete already created repositories. Use --dry-run to inspect the local filtering and planned remote membership changes without writing files or changing GitLab.
Update Configured Members
Use members add when late-joining users should be added to the configured members file before creating or updating repositories:
divekit members add -d ST2M4 alice bob
divekit members add -d ST2M4 --members-file additional-members.txt
Use members filter --include to keep only a known set, for example users who passed an earlier milestone:
divekit members filter -d ST2M4 --include --members-file passed-members.txt
The include mode is also available through the keep, retain, and intersect aliases:
divekit members keep -d ST2M4 --members-file passed-members.txt
Use members filter --exclude when you want the same local filtering behavior as members exclude while keeping the command shape explicit:
divekit members filter -d ST2M4 --exclude --members-file skipped-members.txt
These commands write a new members file, update config.json to point at it, preserve existing UUIDs, remove empty groups, and support --dry-run.
When users are removed and work-target remotes already exist, Divekit also plans matching GitLab membership revocations after confirmation.
When users are added to a distribution with existing work-target remotes, run divekit distribute --add or divekit members sync afterwards to assign them to repositories.
When an interactive divekit distribute run finds an existing configured
members file, it asks how to proceed:
Keep: use the configured members as they are.Add: append newcomers only. Removing configured members is rejected in this mode.Edit: open the current groups in the multiline editor. Existing groups are shown as comma-separated rows, so changingalice,bobtoalice,carolchanges group membership instead of only editing a flat member list.Fresh: start with an empty member list and reset distribution state.
When you choose Edit in divekit distribute and confirm the changed groups,
existing repositories are matched by UUID. Divekit assigns the current members
to each UUID’s work project and removes users who were previously configured for
that UUID but were moved to another UUID or removed from the configured file. It
does not delete repositories and it does not remove arbitrary users that were
added manually outside the configured members file. Eval targets and runs with
--no-member-assignment do not perform membership changes.
Manual edits to members.json only give Divekit the current desired member
state. Use the interactive distribute edit flow, divekit members exclude, or
divekit members filter when you want Divekit to plan remote membership
removals from an existing distribution.
Update Member Roles
Use members role to change the GitLab access level of existing members on the work target. Downgrades also revoke overprivileged Project Access Tokens on affected work projects:
divekit members role -d ST2M4 guest
If no access level is passed, Divekit prompts for one interactively:
divekit members role -d ST2M4
The legacy top-level command divekit role still works for compatibility, but it prints a deprecation warning. Use divekit members role for new scripts and documentation.
Example Flow (Current)
# Initial rollout
divekit distribute -d ST2M4
# Filter configured members before creating repos
divekit members exclude -d ST2M4 --members-file excluded.txt
# Add late-joining members to the configured members file
divekit members add -d ST2M4 --members-file new-members.txt
# Restrict member access after a deadline
divekit members role -d ST2M4 guest
# Assign newly configured members without recreating existing repos
divekit distribute -d ST2M4 --add
# Rebuild state files and re-run while preserving stable repository indexes
divekit distribute -d ST2M4 --fresh
# Preview only (no remote changes)
divekit distribute -d ST2M4 --dry-run
# Write the distribution to local files instead of GitLab
divekit distribute -d ST2M4 -p local
Useful options for advanced runs:
--provider simulated(same idea as--dry-run)--provider localor-p localto create a local filesystem distribution--workers <n>and--rate-limit <n>for large cohorts--warn-unresolved-tokens auto|true|falsefor unresolved token warnings--dangerously-delete-group-projectsfor destructive reset scenarios
Retrofitting Existing Eval Repositories
For evaluation-pipeline distributions, Divekit disables GitLab’s unique Pages domain setting on eval repositories during divekit distribute.
That keeps Pages URLs predictable for course material and generated links.
If a distribution already exists, you can reconcile the stored eval remotes afterwards without patching files:
divekit patch -d ST2M4 --disable-eval-pages-unique-domain
You can also combine this with normal file patches:
divekit patch -d ST2M4 README.md --disable-eval-pages-unique-domain
Behavior notes:
- the flag only affects the
evaltarget - it is safe to run on existing distributions
--dry-runstill shows the planned action without changing remote state
Local Distribution Output
Use the local provider when you want to inspect or test generated repositories without writing to GitLab:
divekit distribute -d ST2M4 -p local
Divekit writes the generated local distribution under:
.divekit/local-data/<distribution>/local/<run-id>/
├── work/<project>/repo
├── eval/<project>/repo
└── remotes.json
These repositories are local filesystem outputs, not GitLab projects. Commands such as divekit run can discover them and run scripts against them.
Outputs and Follow-Up
After a successful run, check:
.divekit/distributions/<distribution>/remotes.jsonfor UUID → repository mapping.divekit/distributions/<distribution>/individuals.jsonfor concrete variation selectionsdivekit overview -d <distribution>for status overview
divekit overview may read cached member data. Use
divekit overview -d <distribution> --no-cache when you need fresh member data
from GitLab. The older overview --fresh spelling is deprecated; keep
--fresh for divekit distribute only, where it resets generated distribution
state before re-running.
For GitLab-backed repositories, divekit fetch -d <distribution> creates timestamped snapshots under .divekit/local-data/<distribution>/gitlab/<timestamp>/. divekit run --fetch-first uses the same storage before executing a script.
When a long-running operation is interrupted, Divekit now reports the partial
progress before exiting. distribute, fetch, patch, snapshot, run, and purge
show how many items completed, failed, were skipped, or were not attempted where
that information is available. For distribute, already completed remote
mappings and individuals are persisted so follow-up commands can continue from
the recorded state.
Create Research Snapshots
Use divekit snapshot when you need a research or archival copy of distributed
work repositories without the original Git commit history.
# Interactive: select distribution, snapshot remote, and snapshot root group
divekit snapshot
# Explicit target group
divekit snapshot -d ST2M4 --target-group research/course-2026
# Preview the target projects without cloning, creating groups, or pushing
divekit snapshot -d ST2M4 --dry-run
# Keep local staging repositories after upload
divekit snapshot -d ST2M4 --output-dir ./snapshot-staging
For each selected work repository, Divekit clones the current source state,
removes .git, initializes a new repository, creates one neutral initial commit,
and pushes that repository into the snapshot target group. By default the commit
author is Divekit <divekit@invalid.local> and the target branch is main.
The command deliberately removes Git history and commit metadata only. It does not scrub file contents, file paths, UUIDs, repository names, or other data that is present in the repository tree. Review the generated plan before confirming, and avoid name patterns that include usernames unless that exposure is intended.
Snapshot target resolution works like this:
--target-groupwrites below that GitLab group.snapshot.groupIdin.divekit/distributions/<distribution>/config.jsonstores the configured snapshot root group used by interactive runs.- Without
snapshot.groupId, Divekit derives a snapshot location from the distribution group layout. Fordistribution-subgroups, it writes below the layout root as<distribution>/snapshot/<label>. For legacy layouts, it writes belowsnapshot/<label>next to the work group.
In interactive mode, Divekit asks for the snapshot remote when multiple host
aliases are configured, then asks for the snapshot root group before showing the
confirmation plan. If you enter a different root group, Divekit resolves it to a
GitLab group ID and stores it as snapshot.groupId for the distribution.
By default local staging uses a temporary directory and is removed after the run.
Set --output-dir only when you want to inspect or keep the local rewritten
repositories after they have been pushed. Snapshot does not write a manifest
file and does not update remotes.json.
Useful options:
--uuidor--userlimits the selected work repositories.--name-patterncontrols target project names. The default is{{distribution}}-{{uuid}}.--on-existing skip|overwrite|failcontrols how existing target projects are handled.--source-refclones a specific source branch or tag instead of the default source branch.--workersand--rate-limitcontrol concurrency for larger cohorts.
Delete Distributed Repositories
Use divekit purge when you intentionally want to remove repositories recorded
for a distribution. The command builds and prints a deletion plan before it asks
for confirmation.
# Interactive: inspect the plan and confirm at the prompt
divekit purge -d ST2M4
# Scripted destructive run: confirmation must be explicit
divekit purge -d ST2M4 --yes
Plain --non-interactive is not enough for purge, because deleting remote
repositories requires an explicit confirmation. Use --yes only in scripts
where the selected distribution and host are already constrained.
Patch Existing Repositories
Use divekit patch when repositories have already been distributed and you need to apply focused follow-up changes.
# Choose files from a searchable interactive picker
divekit patch
# Create or update files from the origin repository
divekit patch -d ST2M4 README.md docs/changelog.txt
# Equivalent explicit form for file patches
divekit patch files -d ST2M4 README.md docs/changelog.txt
# Rewrite patch files even when the target content already matches the reference
divekit patch -d ST2M4 README.md --force
# Delete a remote file even if it no longer exists locally
divekit patch -d ST2M4 --delete obsolete-file.txt
# Delete all files below a remote directory in every distributed repository
divekit patch -d ST2M4 --delete-dir old-material
# Move a remote file
divekit patch -d ST2M4 --move old/path.txt:new/path.txt
When no positional file or explicit operation is supplied in an interactive
terminal, Divekit lists Git-visible project files in a searchable multi-select
picker. Changed and untracked files appear first. The picker excludes .git/
and .divekit/; selecting no files cancels the patch. Non-interactive runs must
continue to provide files or an explicit operation such as --delete.
Patch paths are repository-relative. Positional files are read from the current
origin repository and individualized before being committed. By default Divekit
skips file patches when the distributed repository already matches the reference
content; add --force when the file should still be written and committed, for
example after repairing derived state around a patch. --delete, --delete-dir,
and --move operate on the distributed repositories directly, so they also work
for files that were accidentally distributed but are not present in the origin
repository anymore.
When patched files affect hashed solution tables, Divekit also regenerates the
derived work-repository hash artifact. For example, changing
src/test/resources/E1.solution_evalrepo.md or src/test/resources/E1.config.json
causes the matching src/test/resources/E1.hashed.md file to be updated in the
work repositories.
--delete-dir expands the directory separately in each target repository and deletes all files below that path. Missing delete paths and missing move sources are skipped, which keeps cleanup patches idempotent across groups whose repositories may differ slightly. The interactive confirmation plan is based on one group as a preview; execution resolves these operations per project.
File patches do not reconcile GitLab project membership. Use the interactive
divekit distribute edit flow or the member filtering commands when regrouping
or removing configured members should also assign and revoke work-project
members. divekit patch still individualizes patched files by UUID using the
current configured members when available.
Repair Repository Names and Paths
Use divekit patch name when repositories were distributed with outdated GitLab
project names or URL paths and you want to reconcile them with the current
config.json naming rule.
# Interactive: select distribution and choose whether to patch name, URL path, or both
divekit patch name
# Preview the plan for one distribution
divekit patch name -d ST2M4 --scope both
# Apply non-interactively
divekit patch name -d ST2M4 --scope both --apply --non-interactive
The expected repository name is calculated from config.json field name.
If name is missing, Divekit uses the default {{distribution}}-{{uuid}}.
The URL path is the GitLab-normalized slug for that expected name.
Group labels and metadata from the current members file can be part of the
template, for example {{label}}, {{slug}}, or {{metadata "fruit"}}.
When the template uses {{index}}, the patch reuses persisted assignments or
allocates missing ones before reconciling names and URL paths.
Scopes:
name: update only the GitLab display namepath: update only the GitLab URL pathboth: update display name and URL path
When URL paths change, Divekit also refreshes remotes.json, reconciles
repository linking variables, keeps inbound token scopes current, and re-patches
origin files that contain Pages URL tokens such as PagesURL, PagesUrl,
PageUrl, or pagesUrl. Hard-coded old URLs without those tokens are not
rewritten automatically.
divekit patch name does not rename local distribution folders.
Next Steps
- Configuration details
- Individualization setup
- Evaluation Pipeline details
- Member aliases
divekit distributecommand reference