in registry/handlers/repositories.go [1140:1227]
func handleRenameStoreOperation(ctx context.Context, w http.ResponseWriter, repo renameStoreParams, cache *iredis.Cache, db *datastore.DB) (bool, error) {
// create a repository lease store
var (
opts []datastore.RepositoryLeaseStoreOption
isRenamed bool
)
opts = append(opts, datastore.WithRepositoryLeaseCache(datastore.NewCentralRepositoryLeaseCache(cache)))
rlstore := datastore.NewRepositoryLeaseStore(opts...)
// verify a valid rename lease exists or create one if one can be created
lease, err := enforceRenameLease(ctx, rlstore, repo.newPath, repo.source.Path)
if err != nil {
return isRenamed, errcode.FromUnknownError(err)
}
// set a time limit for the rename operation query (both dry-run and real run)
var repositoryRenameOperationTTL time.Duration
if repo.isDryRun {
repositoryRenameOperationTTL = defaultDryRunRenameOperationTimeout
} else {
// selects the lower of `defaultDryRunRenameOperationTimeout` and the TTL of the lease
repositoryRenameOperationTTL, err = getDynamicRenameOperationTTL(ctx, rlstore, lease)
if err != nil {
return isRenamed, errcode.FromUnknownError(err)
}
}
if !repo.isDryRun {
// enact a lease on the source project path which will be used to block all
// write operations to the existing repositories in the given GitLab project.
plStore, err := datastore.NewProjectLeaseStore(datastore.NewCentralProjectLeaseCache(cache))
if err != nil {
return isRenamed, errcode.FromUnknownError(err)
}
// this lease expires in less than repositoryRenameOperationTTL + 1 second.
// where repositoryRenameOperationTTL is at most 5 seconds.
if err := plStore.Set(ctx, repo.source.Path, (repositoryRenameOperationTTL + 1*time.Second)); err != nil {
return isRenamed, errcode.FromUnknownError(err)
}
defer func() {
if err := plStore.Invalidate(ctx, repo.source.Path); err != nil {
errortracking.Capture(err, errortracking.WithContext(ctx), errortracking.WithStackTrace())
}
}()
}
// start a transaction to rename the repository (and sub-repository attributes)
// and specify a timeout limit to prevent long running repository rename operations
txCtx, cancel := context.WithTimeout(ctx, repositoryRenameOperationTTL)
defer cancel()
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return isRenamed, errcode.FromUnknownError(fmt.Errorf("failed to create database transaction: %w", err))
}
defer tx.Rollback()
// run the rename operation in a transaction
if err = executeRenameOperation(txCtx, tx, repo.source, repo.isPathOriginRepo, repo.newPath, repo.newName); err != nil {
return isRenamed, errcode.FromUnknownError(err)
}
// only commit the transaction if the request was not a dry-run
if !repo.isDryRun {
if err := tx.Commit(); err != nil {
return isRenamed, errcode.FromUnknownError(fmt.Errorf("failed to commit database transaction: %w", err))
}
w.WriteHeader(http.StatusNoContent)
isRenamed = true
// When a lease fails to be destroyed after it is no longer needed it should not impact the response to the caller.
// The lease will eventually expire regardless, but we still need to record these failed cases.
if err := rlstore.Destroy(ctx, lease); err != nil {
errortracking.Capture(err, errortracking.WithContext(ctx), errortracking.WithStackTrace())
}
} else {
w.WriteHeader(http.StatusAccepted)
w.Header().Set("Content-Type", "application/json")
repositoryRenameOperationTTL, err = rlstore.GetTTL(ctx, lease)
if err != nil {
return isRenamed, errcode.FromUnknownError(err)
}
if err := json.NewEncoder(w).Encode(&RenameRepositoryAPIResponse{TTL: time.Now().Add(repositoryRenameOperationTTL).UTC()}); err != nil {
return isRenamed, errcode.FromUnknownError(err)
}
}
return isRenamed, nil
}