in internal/gitaly/repoutil/remove.go [39:144]
func remove(
ctx context.Context,
logger log.Logger,
locator storage.Locator,
txManager transaction.Manager,
repository storage.Repository,
removeAll func(string) error,
) error {
path, err := locator.GetRepoPath(ctx, repository, storage.WithRepositoryVerificationSkipped())
if err != nil {
return structerr.NewInternal("%w", err)
}
if tx := storage.ExtractTransaction(ctx); tx != nil {
tx.DeleteRepository()
originalRelativePath, err := filepath.Rel(tx.FS().Root(), path)
if err != nil {
return fmt.Errorf("original relative path: %w", err)
}
if err := storage.RecordDirectoryRemoval(tx.FS(), tx.FS().Root(), originalRelativePath); err != nil {
return fmt.Errorf("record directory removal: %w", err)
}
if err := tx.KV().Delete(storage.RepositoryKey(originalRelativePath)); err != nil {
return fmt.Errorf("delete repository key: %w", err)
}
}
tempDir, err := locator.TempDir(repository.GetStorageName())
if err != nil {
return structerr.NewInternal("temporary directory: %w", err)
}
if err := os.MkdirAll(tempDir, mode.Directory); err != nil {
return structerr.NewInternal("%w", err)
}
// Check whether the repository exists. If not, then there is nothing we can
// remove. Historically, we didn't return an error in this case, which was just
// plain bad RPC design: callers should be able to act on this, and if they don't
// care they may still just return `NotFound` errors.
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return structerr.NewNotFound("repository does not exist")
}
return structerr.NewInternal("statting repository: %w", err)
}
// Lock the repository such that it cannot be created or removed by any concurrent
// RPC call.
unlock, err := Lock(ctx, logger, locator, repository)
if err != nil {
if errors.Is(err, safe.ErrFileAlreadyLocked) {
return structerr.NewFailedPrecondition("repository is already locked")
}
return structerr.NewInternal("locking repository for removal: %w", err)
}
defer unlock()
// Recheck whether the repository still exists after we have taken the lock. It
// could be a concurrent RPC call removed the repository while we have not yet been
// holding the lock.
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return structerr.NewNotFound("repository was concurrently removed")
}
return structerr.NewInternal("re-statting repository: %w", err)
}
if err := voteOnAction(ctx, txManager, repository, voting.Prepared); err != nil {
return structerr.NewInternal("vote on rename: %w", err)
}
destDir, err := os.MkdirTemp(tempDir, filepath.Base(path)+"+removed-*")
if err != nil {
return fmt.Errorf("mkdir temp: %w", err)
}
defer func() {
if err := removeAll(destDir); err != nil {
logger.WithError(err).ErrorContext(ctx, "failed removing repository from temporary directory")
}
}()
// We move the repository into our temporary directory first before we start to
// delete it. This is done such that we don't leave behind a partially-removed and
// thus likely corrupt repository.
if err := os.Rename(path, filepath.Join(destDir, "repo")); err != nil {
return structerr.NewInternal("staging repository for removal: %w", err)
}
if storage.NeedsSync(ctx) {
if err := safe.NewSyncer().SyncParent(ctx, path); err != nil {
return fmt.Errorf("sync removal: %w", err)
}
}
if err := voteOnAction(ctx, txManager, repository, voting.Committed); err != nil {
return structerr.NewInternal("vote on finalizing: %w", err)
}
return nil
}