Skip clearing repositories for exclusion-based backups
What does this MR do and why?
Backup::Targets::Repositories#remove_all_repositories decided whether to clear
existing repositories before a restore based only on whether REPOSITORIES_PATHS
was set:
return if paths.present?A backup created with SKIP_REPOSITORIES_PATHS alone leaves paths empty, so
the restore passed -remove-all-repositories to gitaly-backup and cleared
every repository in the configured storages first. Because the excluded
repositories are also filtered out of the restore itself, they were deleted and
never restored, with no way to recover them from that backup.
This also made it impossible to restore several SKIP_REPOSITORIES_PATHS
backups in sequence, since each restore destroyed the repositories left by the
previous one. The guard on REPOSITORIES_PATHS exists to support exactly that
workflow.
This MR treats a backup as partial when either option is set. A full restore, with neither set, still clears stale repositories.
Why this was missed
SKIP_REPOSITORIES_PATHS was added in !121865 (merged). That MR threaded skip_paths
through project_relation, snippet_relation, and a new skipped_path_relation,
but left remove_all_repositories unchanged. It appears as untouched context in
that diff, immediately below the attr_reader line the MR did modify. The
for skip_paths examples added in the same MR asserted the resulting behaviour,
so this MR updates those two assertions along with the fix.
The behaviour also stayed hidden because the documented usage passes both
options together. paths.present? short-circuits in that case, so the
SKIP_REPOSITORIES_PATHS-only path was never exercised.
Points for reviewer attention
- Two existing assertions are inverted. The
for skip_pathsexamples under#restorepreviously expectedremove_all_repositories: %w[default]. Both now expectnil. See "Why this was missed" above. - Presence is treated as intent. A non-matching value, for example a typo'd
path, now suppresses clearing even though nothing is actually excluded. This
matches how the existing
pathsguard already behaves. REPOSITORIES_STORAGEScombined withSKIP_REPOSITORIES_PATHSpreviously cleared the named storage and now does not. Covered by a new spec.- Restore-time environment variables.
Backup::Options#update_from_backup_information!merges manifest values with+=, soSKIP_REPOSITORIES_PATHSpassed at restore time also suppresses clearing, even for a backup that recorded no skip paths. This seems reasonable, since the restore is partial, but it is new behaviour forskip_paths.
References
- Closes #610910 (closed)
SKIP_REPOSITORIES_PATHSwas introduced in !121865 (merged), for #18287 (closed)
Screenshots or screen recordings
Note
The screen recording has been edited to skip over waiting for commands.
How to set up and validate locally
Warning
Run this on a disposable GDK. Step 3 permanently destroys the flightjs
repositories, and they are only recoverable by completing step 4.
Steps 3 and 5 restore the same backup artifact, so the only variable is the code.
Prerequisites
Run everything from <gdk-root>/gitlab, starting on master:
git checkout masterBefore starting, note which repositories the flightjs projects currently have,
so you can compare after each restore. Checking in the UI is enough.
1. On master, create a full backup to use as a restore point
bundle exec rake gitlab:backup:create BACKUP=full-backup \
SKIP=db,uploads,builds,artifacts,lfs,terraform_state,registry,packages,ci_secure_files,external_diffs,pages,agent_plan_contentThis deliberately omits SKIP_REPOSITORIES_PATHS, so the backup contains every
repository and step 4 can restore flightjs.
2. Create a partial backup that excludes flightjs
bundle exec rake gitlab:backup:create BACKUP=skip-test \
SKIP_REPOSITORIES_PATHS=flightjs \
SKIP=db,uploads,builds,artifacts,lfs,terraform_state,registry,packages,ci_secure_files,external_diffs,pages,agent_plan_contentConfirm the manifest recorded the exclusion:
tar -xOf tmp/backups/skip-test_gitlab_backup.tar backup_information.yml \
| grep -E ':backup_id|:skip_repositories_paths'3. Restore skip-test and observe the data loss
rm -rf tmp/backups/db tmp/backups/repositories tmp/backups/backup_information.yml
tar -xf tmp/backups/skip-test_gitlab_backup.tar -C tmp/backups
bundle exec rake gitlab:backup:repo:restore BACKUP=skip-testThe flightjs repositories are now gone, while every other repository is
present.
4. Reset back to the starting state
rm -rf tmp/backups/db tmp/backups/repositories tmp/backups/backup_information.yml
tar -xf tmp/backups/full-backup_gitlab_backup.tar -C tmp/backups
bundle exec rake gitlab:backup:repo:restore BACKUP=full-backupThe flightjs repositories are present again.
5. On the fix/skip-repositories-path-deletes-on-restore branch, restore the same backup
git checkout fix/skip-repositories-path-deletes-on-restore
gdk restart rails-web vite
rm -rf tmp/backups/db tmp/backups/repositories tmp/backups/backup_information.yml
tar -xf tmp/backups/skip-test_gitlab_backup.tar -C tmp/backups
bundle exec rake gitlab:backup:repo:restore BACKUP=skip-testThe flightjs repositories are still present and unchanged, because the
clearing step was skipped.
6. Still on this branch, confirm the documented restore-time argument
The restore documentation shows SKIP_REPOSITORIES_PATHS passed directly to a
restore. That route reaches the clearing decision differently, through
extract_from_env! in the Backup::Manager constructor, so verify it too using
the full backup from step 1:
rm -rf tmp/backups/db tmp/backups/repositories tmp/backups/backup_information.yml
tar -xf tmp/backups/full-backup_gitlab_backup.tar -C tmp/backups
bundle exec rake gitlab:backup:repo:restore BACKUP=full-backup \
SKIP_REPOSITORIES_PATHS=flightjsThe flightjs repositories survive, and the restore log shows only the
flightjs group wiki being restored. On master, the same command removes them.
Notes on the steps
gitlab:backup:repo:restorerestores only repositories, leaving the database andgitlab:shell:setupalone.- The
rm -rfandtar -xfbefore each restore matter:restore_taskdoes not callrun_unpack, so it reads the manifest and staging directory from disk. Without clearing first, a restore can run against a stale manifest from an earlier step and give a misleading result. SKIPfilters tasks, whereasSKIP_REPOSITORIES_PATHSfilters repositories.agent_plan_contentis in theSKIPlist only because that storage path is not provisioned in a default GDK.- A full
gitlab:backup:createis used rather thangitlab:backup:repo:createbecause only the full task list writes the manifest that carriesskip_repositories_pathsthrough to the restore.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.