diff options
author | James Fargher <jfargher@gitlab.com> | 2023-08-07 07:26:10 +0300 |
---|---|---|
committer | James Fargher <jfargher@gitlab.com> | 2023-08-10 01:06:39 +0300 |
commit | f2bc26bbbb0a9781d36ee6a72bd1786c6060c502 (patch) | |
tree | 2e400ed703860c34c2a5ca2b73ba782f012e8453 | |
parent | 9c19257b770a75fdc73e700f8c1891d5420f1370 (diff) |
backup: Restore empty repository backups
Reads the refs file to detect empty repository backups. When there are
no references in the refs file, the bundle can be skipped.
In the future the loaded refs will be used to remove deleted refs.
https://gitlab.com/gitlab-org/gitaly/-/issues/3857
Changelog: changed
-rw-r--r-- | internal/backup/backup.go | 69 | ||||
-rw-r--r-- | internal/backup/backup_test.go | 10 | ||||
-rw-r--r-- | internal/backup/server_side_test.go | 2 | ||||
-rw-r--r-- | internal/gitaly/service/repository/restore_repository_test.go | 2 |
4 files changed, 59 insertions, 24 deletions
diff --git a/internal/backup/backup.go b/internal/backup/backup.go index 01b48d60c..fd308abd2 100644 --- a/internal/backup/backup.go +++ b/internal/backup/backup.go @@ -270,23 +270,33 @@ func (mgr *Manager) Restore(ctx context.Context, req *RestoreRequest) error { } for _, step := range backup.Steps { - if err := mgr.restoreBundle(ctx, repo, step.BundlePath); err != nil { - if errors.Is(err, ErrDoesntExist) { - // For compatibility with existing backups we need to make sure the - // repository exists even if there's no bundle for project - // repositories (not wiki or snippet repositories). Gitaly does - // not know which repository is which type so here we accept a - // parameter to tell us to employ this behaviour. Since the - // repository has already been created, we simply skip cleaning up. - if req.AlwaysCreate { - return nil - } - - if err := repo.Remove(ctx); err != nil { - return fmt.Errorf("manager: remove on skipped: %w", err) - } - - return fmt.Errorf("manager: %w: %s", ErrSkipped, err.Error()) + refs, err := mgr.readRefs(ctx, step.RefPath) + switch { + case errors.Is(err, ErrDoesntExist): + // For compatibility with existing backups we need to make sure the + // repository exists even if there's no bundle for project + // repositories (not wiki or snippet repositories). Gitaly does + // not know which repository is which type so here we accept a + // parameter to tell us to employ this behaviour. Since the + // repository has already been created, we simply skip cleaning up. + if req.AlwaysCreate { + return nil + } + + if err := repo.Remove(ctx); err != nil { + return fmt.Errorf("manager: remove on skipped: %w", err) + } + + return fmt.Errorf("manager: %w: %s", ErrSkipped, err.Error()) + case err != nil: + return fmt.Errorf("manager: %w", err) + } + + // Git bundles can not be created for empty repositories. Since empty + // repository backups do not contain a bundle, skip bundle restoration. + if len(refs) > 0 { + if err := mgr.restoreBundle(ctx, repo, step.BundlePath); err != nil { + return fmt.Errorf("manager: %w", err) } } if err := mgr.restoreCustomHooks(ctx, repo, step.CustomHooksPath); err != nil { @@ -401,6 +411,31 @@ func (mgr *Manager) negatedKnownRefs(ctx context.Context, step *Step) (io.ReadCl return r, nil } +func (mgr *Manager) readRefs(ctx context.Context, path string) ([]git.Reference, error) { + reader, err := mgr.sink.GetReader(ctx, path) + if err != nil { + return nil, fmt.Errorf("read refs: %w", err) + } + defer reader.Close() + + var refs []git.Reference + + d := git.NewShowRefDecoder(reader) + for { + var ref git.Reference + + if err := d.Decode(&ref); err == io.EOF { + break + } else if err != nil { + return refs, fmt.Errorf("read refs: %w", err) + } + + refs = append(refs, ref) + } + + return refs, nil +} + func (mgr *Manager) restoreBundle(ctx context.Context, repo Repository, path string) error { reader, err := mgr.sink.GetReader(ctx, path) if err != nil { diff --git a/internal/backup/backup_test.go b/internal/backup/backup_test.go index d9eeb80c6..fb156d732 100644 --- a/internal/backup/backup_test.go +++ b/internal/backup/backup_test.go @@ -518,9 +518,9 @@ func TestManager_Restore_latest(t *testing.T) { relativePath + ".refs": "", }) - return repo, nil + return repo, new(git.Checksum) }, - expectedErrAs: backup.ErrSkipped, + expectExists: true, }, { desc: "empty backup, always create", @@ -533,7 +533,7 @@ func TestManager_Restore_latest(t *testing.T) { relativePath + ".refs": "", }) - return repo, nil + return repo, new(git.Checksum) }, alwaysCreate: true, expectExists: true, @@ -590,9 +590,9 @@ func TestManager_Restore_latest(t *testing.T) { filepath.Join(relativePath, backupID, "001.refs"): "", }) - return repo, nil + return repo, new(git.Checksum) }, - expectedErrAs: backup.ErrSkipped, + expectExists: true, }, { desc: "many incrementals", diff --git a/internal/backup/server_side_test.go b/internal/backup/server_side_test.go index 91dfe9784..3329c8a78 100644 --- a/internal/backup/server_side_test.go +++ b/internal/backup/server_side_test.go @@ -194,7 +194,7 @@ func TestServerSideAdapter_Restore(t *testing.T) { backupID: "", } }, - expectedErr: fmt.Errorf("server-side restore: %w: rpc error: code = FailedPrecondition desc = restore repository: manager: repository skipped: restore bundle: \"@test/restore/latest/missing.bundle\": doesn't exist", backup.ErrSkipped), + expectedErr: fmt.Errorf("server-side restore: %w: rpc error: code = FailedPrecondition desc = restore repository: manager: repository skipped: read refs: doesn't exist", backup.ErrSkipped), }, } { tc := tc diff --git a/internal/gitaly/service/repository/restore_repository_test.go b/internal/gitaly/service/repository/restore_repository_test.go index df0043a69..2551ffeb7 100644 --- a/internal/gitaly/service/repository/restore_repository_test.go +++ b/internal/gitaly/service/repository/restore_repository_test.go @@ -138,7 +138,7 @@ func TestRestoreRepository(t *testing.T) { backupID: "", } }, - expectedErr: structerr.NewFailedPrecondition("restore repository: manager: repository skipped: restore bundle: \"@test/restore/latest/missing.bundle\": doesn't exist").WithDetail( + expectedErr: structerr.NewFailedPrecondition("restore repository: manager: repository skipped: read refs: doesn't exist").WithDetail( &gitalypb.RestoreRepositoryResponse_SkippedError{}, ), }, |