diff options
67 files changed, 2184 insertions, 2827 deletions
diff --git a/models/actions/run.go b/models/actions/run.go index f0ab61b200..f5ccba06c2 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -282,77 +282,72 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin // InsertRun inserts a run // The title will be cut off at 255 characters if it's longer than 255 characters. func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID) - if err != nil { - return err - } - run.Index = index - run.Title = util.EllipsisDisplayString(run.Title, 255) - - if err := db.Insert(ctx, run); err != nil { - return err - } - - if run.Repo == nil { - repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID) + return db.WithTx(ctx, func(ctx context.Context) error { + index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID) if err != nil { return err } - run.Repo = repo - } + run.Index = index + run.Title = util.EllipsisDisplayString(run.Title, 255) - if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil { - return err - } - - runJobs := make([]*ActionRunJob, 0, len(jobs)) - var hasWaiting bool - for _, v := range jobs { - id, job := v.Job() - needs := job.Needs() - if err := v.SetJob(id, job.EraseNeeds()); err != nil { + if err := db.Insert(ctx, run); err != nil { return err } - payload, _ := v.Marshal() - status := StatusWaiting - if len(needs) > 0 || run.NeedApproval { - status = StatusBlocked - } else { - hasWaiting = true + + if run.Repo == nil { + repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID) + if err != nil { + return err + } + run.Repo = repo } - job.Name = util.EllipsisDisplayString(job.Name, 255) - runJobs = append(runJobs, &ActionRunJob{ - RunID: run.ID, - RepoID: run.RepoID, - OwnerID: run.OwnerID, - CommitSHA: run.CommitSHA, - IsForkPullRequest: run.IsForkPullRequest, - Name: job.Name, - WorkflowPayload: payload, - JobID: id, - Needs: needs, - RunsOn: job.RunsOn(), - Status: status, - }) - } - if err := db.Insert(ctx, runJobs); err != nil { - return err - } - // if there is a job in the waiting status, increase tasks version. - if hasWaiting { - if err := IncreaseTaskVersion(ctx, run.OwnerID, run.RepoID); err != nil { + if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil { return err } - } - return committer.Commit() + runJobs := make([]*ActionRunJob, 0, len(jobs)) + var hasWaiting bool + for _, v := range jobs { + id, job := v.Job() + needs := job.Needs() + if err := v.SetJob(id, job.EraseNeeds()); err != nil { + return err + } + payload, _ := v.Marshal() + status := StatusWaiting + if len(needs) > 0 || run.NeedApproval { + status = StatusBlocked + } else { + hasWaiting = true + } + job.Name = util.EllipsisDisplayString(job.Name, 255) + runJobs = append(runJobs, &ActionRunJob{ + RunID: run.ID, + RepoID: run.RepoID, + OwnerID: run.OwnerID, + CommitSHA: run.CommitSHA, + IsForkPullRequest: run.IsForkPullRequest, + Name: job.Name, + WorkflowPayload: payload, + JobID: id, + Needs: needs, + RunsOn: job.RunsOn(), + Status: status, + }) + } + if err := db.Insert(ctx, runJobs); err != nil { + return err + } + + // if there is a job in the waiting status, increase tasks version. + if hasWaiting { + if err := IncreaseTaskVersion(ctx, run.OwnerID, run.RepoID); err != nil { + return err + } + } + return nil + }) } func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, error) { diff --git a/models/actions/schedule.go b/models/actions/schedule.go index 2edf483fe0..ffde5092e0 100644 --- a/models/actions/schedule.go +++ b/models/actions/schedule.go @@ -56,65 +56,54 @@ func CreateScheduleTask(ctx context.Context, rows []*ActionSchedule) error { return nil } - // Begin transaction - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - // Loop through each schedule row - for _, row := range rows { - row.Title = util.EllipsisDisplayString(row.Title, 255) - // Create new schedule row - if err = db.Insert(ctx, row); err != nil { - return err - } - - // Loop through each schedule spec and create a new spec row - now := time.Now() - - for _, spec := range row.Specs { - specRow := &ActionScheduleSpec{ - RepoID: row.RepoID, - ScheduleID: row.ID, - Spec: spec, - } - // Parse the spec and check for errors - schedule, err := specRow.Parse() - if err != nil { - continue // skip to the next spec if there's an error + return db.WithTx(ctx, func(ctx context.Context) error { + // Loop through each schedule row + for _, row := range rows { + row.Title = util.EllipsisDisplayString(row.Title, 255) + // Create new schedule row + if err := db.Insert(ctx, row); err != nil { + return err } - specRow.Next = timeutil.TimeStamp(schedule.Next(now).Unix()) - - // Insert the new schedule spec row - if err = db.Insert(ctx, specRow); err != nil { - return err + // Loop through each schedule spec and create a new spec row + now := time.Now() + + for _, spec := range row.Specs { + specRow := &ActionScheduleSpec{ + RepoID: row.RepoID, + ScheduleID: row.ID, + Spec: spec, + } + // Parse the spec and check for errors + schedule, err := specRow.Parse() + if err != nil { + continue // skip to the next spec if there's an error + } + + specRow.Next = timeutil.TimeStamp(schedule.Next(now).Unix()) + + // Insert the new schedule spec row + if err = db.Insert(ctx, specRow); err != nil { + return err + } } } - } - - // Commit transaction - return committer.Commit() + return nil + }) } func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if _, err := db.GetEngine(ctx).Delete(&ActionSchedule{RepoID: id}); err != nil { - return err - } + return db.WithTx(ctx, func(ctx context.Context) error { + if _, err := db.GetEngine(ctx).Delete(&ActionSchedule{RepoID: id}); err != nil { + return err + } - if _, err := db.GetEngine(ctx).Delete(&ActionScheduleSpec{RepoID: id}); err != nil { - return err - } + if _, err := db.GetEngine(ctx).Delete(&ActionScheduleSpec{RepoID: id}); err != nil { + return err + } - return committer.Commit() + return nil + }) } func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) ([]*ActionRunJob, error) { diff --git a/models/actions/task.go b/models/actions/task.go index e0756b10c2..c1306a8418 100644 --- a/models/actions/task.go +++ b/models/actions/task.go @@ -352,78 +352,70 @@ func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.Task stepStates[v.Id] = v } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - e := db.GetEngine(ctx) - - task := &ActionTask{} - if has, err := e.ID(state.Id).Get(task); err != nil { - return nil, err - } else if !has { - return nil, util.ErrNotExist - } else if runnerID != task.RunnerID { - return nil, errors.New("invalid runner for task") - } - - if task.Status.IsDone() { - // the state is final, do nothing - return task, nil - } + return db.WithTx2(ctx, func(ctx context.Context) (*ActionTask, error) { + e := db.GetEngine(ctx) - // state.Result is not unspecified means the task is finished - if state.Result != runnerv1.Result_RESULT_UNSPECIFIED { - task.Status = Status(state.Result) - task.Stopped = timeutil.TimeStamp(state.StoppedAt.AsTime().Unix()) - if err := UpdateTask(ctx, task, "status", "stopped"); err != nil { + task := &ActionTask{} + if has, err := e.ID(state.Id).Get(task); err != nil { return nil, err + } else if !has { + return nil, util.ErrNotExist + } else if runnerID != task.RunnerID { + return nil, errors.New("invalid runner for task") } - if _, err := UpdateRunJob(ctx, &ActionRunJob{ - ID: task.JobID, - Status: task.Status, - Stopped: task.Stopped, - }, nil); err != nil { - return nil, err - } - } else { - // Force update ActionTask.Updated to avoid the task being judged as a zombie task - task.Updated = timeutil.TimeStampNow() - if err := UpdateTask(ctx, task, "updated"); err != nil { - return nil, err - } - } - - if err := task.LoadAttributes(ctx); err != nil { - return nil, err - } - for _, step := range task.Steps { - var result runnerv1.Result - if v, ok := stepStates[step.Index]; ok { - result = v.Result - step.LogIndex = v.LogIndex - step.LogLength = v.LogLength - step.Started = convertTimestamp(v.StartedAt) - step.Stopped = convertTimestamp(v.StoppedAt) + if task.Status.IsDone() { + // the state is final, do nothing + return task, nil } - if result != runnerv1.Result_RESULT_UNSPECIFIED { - step.Status = Status(result) - } else if step.Started != 0 { - step.Status = StatusRunning + + // state.Result is not unspecified means the task is finished + if state.Result != runnerv1.Result_RESULT_UNSPECIFIED { + task.Status = Status(state.Result) + task.Stopped = timeutil.TimeStamp(state.StoppedAt.AsTime().Unix()) + if err := UpdateTask(ctx, task, "status", "stopped"); err != nil { + return nil, err + } + if _, err := UpdateRunJob(ctx, &ActionRunJob{ + ID: task.JobID, + Status: task.Status, + Stopped: task.Stopped, + }, nil); err != nil { + return nil, err + } + } else { + // Force update ActionTask.Updated to avoid the task being judged as a zombie task + task.Updated = timeutil.TimeStampNow() + if err := UpdateTask(ctx, task, "updated"); err != nil { + return nil, err + } } - if _, err := e.ID(step.ID).Update(step); err != nil { + + if err := task.LoadAttributes(ctx); err != nil { return nil, err } - } - if err := committer.Commit(); err != nil { - return nil, err - } + for _, step := range task.Steps { + var result runnerv1.Result + if v, ok := stepStates[step.Index]; ok { + result = v.Result + step.LogIndex = v.LogIndex + step.LogLength = v.LogLength + step.Started = convertTimestamp(v.StartedAt) + step.Stopped = convertTimestamp(v.StoppedAt) + } + if result != runnerv1.Result_RESULT_UNSPECIFIED { + step.Status = Status(result) + } else if step.Started != 0 { + step.Status = StatusRunning + } + if _, err := e.ID(step.ID).Update(step); err != nil { + return nil, err + } + } - return task, nil + return task, nil + }) } func StopTask(ctx context.Context, taskID int64, status Status) error { diff --git a/models/actions/tasks_version.go b/models/actions/tasks_version.go index 96c5468c1a..b686ce2443 100644 --- a/models/actions/tasks_version.go +++ b/models/actions/tasks_version.go @@ -73,33 +73,29 @@ func increaseTasksVersionByScope(ctx context.Context, ownerID, repoID int64) err } func IncreaseTaskVersion(ctx context.Context, ownerID, repoID int64) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - // 1. increase global - if err := increaseTasksVersionByScope(ctx, 0, 0); err != nil { - log.Error("IncreaseTasksVersionByScope(Global): %v", err) - return err - } - - // 2. increase owner - if ownerID > 0 { - if err := increaseTasksVersionByScope(ctx, ownerID, 0); err != nil { - log.Error("IncreaseTasksVersionByScope(Owner): %v", err) + return db.WithTx(ctx, func(ctx context.Context) error { + // 1. increase global + if err := increaseTasksVersionByScope(ctx, 0, 0); err != nil { + log.Error("IncreaseTasksVersionByScope(Global): %v", err) return err } - } - // 3. increase repo - if repoID > 0 { - if err := increaseTasksVersionByScope(ctx, 0, repoID); err != nil { - log.Error("IncreaseTasksVersionByScope(Repo): %v", err) - return err + // 2. increase owner + if ownerID > 0 { + if err := increaseTasksVersionByScope(ctx, ownerID, 0); err != nil { + log.Error("IncreaseTasksVersionByScope(Owner): %v", err) + return err + } + } + + // 3. increase repo + if repoID > 0 { + if err := increaseTasksVersionByScope(ctx, 0, repoID); err != nil { + log.Error("IncreaseTasksVersionByScope(Repo): %v", err) + return err + } } - } - return committer.Commit() + return nil + }) } diff --git a/models/activities/notification_list.go b/models/activities/notification_list.go index b47f5dc404..6539e14ea2 100644 --- a/models/activities/notification_list.go +++ b/models/activities/notification_list.go @@ -70,17 +70,9 @@ func (opts FindNotificationOptions) ToOrders() string { // for each watcher, or updates it if already exists // receiverID > 0 just send to receiver, else send to all watcher func CreateOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err := createOrUpdateIssueNotifications(ctx, issueID, commentID, notificationAuthorID, receiverID); err != nil { - return err - } - - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + return createOrUpdateIssueNotifications(ctx, issueID, commentID, notificationAuthorID, receiverID) + }) } func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { diff --git a/models/asymkey/gpg_key.go b/models/asymkey/gpg_key.go index 220f46ad1d..38de7cbda6 100644 --- a/models/asymkey/gpg_key.go +++ b/models/asymkey/gpg_key.go @@ -228,17 +228,10 @@ func DeleteGPGKey(ctx context.Context, doer *user_model.User, id int64) (err err return fmt.Errorf("GetPublicKeyByID: %w", err) } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if _, err = deleteGPGKey(ctx, key.KeyID); err != nil { + return db.WithTx(ctx, func(ctx context.Context) error { + _, err = deleteGPGKey(ctx, key.KeyID) return err - } - - return committer.Commit() + }) } func FindGPGKeyWithSubKeys(ctx context.Context, keyID string) ([]*GPGKey, error) { diff --git a/models/asymkey/gpg_key_verify.go b/models/asymkey/gpg_key_verify.go index 5ab2fd8081..55c64973b4 100644 --- a/models/asymkey/gpg_key_verify.go +++ b/models/asymkey/gpg_key_verify.go @@ -14,97 +14,76 @@ import ( "code.gitea.io/gitea/modules/log" ) -// __________________ ________ ____ __. -// / _____/\______ \/ _____/ | |/ _|____ ___.__. -// / \ ___ | ___/ \ ___ | <_/ __ < | | -// \ \_\ \| | \ \_\ \ | | \ ___/\___ | -// \______ /|____| \______ / |____|__ \___ > ____| -// \/ \/ \/ \/\/ -// ____ ____ .__ _____ -// \ \ / /___________|__|/ ____\__.__. -// \ Y // __ \_ __ \ \ __< | | -// \ /\ ___/| | \/ || | \___ | -// \___/ \___ >__| |__||__| / ____| -// \/ \/ - // This file provides functions relating verifying gpg keys // VerifyGPGKey marks a GPG key as verified func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature string) (string, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return "", err - } - defer committer.Close() - - key := new(GPGKey) - - has, err := db.GetEngine(ctx).Where("owner_id = ? AND key_id = ?", ownerID, keyID).Get(key) - if err != nil { - return "", err - } else if !has { - return "", ErrGPGKeyNotExist{} - } - - if err := key.LoadSubKeys(ctx); err != nil { - return "", err - } + return db.WithTx2(ctx, func(ctx context.Context) (string, error) { + key := new(GPGKey) - sig, err := ExtractSignature(signature) - if err != nil { - return "", ErrGPGInvalidTokenSignature{ - ID: key.KeyID, - Wrapped: err, + has, err := db.GetEngine(ctx).Where("owner_id = ? AND key_id = ?", ownerID, keyID).Get(key) + if err != nil { + return "", err + } else if !has { + return "", ErrGPGKeyNotExist{} } - } - signer, err := hashAndVerifyWithSubKeys(sig, token, key) - if err != nil { - return "", ErrGPGInvalidTokenSignature{ - ID: key.KeyID, - Wrapped: err, + if err := key.LoadSubKeys(ctx); err != nil { + return "", err } - } - if signer == nil { - signer, err = hashAndVerifyWithSubKeys(sig, token+"\n", key) + + sig, err := ExtractSignature(signature) if err != nil { return "", ErrGPGInvalidTokenSignature{ ID: key.KeyID, Wrapped: err, } } - } - if signer == nil { - signer, err = hashAndVerifyWithSubKeys(sig, token+"\n\n", key) + + signer, err := hashAndVerifyWithSubKeys(sig, token, key) if err != nil { return "", ErrGPGInvalidTokenSignature{ ID: key.KeyID, Wrapped: err, } } - } - - if signer == nil { - log.Debug("VerifyGPGKey failed: no signer") - return "", ErrGPGInvalidTokenSignature{ - ID: key.KeyID, + if signer == nil { + signer, err = hashAndVerifyWithSubKeys(sig, token+"\n", key) + if err != nil { + return "", ErrGPGInvalidTokenSignature{ + ID: key.KeyID, + Wrapped: err, + } + } + } + if signer == nil { + signer, err = hashAndVerifyWithSubKeys(sig, token+"\n\n", key) + if err != nil { + return "", ErrGPGInvalidTokenSignature{ + ID: key.KeyID, + Wrapped: err, + } + } } - } - if signer.PrimaryKeyID != key.KeyID && signer.KeyID != key.KeyID { - return "", ErrGPGKeyNotExist{} - } + if signer == nil { + log.Debug("VerifyGPGKey failed: no signer") + return "", ErrGPGInvalidTokenSignature{ + ID: key.KeyID, + } + } - key.Verified = true - if _, err := db.GetEngine(ctx).ID(key.ID).SetExpr("verified", true).Update(new(GPGKey)); err != nil { - return "", err - } + if signer.PrimaryKeyID != key.KeyID && signer.KeyID != key.KeyID { + return "", ErrGPGKeyNotExist{} + } - if err := committer.Commit(); err != nil { - return "", err - } + key.Verified = true + if _, err := db.GetEngine(ctx).ID(key.ID).SetExpr("verified", true).Update(new(GPGKey)); err != nil { + return "", err + } - return key.KeyID, nil + return key.KeyID, nil + }) } // VerificationToken returns token for the user that will be valid in minutes (time) diff --git a/models/asymkey/ssh_key.go b/models/asymkey/ssh_key.go index dd94070fb9..87205f0651 100644 --- a/models/asymkey/ssh_key.go +++ b/models/asymkey/ssh_key.go @@ -99,40 +99,36 @@ func AddPublicKey(ctx context.Context, ownerID int64, name, content string, auth return nil, err } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - if err := checkKeyFingerprint(ctx, fingerprint); err != nil { - return nil, err - } + return db.WithTx2(ctx, func(ctx context.Context) (*PublicKey, error) { + if err := checkKeyFingerprint(ctx, fingerprint); err != nil { + return nil, err + } - // Key name of same user cannot be duplicated. - has, err := db.GetEngine(ctx). - Where("owner_id = ? AND name = ?", ownerID, name). - Get(new(PublicKey)) - if err != nil { - return nil, err - } else if has { - return nil, ErrKeyNameAlreadyUsed{ownerID, name} - } + // Key name of same user cannot be duplicated. + has, err := db.GetEngine(ctx). + Where("owner_id = ? AND name = ?", ownerID, name). + Get(new(PublicKey)) + if err != nil { + return nil, err + } else if has { + return nil, ErrKeyNameAlreadyUsed{ownerID, name} + } - key := &PublicKey{ - OwnerID: ownerID, - Name: name, - Fingerprint: fingerprint, - Content: content, - Mode: perm.AccessModeWrite, - Type: KeyTypeUser, - LoginSourceID: authSourceID, - } - if err = addKey(ctx, key); err != nil { - return nil, fmt.Errorf("addKey: %w", err) - } + key := &PublicKey{ + OwnerID: ownerID, + Name: name, + Fingerprint: fingerprint, + Content: content, + Mode: perm.AccessModeWrite, + Type: KeyTypeUser, + LoginSourceID: authSourceID, + } + if err = addKey(ctx, key); err != nil { + return nil, fmt.Errorf("addKey: %w", err) + } - return key, committer.Commit() + return key, nil + }) } // GetPublicKeyByID returns public key by given ID. @@ -288,33 +284,24 @@ func PublicKeyIsExternallyManaged(ctx context.Context, id int64) (bool, error) { // deleteKeysMarkedForDeletion returns true if ssh keys needs update func deleteKeysMarkedForDeletion(ctx context.Context, keys []string) (bool, error) { - // Start session - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return false, err - } - defer committer.Close() - - // Delete keys marked for deletion - var sshKeysNeedUpdate bool - for _, KeyToDelete := range keys { - key, err := SearchPublicKeyByContent(ctx, KeyToDelete) - if err != nil { - log.Error("SearchPublicKeyByContent: %v", err) - continue - } - if _, err = db.DeleteByID[PublicKey](ctx, key.ID); err != nil { - log.Error("DeleteByID[PublicKey]: %v", err) - continue + return db.WithTx2(ctx, func(ctx context.Context) (bool, error) { + // Delete keys marked for deletion + var sshKeysNeedUpdate bool + for _, KeyToDelete := range keys { + key, err := SearchPublicKeyByContent(ctx, KeyToDelete) + if err != nil { + log.Error("SearchPublicKeyByContent: %v", err) + continue + } + if _, err = db.DeleteByID[PublicKey](ctx, key.ID); err != nil { + log.Error("DeleteByID[PublicKey]: %v", err) + continue + } + sshKeysNeedUpdate = true } - sshKeysNeedUpdate = true - } - if err := committer.Commit(); err != nil { - return false, err - } - - return sshKeysNeedUpdate, nil + return sshKeysNeedUpdate, nil + }) } // AddPublicKeysBySource add a users public keys. Returns true if there are changes. diff --git a/models/asymkey/ssh_key_deploy.go b/models/asymkey/ssh_key_deploy.go index 923c5020ed..4ab84eabcf 100644 --- a/models/asymkey/ssh_key_deploy.go +++ b/models/asymkey/ssh_key_deploy.go @@ -125,39 +125,35 @@ func AddDeployKey(ctx context.Context, repoID int64, name, content string, readO accessMode = perm.AccessModeWrite } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - pkey, exist, err := db.Get[PublicKey](ctx, builder.Eq{"fingerprint": fingerprint}) - if err != nil { - return nil, err - } else if exist { - if pkey.Type != KeyTypeDeploy { - return nil, ErrKeyAlreadyExist{0, fingerprint, ""} - } - } else { - // First time use this deploy key. - pkey = &PublicKey{ - Fingerprint: fingerprint, - Mode: accessMode, - Type: KeyTypeDeploy, - Content: content, - Name: name, + return db.WithTx2(ctx, func(ctx context.Context) (*DeployKey, error) { + pkey, exist, err := db.Get[PublicKey](ctx, builder.Eq{"fingerprint": fingerprint}) + if err != nil { + return nil, err + } else if exist { + if pkey.Type != KeyTypeDeploy { + return nil, ErrKeyAlreadyExist{0, fingerprint, ""} + } + } else { + // First time use this deploy key. + pkey = &PublicKey{ + Fingerprint: fingerprint, + Mode: accessMode, + Type: KeyTypeDeploy, + Content: content, + Name: name, + } + if err = addKey(ctx, pkey); err != nil { + return nil, fmt.Errorf("addKey: %w", err) + } } - if err = addKey(ctx, pkey); err != nil { - return nil, fmt.Errorf("addKey: %w", err) - } - } - key, err := addDeployKey(ctx, pkey.ID, repoID, name, pkey.Fingerprint, accessMode) - if err != nil { - return nil, err - } + key, err := addDeployKey(ctx, pkey.ID, repoID, name, pkey.Fingerprint, accessMode) + if err != nil { + return nil, err + } - return key, committer.Commit() + return key, nil + }) } // GetDeployKeyByID returns deploy key by given ID. diff --git a/models/asymkey/ssh_key_verify.go b/models/asymkey/ssh_key_verify.go index 0cf29ca9f1..04917239ee 100644 --- a/models/asymkey/ssh_key_verify.go +++ b/models/asymkey/ssh_key_verify.go @@ -15,41 +15,33 @@ import ( // VerifySSHKey marks a SSH key as verified func VerifySSHKey(ctx context.Context, ownerID int64, fingerprint, token, signature string) (string, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return "", err - } - defer committer.Close() - - key := new(PublicKey) - - has, err := db.GetEngine(ctx).Where("owner_id = ? AND fingerprint = ?", ownerID, fingerprint).Get(key) - if err != nil { - return "", err - } else if !has { - return "", ErrKeyNotExist{} - } - - err = sshsig.Verify(strings.NewReader(token), []byte(signature), []byte(key.Content), "gitea") - if err != nil { - // edge case for Windows based shells that will add CR LF if piped to ssh-keygen command - // see https://github.com/PowerShell/PowerShell/issues/5974 - if sshsig.Verify(strings.NewReader(token+"\r\n"), []byte(signature), []byte(key.Content), "gitea") != nil { - log.Debug("VerifySSHKey sshsig.Verify failed: %v", err) - return "", ErrSSHInvalidTokenSignature{ - Fingerprint: key.Fingerprint, - } + return db.WithTx2(ctx, func(ctx context.Context) (string, error) { + key := new(PublicKey) + + has, err := db.GetEngine(ctx).Where("owner_id = ? AND fingerprint = ?", ownerID, fingerprint).Get(key) + if err != nil { + return "", err + } else if !has { + return "", ErrKeyNotExist{} } - } - key.Verified = true - if _, err := db.GetEngine(ctx).ID(key.ID).Cols("verified").Update(key); err != nil { - return "", err - } + err = sshsig.Verify(strings.NewReader(token), []byte(signature), []byte(key.Content), "gitea") + if err != nil { + // edge case for Windows based shells that will add CR LF if piped to ssh-keygen command + // see https://github.com/PowerShell/PowerShell/issues/5974 + if sshsig.Verify(strings.NewReader(token+"\r\n"), []byte(signature), []byte(key.Content), "gitea") != nil { + log.Debug("VerifySSHKey sshsig.Verify failed: %v", err) + return "", ErrSSHInvalidTokenSignature{ + Fingerprint: key.Fingerprint, + } + } + } - if err := committer.Commit(); err != nil { - return "", err - } + key.Verified = true + if _, err := db.GetEngine(ctx).ID(key.ID).Cols("verified").Update(key); err != nil { + return "", err + } - return key.Fingerprint, nil + return key.Fingerprint, nil + }) } diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index 55af4e9036..d664841306 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -289,35 +289,31 @@ type UpdateOAuth2ApplicationOptions struct { // UpdateOAuth2Application updates an oauth2 application func UpdateOAuth2Application(ctx context.Context, opts UpdateOAuth2ApplicationOptions) (*OAuth2Application, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - app, err := GetOAuth2ApplicationByID(ctx, opts.ID) - if err != nil { - return nil, err - } - if app.UID != opts.UserID { - return nil, errors.New("UID mismatch") - } - builtinApps := BuiltinApplications() - if _, builtin := builtinApps[app.ClientID]; builtin { - return nil, fmt.Errorf("failed to edit OAuth2 application: application is locked: %s", app.ClientID) - } + return db.WithTx2(ctx, func(ctx context.Context) (*OAuth2Application, error) { + app, err := GetOAuth2ApplicationByID(ctx, opts.ID) + if err != nil { + return nil, err + } + if app.UID != opts.UserID { + return nil, errors.New("UID mismatch") + } + builtinApps := BuiltinApplications() + if _, builtin := builtinApps[app.ClientID]; builtin { + return nil, fmt.Errorf("failed to edit OAuth2 application: application is locked: %s", app.ClientID) + } - app.Name = opts.Name - app.RedirectURIs = opts.RedirectURIs - app.ConfidentialClient = opts.ConfidentialClient - app.SkipSecondaryAuthorization = opts.SkipSecondaryAuthorization + app.Name = opts.Name + app.RedirectURIs = opts.RedirectURIs + app.ConfidentialClient = opts.ConfidentialClient + app.SkipSecondaryAuthorization = opts.SkipSecondaryAuthorization - if err = updateOAuth2Application(ctx, app); err != nil { - return nil, err - } - app.ClientSecret = "" + if err = updateOAuth2Application(ctx, app); err != nil { + return nil, err + } + app.ClientSecret = "" - return app, committer.Commit() + return app, nil + }) } func updateOAuth2Application(ctx context.Context, app *OAuth2Application) error { @@ -358,23 +354,17 @@ func deleteOAuth2Application(ctx context.Context, id, userid int64) error { // DeleteOAuth2Application deletes the application with the given id and the grants and auth codes related to it. It checks if the userid was the creator of the app. func DeleteOAuth2Application(ctx context.Context, id, userid int64) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - app, err := GetOAuth2ApplicationByID(ctx, id) - if err != nil { - return err - } - builtinApps := BuiltinApplications() - if _, builtin := builtinApps[app.ClientID]; builtin { - return fmt.Errorf("failed to delete OAuth2 application: application is locked: %s", app.ClientID) - } - if err := deleteOAuth2Application(ctx, id, userid); err != nil { - return err - } - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + app, err := GetOAuth2ApplicationByID(ctx, id) + if err != nil { + return err + } + builtinApps := BuiltinApplications() + if _, builtin := builtinApps[app.ClientID]; builtin { + return fmt.Errorf("failed to delete OAuth2 application: application is locked: %s", app.ClientID) + } + return deleteOAuth2Application(ctx, id, userid) + }) } ////////////////////////////////////////////////////// diff --git a/models/auth/session.go b/models/auth/session.go index 75a205f702..0378d0ec6f 100644 --- a/models/auth/session.go +++ b/models/auth/session.go @@ -35,26 +35,22 @@ func UpdateSession(ctx context.Context, key string, data []byte) error { // ReadSession reads the data for the provided session func ReadSession(ctx context.Context, key string) (*Session, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - session, exist, err := db.Get[Session](ctx, builder.Eq{"`key`": key}) - if err != nil { - return nil, err - } else if !exist { - session = &Session{ - Key: key, - Expiry: timeutil.TimeStampNow(), - } - if err := db.Insert(ctx, session); err != nil { + return db.WithTx2(ctx, func(ctx context.Context) (*Session, error) { + session, exist, err := db.Get[Session](ctx, builder.Eq{"`key`": key}) + if err != nil { return nil, err + } else if !exist { + session = &Session{ + Key: key, + Expiry: timeutil.TimeStampNow(), + } + if err := db.Insert(ctx, session); err != nil { + return nil, err + } } - } - return session, committer.Commit() + return session, nil + }) } // ExistSession checks if a session exists @@ -72,40 +68,36 @@ func DestroySession(ctx context.Context, key string) error { // RegenerateSession regenerates a session from the old id func RegenerateSession(ctx context.Context, oldKey, newKey string) (*Session, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": newKey}); err != nil { - return nil, err - } else if has { - return nil, fmt.Errorf("session Key: %s already exists", newKey) - } - - if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": oldKey}); err != nil { - return nil, err - } else if !has { - if err := db.Insert(ctx, &Session{ - Key: oldKey, - Expiry: timeutil.TimeStampNow(), - }); err != nil { + return db.WithTx2(ctx, func(ctx context.Context) (*Session, error) { + if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": newKey}); err != nil { + return nil, err + } else if has { + return nil, fmt.Errorf("session Key: %s already exists", newKey) + } + + if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": oldKey}); err != nil { return nil, err + } else if !has { + if err := db.Insert(ctx, &Session{ + Key: oldKey, + Expiry: timeutil.TimeStampNow(), + }); err != nil { + return nil, err + } } - } - if _, err := db.Exec(ctx, "UPDATE "+db.TableName(&Session{})+" SET `key` = ? WHERE `key`=?", newKey, oldKey); err != nil { - return nil, err - } + if _, err := db.Exec(ctx, "UPDATE "+db.TableName(&Session{})+" SET `key` = ? WHERE `key`=?", newKey, oldKey); err != nil { + return nil, err + } - s, _, err := db.Get[Session](ctx, builder.Eq{"`key`": newKey}) - if err != nil { - // is not exist, it should be impossible - return nil, err - } + s, _, err := db.Get[Session](ctx, builder.Eq{"`key`": newKey}) + if err != nil { + // is not exist, it should be impossible + return nil, err + } - return s, committer.Commit() + return s, nil + }) } // CountSessions returns the number of sessions diff --git a/models/git/commit_status.go b/models/git/commit_status.go index f85e1b15e5..e255bca5d0 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -470,35 +470,31 @@ func NewCommitStatus(ctx context.Context, opts NewCommitStatusOptions) error { return fmt.Errorf("NewCommitStatus[%s, %s]: no user specified", opts.Repo.FullName(), opts.SHA) } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", opts.Repo.ID, opts.Creator.ID, opts.SHA, err) - } - defer committer.Close() - - // Get the next Status Index - idx, err := GetNextCommitStatusIndex(ctx, opts.Repo.ID, opts.SHA.String()) - if err != nil { - return fmt.Errorf("generate commit status index failed: %w", err) - } + return db.WithTx(ctx, func(ctx context.Context) error { + // Get the next Status Index + idx, err := GetNextCommitStatusIndex(ctx, opts.Repo.ID, opts.SHA.String()) + if err != nil { + return fmt.Errorf("generate commit status index failed: %w", err) + } - opts.CommitStatus.Description = strings.TrimSpace(opts.CommitStatus.Description) - opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context) - opts.CommitStatus.TargetURL = strings.TrimSpace(opts.CommitStatus.TargetURL) - opts.CommitStatus.SHA = opts.SHA.String() - opts.CommitStatus.CreatorID = opts.Creator.ID - opts.CommitStatus.RepoID = opts.Repo.ID - opts.CommitStatus.Index = idx - log.Debug("NewCommitStatus[%s, %s]: %d", opts.Repo.FullName(), opts.SHA, opts.CommitStatus.Index) + opts.CommitStatus.Description = strings.TrimSpace(opts.CommitStatus.Description) + opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context) + opts.CommitStatus.TargetURL = strings.TrimSpace(opts.CommitStatus.TargetURL) + opts.CommitStatus.SHA = opts.SHA.String() + opts.CommitStatus.CreatorID = opts.Creator.ID + opts.CommitStatus.RepoID = opts.Repo.ID + opts.CommitStatus.Index = idx + log.Debug("NewCommitStatus[%s, %s]: %d", opts.Repo.FullName(), opts.SHA, opts.CommitStatus.Index) - opts.CommitStatus.ContextHash = hashCommitStatusContext(opts.CommitStatus.Context) + opts.CommitStatus.ContextHash = hashCommitStatusContext(opts.CommitStatus.Context) - // Insert new CommitStatus - if _, err = db.GetEngine(ctx).Insert(opts.CommitStatus); err != nil { - return fmt.Errorf("insert CommitStatus[%s, %s]: %w", opts.Repo.FullName(), opts.SHA, err) - } + // Insert new CommitStatus + if err = db.Insert(ctx, opts.CommitStatus); err != nil { + return fmt.Errorf("insert CommitStatus[%s, %s]: %w", opts.Repo.FullName(), opts.SHA, err) + } - return committer.Commit() + return nil + }) } // SignCommitWithStatuses represents a commit with validation of signature and status state. diff --git a/models/git/lfs.go b/models/git/lfs.go index e4fa2b446a..c471baf588 100644 --- a/models/git/lfs.go +++ b/models/git/lfs.go @@ -135,25 +135,18 @@ var ErrLFSObjectNotExist = db.ErrNotExist{Resource: "LFS Meta object"} // NewLFSMetaObject stores a given populated LFSMetaObject structure in the database // if it is not already present. func NewLFSMetaObject(ctx context.Context, repoID int64, p lfs.Pointer) (*LFSMetaObject, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - m, exist, err := db.Get[LFSMetaObject](ctx, builder.Eq{"repository_id": repoID, "oid": p.Oid}) if err != nil { return nil, err } else if exist { - return m, committer.Commit() + return m, nil } m = &LFSMetaObject{Pointer: p, RepositoryID: repoID} if err = db.Insert(ctx, m); err != nil { return nil, err } - - return m, committer.Commit() + return m, nil } // GetLFSMetaObjectByOid selects a LFSMetaObject entry from database by its OID. diff --git a/models/git/lfs_lock.go b/models/git/lfs_lock.go index 07ce7d4abf..c5f9a4e6de 100644 --- a/models/git/lfs_lock.go +++ b/models/git/lfs_lock.go @@ -70,32 +70,28 @@ func (l *LFSLock) LoadOwner(ctx context.Context) error { // CreateLFSLock creates a new lock. func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLock) (*LFSLock, error) { - dbCtx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - if err := CheckLFSAccessForRepo(dbCtx, lock.OwnerID, repo, perm.AccessModeWrite); err != nil { - return nil, err - } + return db.WithTx2(ctx, func(ctx context.Context) (*LFSLock, error) { + if err := CheckLFSAccessForRepo(ctx, lock.OwnerID, repo, perm.AccessModeWrite); err != nil { + return nil, err + } - lock.Path = util.PathJoinRel(lock.Path) - lock.RepoID = repo.ID + lock.Path = util.PathJoinRel(lock.Path) + lock.RepoID = repo.ID - l, err := GetLFSLock(dbCtx, repo, lock.Path) - if err == nil { - return l, ErrLFSLockAlreadyExist{lock.RepoID, lock.Path} - } - if !IsErrLFSLockNotExist(err) { - return nil, err - } + l, err := GetLFSLock(ctx, repo, lock.Path) + if err == nil { + return l, ErrLFSLockAlreadyExist{lock.RepoID, lock.Path} + } + if !IsErrLFSLockNotExist(err) { + return nil, err + } - if err := db.Insert(dbCtx, lock); err != nil { - return nil, err - } + if err := db.Insert(ctx, lock); err != nil { + return nil, err + } - return lock, committer.Commit() + return lock, nil + }) } // GetLFSLock returns release by given path. @@ -163,30 +159,26 @@ func CountLFSLockByRepoID(ctx context.Context, repoID int64) (int64, error) { // DeleteLFSLockByID deletes a lock by given ID. func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repository, u *user_model.User, force bool) (*LFSLock, error) { - dbCtx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - lock, err := GetLFSLockByID(dbCtx, id) - if err != nil { - return nil, err - } + return db.WithTx2(ctx, func(ctx context.Context) (*LFSLock, error) { + lock, err := GetLFSLockByID(ctx, id) + if err != nil { + return nil, err + } - if err := CheckLFSAccessForRepo(dbCtx, u.ID, repo, perm.AccessModeWrite); err != nil { - return nil, err - } + if err := CheckLFSAccessForRepo(ctx, u.ID, repo, perm.AccessModeWrite); err != nil { + return nil, err + } - if !force && u.ID != lock.OwnerID { - return nil, errors.New("user doesn't own lock and force flag is not set") - } + if !force && u.ID != lock.OwnerID { + return nil, errors.New("user doesn't own lock and force flag is not set") + } - if _, err := db.GetEngine(dbCtx).ID(id).Delete(new(LFSLock)); err != nil { - return nil, err - } + if _, err := db.GetEngine(ctx).ID(id).Delete(new(LFSLock)); err != nil { + return nil, err + } - return lock, committer.Commit() + return lock, nil + }) } // CheckLFSAccessForRepo check needed access mode base on action diff --git a/models/issues/comment.go b/models/issues/comment.go index 4fdb0c1808..d22f08fa87 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -766,81 +766,73 @@ func (c *Comment) CodeCommentLink(ctx context.Context) string { // CreateComment creates comment with context func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment, err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - e := db.GetEngine(ctx) - var LabelID int64 - if opts.Label != nil { - LabelID = opts.Label.ID - } + return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) { + var LabelID int64 + if opts.Label != nil { + LabelID = opts.Label.ID + } - var commentMetaData *CommentMetaData - if opts.ProjectColumnTitle != "" { - commentMetaData = &CommentMetaData{ - ProjectColumnID: opts.ProjectColumnID, - ProjectColumnTitle: opts.ProjectColumnTitle, - ProjectTitle: opts.ProjectTitle, + var commentMetaData *CommentMetaData + if opts.ProjectColumnTitle != "" { + commentMetaData = &CommentMetaData{ + ProjectColumnID: opts.ProjectColumnID, + ProjectColumnTitle: opts.ProjectColumnTitle, + ProjectTitle: opts.ProjectTitle, + } } - } - comment := &Comment{ - Type: opts.Type, - PosterID: opts.Doer.ID, - Poster: opts.Doer, - IssueID: opts.Issue.ID, - LabelID: LabelID, - OldMilestoneID: opts.OldMilestoneID, - MilestoneID: opts.MilestoneID, - OldProjectID: opts.OldProjectID, - ProjectID: opts.ProjectID, - TimeID: opts.TimeID, - RemovedAssignee: opts.RemovedAssignee, - AssigneeID: opts.AssigneeID, - AssigneeTeamID: opts.AssigneeTeamID, - CommitID: opts.CommitID, - CommitSHA: opts.CommitSHA, - Line: opts.LineNum, - Content: opts.Content, - OldTitle: opts.OldTitle, - NewTitle: opts.NewTitle, - OldRef: opts.OldRef, - NewRef: opts.NewRef, - DependentIssueID: opts.DependentIssueID, - TreePath: opts.TreePath, - ReviewID: opts.ReviewID, - Patch: opts.Patch, - RefRepoID: opts.RefRepoID, - RefIssueID: opts.RefIssueID, - RefCommentID: opts.RefCommentID, - RefAction: opts.RefAction, - RefIsPull: opts.RefIsPull, - IsForcePush: opts.IsForcePush, - Invalidated: opts.Invalidated, - CommentMetaData: commentMetaData, - } - if _, err = e.Insert(comment); err != nil { - return nil, err - } + comment := &Comment{ + Type: opts.Type, + PosterID: opts.Doer.ID, + Poster: opts.Doer, + IssueID: opts.Issue.ID, + LabelID: LabelID, + OldMilestoneID: opts.OldMilestoneID, + MilestoneID: opts.MilestoneID, + OldProjectID: opts.OldProjectID, + ProjectID: opts.ProjectID, + TimeID: opts.TimeID, + RemovedAssignee: opts.RemovedAssignee, + AssigneeID: opts.AssigneeID, + AssigneeTeamID: opts.AssigneeTeamID, + CommitID: opts.CommitID, + CommitSHA: opts.CommitSHA, + Line: opts.LineNum, + Content: opts.Content, + OldTitle: opts.OldTitle, + NewTitle: opts.NewTitle, + OldRef: opts.OldRef, + NewRef: opts.NewRef, + DependentIssueID: opts.DependentIssueID, + TreePath: opts.TreePath, + ReviewID: opts.ReviewID, + Patch: opts.Patch, + RefRepoID: opts.RefRepoID, + RefIssueID: opts.RefIssueID, + RefCommentID: opts.RefCommentID, + RefAction: opts.RefAction, + RefIsPull: opts.RefIsPull, + IsForcePush: opts.IsForcePush, + Invalidated: opts.Invalidated, + CommentMetaData: commentMetaData, + } + if err = db.Insert(ctx, comment); err != nil { + return nil, err + } - if err = opts.Repo.LoadOwner(ctx); err != nil { - return nil, err - } + if err = opts.Repo.LoadOwner(ctx); err != nil { + return nil, err + } - if err = updateCommentInfos(ctx, opts, comment); err != nil { - return nil, err - } + if err = updateCommentInfos(ctx, opts, comment); err != nil { + return nil, err + } - if err = comment.AddCrossReferences(ctx, opts.Doer, false); err != nil { - return nil, err - } - if err = committer.Commit(); err != nil { - return nil, err - } - return comment, nil + if err = comment.AddCrossReferences(ctx, opts.Doer, false); err != nil { + return nil, err + } + return comment, nil + }) } func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment *Comment) (err error) { @@ -1092,33 +1084,21 @@ func UpdateCommentInvalidate(ctx context.Context, c *Comment) error { // UpdateComment updates information of comment. func UpdateComment(ctx context.Context, c *Comment, contentVersion int, doer *user_model.User) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - sess := db.GetEngine(ctx) - - c.ContentVersion = contentVersion + 1 - - affected, err := sess.ID(c.ID).AllCols().Where("content_version = ?", contentVersion).Update(c) - if err != nil { - return err - } - if affected == 0 { - return ErrCommentAlreadyChanged - } - if err := c.LoadIssue(ctx); err != nil { - return err - } - if err := c.AddCrossReferences(ctx, doer, true); err != nil { - return err - } - if err := committer.Commit(); err != nil { - return fmt.Errorf("Commit: %w", err) - } + return db.WithTx(ctx, func(ctx context.Context) error { + c.ContentVersion = contentVersion + 1 - return nil + affected, err := db.GetEngine(ctx).ID(c.ID).AllCols().Where("content_version = ?", contentVersion).Update(c) + if err != nil { + return err + } + if affected == 0 { + return ErrCommentAlreadyChanged + } + if err := c.LoadIssue(ctx); err != nil { + return err + } + return c.AddCrossReferences(ctx, doer, true) + }) } // DeleteComment deletes the comment @@ -1277,31 +1257,28 @@ func InsertIssueComments(ctx context.Context, comments []*Comment) error { return comment.IssueID, true }) - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - for _, comment := range comments { - if _, err := db.GetEngine(ctx).NoAutoTime().Insert(comment); err != nil { - return err - } - - for _, reaction := range comment.Reactions { - reaction.IssueID = comment.IssueID - reaction.CommentID = comment.ID - } - if len(comment.Reactions) > 0 { - if err := db.Insert(ctx, comment.Reactions); err != nil { + return db.WithTx(ctx, func(ctx context.Context) error { + for _, comment := range comments { + if _, err := db.GetEngine(ctx).NoAutoTime().Insert(comment); err != nil { return err } + + for _, reaction := range comment.Reactions { + reaction.IssueID = comment.IssueID + reaction.CommentID = comment.ID + } + if len(comment.Reactions) > 0 { + if err := db.Insert(ctx, comment.Reactions); err != nil { + return err + } + } } - } - for _, issueID := range issueIDs { - if err := UpdateIssueNumComments(ctx, issueID); err != nil { - return err + for _, issueID := range issueIDs { + if err := UpdateIssueNumComments(ctx, issueID); err != nil { + return err + } } - } - return committer.Commit() + return nil + }) } diff --git a/models/issues/dependency.go b/models/issues/dependency.go index 146dd1887d..0eaa47e359 100644 --- a/models/issues/dependency.go +++ b/models/issues/dependency.go @@ -128,79 +128,64 @@ const ( // CreateIssueDependency creates a new dependency for an issue func CreateIssueDependency(ctx context.Context, user *user_model.User, issue, dep *Issue) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - // Check if it already exists - exists, err := issueDepExists(ctx, issue.ID, dep.ID) - if err != nil { - return err - } - if exists { - return ErrDependencyExists{issue.ID, dep.ID} - } - // And if it would be circular - circular, err := issueDepExists(ctx, dep.ID, issue.ID) - if err != nil { - return err - } - if circular { - return ErrCircularDependency{issue.ID, dep.ID} - } - - if err := db.Insert(ctx, &IssueDependency{ - UserID: user.ID, - IssueID: issue.ID, - DependencyID: dep.ID, - }); err != nil { - return err - } - - // Add comment referencing the new dependency - if err = createIssueDependencyComment(ctx, user, issue, dep, true); err != nil { - return err - } - - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + // Check if it already exists + exists, err := issueDepExists(ctx, issue.ID, dep.ID) + if err != nil { + return err + } + if exists { + return ErrDependencyExists{issue.ID, dep.ID} + } + // And if it would be circular + circular, err := issueDepExists(ctx, dep.ID, issue.ID) + if err != nil { + return err + } + if circular { + return ErrCircularDependency{issue.ID, dep.ID} + } + + if err := db.Insert(ctx, &IssueDependency{ + UserID: user.ID, + IssueID: issue.ID, + DependencyID: dep.ID, + }); err != nil { + return err + } + + // Add comment referencing the new dependency + return createIssueDependencyComment(ctx, user, issue, dep, true) + }) } // RemoveIssueDependency removes a dependency from an issue func RemoveIssueDependency(ctx context.Context, user *user_model.User, issue, dep *Issue, depType DependencyType) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - var issueDepToDelete IssueDependency - - switch depType { - case DependencyTypeBlockedBy: - issueDepToDelete = IssueDependency{IssueID: issue.ID, DependencyID: dep.ID} - case DependencyTypeBlocking: - issueDepToDelete = IssueDependency{IssueID: dep.ID, DependencyID: issue.ID} - default: - return ErrUnknownDependencyType{depType} - } - - affected, err := db.GetEngine(ctx).Delete(&issueDepToDelete) - if err != nil { - return err - } - - // If we deleted nothing, the dependency did not exist - if affected <= 0 { - return ErrDependencyNotExists{issue.ID, dep.ID} - } - - // Add comment referencing the removed dependency - if err = createIssueDependencyComment(ctx, user, issue, dep, false); err != nil { - return err - } - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + var issueDepToDelete IssueDependency + + switch depType { + case DependencyTypeBlockedBy: + issueDepToDelete = IssueDependency{IssueID: issue.ID, DependencyID: dep.ID} + case DependencyTypeBlocking: + issueDepToDelete = IssueDependency{IssueID: dep.ID, DependencyID: issue.ID} + default: + return ErrUnknownDependencyType{depType} + } + + affected, err := db.GetEngine(ctx).Delete(&issueDepToDelete) + if err != nil { + return err + } + + // If we deleted nothing, the dependency did not exist + if affected <= 0 { + return ErrDependencyNotExists{issue.ID, dep.ID} + } + + // Add comment referencing the removed dependency + return createIssueDependencyComment(ctx, user, issue, dep, false) + }) } // Check if the dependency already exists diff --git a/models/issues/issue.go b/models/issues/issue.go index a86d50ca9d..ef651359ab 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -755,18 +755,14 @@ func (issue *Issue) HasOriginalAuthor() bool { // InsertIssues insert issues to database func InsertIssues(ctx context.Context, issues ...*Issue) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - for _, issue := range issues { - if err := insertIssue(ctx, issue); err != nil { - return err + return db.WithTx(ctx, func(ctx context.Context) error { + for _, issue := range issues { + if err := insertIssue(ctx, issue); err != nil { + return err + } } - } - return committer.Commit() + return nil + }) } func insertIssue(ctx context.Context, issue *Issue) error { diff --git a/models/issues/issue_index.go b/models/issues/issue_index.go index 2eb61858bf..1fe4a08a09 100644 --- a/models/issues/issue_index.go +++ b/models/issues/issue_index.go @@ -12,20 +12,12 @@ import ( // RecalculateIssueIndexForRepo create issue_index for repo if not exist and // update it based on highest index of existing issues assigned to a repo func RecalculateIssueIndexForRepo(ctx context.Context, repoID int64) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() + return db.WithTx(ctx, func(ctx context.Context) error { + var maxIndex int64 + if _, err := db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&maxIndex); err != nil { + return err + } - var maxIndex int64 - if _, err = db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&maxIndex); err != nil { - return err - } - - if err = db.SyncMaxResourceIndex(ctx, "issue_index", repoID, maxIndex); err != nil { - return err - } - - return committer.Commit() + return db.SyncMaxResourceIndex(ctx, "issue_index", repoID, maxIndex) + }) } diff --git a/models/issues/issue_label.go b/models/issues/issue_label.go index f082079e07..151469a9b8 100644 --- a/models/issues/issue_label.go +++ b/models/issues/issue_label.go @@ -88,36 +88,28 @@ func NewIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_m return nil } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err = issue.LoadRepo(ctx); err != nil { - return err - } - - // Do NOT add invalid labels - if issue.RepoID != label.RepoID && issue.Repo.OwnerID != label.OrgID { - return nil - } + return db.WithTx(ctx, func(ctx context.Context) error { + if err = issue.LoadRepo(ctx); err != nil { + return err + } - if err = RemoveDuplicateExclusiveIssueLabels(ctx, issue, label, doer); err != nil { - return nil - } + // Do NOT add invalid labels + if issue.RepoID != label.RepoID && issue.Repo.OwnerID != label.OrgID { + return nil + } - if err = newIssueLabel(ctx, issue, label, doer); err != nil { - return err - } + if err = RemoveDuplicateExclusiveIssueLabels(ctx, issue, label, doer); err != nil { + return nil + } - issue.isLabelsLoaded = false - issue.Labels = nil - if err = issue.LoadLabels(ctx); err != nil { - return err - } + if err = newIssueLabel(ctx, issue, label, doer); err != nil { + return err + } - return committer.Commit() + issue.isLabelsLoaded = false + issue.Labels = nil + return issue.LoadLabels(ctx) + }) } // newIssueLabels add labels to an issue. It will check if the labels are valid for the issue @@ -151,24 +143,16 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us // NewIssueLabels creates a list of issue-label relations. func NewIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *user_model.User) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err = newIssueLabels(ctx, issue, labels, doer); err != nil { - return err - } - - // reload all labels - issue.isLabelsLoaded = false - issue.Labels = nil - if err = issue.LoadLabels(ctx); err != nil { - return err - } + return db.WithTx(ctx, func(ctx context.Context) error { + if err = newIssueLabels(ctx, issue, labels, doer); err != nil { + return err + } - return committer.Commit() + // reload all labels + issue.isLabelsLoaded = false + issue.Labels = nil + return issue.LoadLabels(ctx) + }) } func deleteIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_model.User) (err error) { @@ -365,35 +349,23 @@ func clearIssueLabels(ctx context.Context, issue *Issue, doer *user_model.User) // ClearIssueLabels removes all issue labels as the given user. // Triggers appropriate WebHooks, if any. func ClearIssueLabels(ctx context.Context, issue *Issue, doer *user_model.User) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err := issue.LoadRepo(ctx); err != nil { - return err - } else if err = issue.LoadPullRequest(ctx); err != nil { - return err - } - - perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) - if err != nil { - return err - } - if !perm.CanWriteIssuesOrPulls(issue.IsPull) { - return ErrRepoLabelNotExist{} - } - - if err = clearIssueLabels(ctx, issue, doer); err != nil { - return err - } + return db.WithTx(ctx, func(ctx context.Context) error { + if err := issue.LoadRepo(ctx); err != nil { + return err + } else if err = issue.LoadPullRequest(ctx); err != nil { + return err + } - if err = committer.Commit(); err != nil { - return fmt.Errorf("Commit: %w", err) - } + perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) + if err != nil { + return err + } + if !perm.CanWriteIssuesOrPulls(issue.IsPull) { + return ErrRepoLabelNotExist{} + } - return nil + return clearIssueLabels(ctx, issue, doer) + }) } type labelSorter []*Label @@ -438,69 +410,61 @@ func RemoveDuplicateExclusiveLabels(labels []*Label) []*Label { // ReplaceIssueLabels removes all current labels and add new labels to the issue. // Triggers appropriate WebHooks, if any. func ReplaceIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *user_model.User) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() + return db.WithTx(ctx, func(ctx context.Context) error { + if err = issue.LoadRepo(ctx); err != nil { + return err + } - if err = issue.LoadRepo(ctx); err != nil { - return err - } + if err = issue.LoadLabels(ctx); err != nil { + return err + } - if err = issue.LoadLabels(ctx); err != nil { - return err - } + labels = RemoveDuplicateExclusiveLabels(labels) - labels = RemoveDuplicateExclusiveLabels(labels) + sort.Sort(labelSorter(labels)) + sort.Sort(labelSorter(issue.Labels)) - sort.Sort(labelSorter(labels)) - sort.Sort(labelSorter(issue.Labels)) + var toAdd, toRemove []*Label - var toAdd, toRemove []*Label + addIndex, removeIndex := 0, 0 + for addIndex < len(labels) && removeIndex < len(issue.Labels) { + addLabel := labels[addIndex] + removeLabel := issue.Labels[removeIndex] + if addLabel.ID == removeLabel.ID { + // Silently drop invalid labels + if removeLabel.RepoID != issue.RepoID && removeLabel.OrgID != issue.Repo.OwnerID { + toRemove = append(toRemove, removeLabel) + } - addIndex, removeIndex := 0, 0 - for addIndex < len(labels) && removeIndex < len(issue.Labels) { - addLabel := labels[addIndex] - removeLabel := issue.Labels[removeIndex] - if addLabel.ID == removeLabel.ID { - // Silently drop invalid labels - if removeLabel.RepoID != issue.RepoID && removeLabel.OrgID != issue.Repo.OwnerID { + addIndex++ + removeIndex++ + } else if addLabel.ID < removeLabel.ID { + // Only add if the label is valid + if addLabel.RepoID == issue.RepoID || addLabel.OrgID == issue.Repo.OwnerID { + toAdd = append(toAdd, addLabel) + } + addIndex++ + } else { toRemove = append(toRemove, removeLabel) + removeIndex++ } - - addIndex++ - removeIndex++ - } else if addLabel.ID < removeLabel.ID { - // Only add if the label is valid - if addLabel.RepoID == issue.RepoID || addLabel.OrgID == issue.Repo.OwnerID { - toAdd = append(toAdd, addLabel) - } - addIndex++ - } else { - toRemove = append(toRemove, removeLabel) - removeIndex++ } - } - toAdd = append(toAdd, labels[addIndex:]...) - toRemove = append(toRemove, issue.Labels[removeIndex:]...) + toAdd = append(toAdd, labels[addIndex:]...) + toRemove = append(toRemove, issue.Labels[removeIndex:]...) - if len(toAdd) > 0 { - if err = newIssueLabels(ctx, issue, toAdd, doer); err != nil { - return fmt.Errorf("addLabels: %w", err) + if len(toAdd) > 0 { + if err = newIssueLabels(ctx, issue, toAdd, doer); err != nil { + return fmt.Errorf("addLabels: %w", err) + } } - } - for _, l := range toRemove { - if err = deleteIssueLabel(ctx, issue, l, doer); err != nil { - return fmt.Errorf("removeLabel: %w", err) + for _, l := range toRemove { + if err = deleteIssueLabel(ctx, issue, l, doer); err != nil { + return fmt.Errorf("removeLabel: %w", err) + } } - } - - issue.Labels = nil - if err = issue.LoadLabels(ctx); err != nil { - return err - } - return committer.Commit() + issue.Labels = nil + return issue.LoadLabels(ctx) + }) } diff --git a/models/issues/issue_lock.go b/models/issues/issue_lock.go index fa0d128f74..2e5bf64cc6 100644 --- a/models/issues/issue_lock.go +++ b/models/issues/issue_lock.go @@ -47,26 +47,19 @@ func updateIssueLock(ctx context.Context, opts *IssueLockOptions, lock bool) err commentType = CommentTypeUnlock } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err := UpdateIssueCols(ctx, opts.Issue, "is_locked"); err != nil { - return err - } + return db.WithTx(ctx, func(ctx context.Context) error { + if err := UpdateIssueCols(ctx, opts.Issue, "is_locked"); err != nil { + return err + } - opt := &CreateCommentOptions{ - Doer: opts.Doer, - Issue: opts.Issue, - Repo: opts.Issue.Repo, - Type: commentType, - Content: opts.Reason, - } - if _, err := CreateComment(ctx, opt); err != nil { + opt := &CreateCommentOptions{ + Doer: opts.Doer, + Issue: opts.Issue, + Repo: opts.Issue.Repo, + Type: commentType, + Content: opts.Reason, + } + _, err := CreateComment(ctx, opt) return err - } - - return committer.Commit() + }) } diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go index 9b99787e3b..1c16817491 100644 --- a/models/issues/issue_update.go +++ b/models/issues/issue_update.go @@ -167,20 +167,9 @@ func CloseIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Comm return nil, err } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - comment, err := SetIssueAsClosed(ctx, issue, doer, false) - if err != nil { - return nil, err - } - if err := committer.Commit(); err != nil { - return nil, err - } - return comment, nil + return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) { + return SetIssueAsClosed(ctx, issue, doer, false) + }) } // ReopenIssue changes issue status to open. @@ -192,88 +181,64 @@ func ReopenIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Com return nil, err } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - comment, err := setIssueAsReopen(ctx, issue, doer) - if err != nil { - return nil, err - } - if err := committer.Commit(); err != nil { - return nil, err - } - return comment, nil + return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) { + return setIssueAsReopen(ctx, issue, doer) + }) } // ChangeIssueTitle changes the title of this issue, as the given user. func ChangeIssueTitle(ctx context.Context, issue *Issue, doer *user_model.User, oldTitle string) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - issue.Title = util.EllipsisDisplayString(issue.Title, 255) - if err = UpdateIssueCols(ctx, issue, "name"); err != nil { - return fmt.Errorf("updateIssueCols: %w", err) - } - - if err = issue.LoadRepo(ctx); err != nil { - return fmt.Errorf("loadRepo: %w", err) - } + return db.WithTx(ctx, func(ctx context.Context) error { + issue.Title = util.EllipsisDisplayString(issue.Title, 255) + if err = UpdateIssueCols(ctx, issue, "name"); err != nil { + return fmt.Errorf("updateIssueCols: %w", err) + } - opts := &CreateCommentOptions{ - Type: CommentTypeChangeTitle, - Doer: doer, - Repo: issue.Repo, - Issue: issue, - OldTitle: oldTitle, - NewTitle: issue.Title, - } - if _, err = CreateComment(ctx, opts); err != nil { - return fmt.Errorf("createComment: %w", err) - } - if err = issue.AddCrossReferences(ctx, doer, true); err != nil { - return err - } + if err = issue.LoadRepo(ctx); err != nil { + return fmt.Errorf("loadRepo: %w", err) + } - return committer.Commit() + opts := &CreateCommentOptions{ + Type: CommentTypeChangeTitle, + Doer: doer, + Repo: issue.Repo, + Issue: issue, + OldTitle: oldTitle, + NewTitle: issue.Title, + } + if _, err = CreateComment(ctx, opts); err != nil { + return fmt.Errorf("createComment: %w", err) + } + return issue.AddCrossReferences(ctx, doer, true) + }) } // ChangeIssueRef changes the branch of this issue, as the given user. func ChangeIssueRef(ctx context.Context, issue *Issue, doer *user_model.User, oldRef string) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err = UpdateIssueCols(ctx, issue, "ref"); err != nil { - return fmt.Errorf("updateIssueCols: %w", err) - } - - if err = issue.LoadRepo(ctx); err != nil { - return fmt.Errorf("loadRepo: %w", err) - } - oldRefFriendly := strings.TrimPrefix(oldRef, git.BranchPrefix) - newRefFriendly := strings.TrimPrefix(issue.Ref, git.BranchPrefix) + return db.WithTx(ctx, func(ctx context.Context) error { + if err = UpdateIssueCols(ctx, issue, "ref"); err != nil { + return fmt.Errorf("updateIssueCols: %w", err) + } - opts := &CreateCommentOptions{ - Type: CommentTypeChangeIssueRef, - Doer: doer, - Repo: issue.Repo, - Issue: issue, - OldRef: oldRefFriendly, - NewRef: newRefFriendly, - } - if _, err = CreateComment(ctx, opts); err != nil { - return fmt.Errorf("createComment: %w", err) - } + if err = issue.LoadRepo(ctx); err != nil { + return fmt.Errorf("loadRepo: %w", err) + } + oldRefFriendly := strings.TrimPrefix(oldRef, git.BranchPrefix) + newRefFriendly := strings.TrimPrefix(issue.Ref, git.BranchPrefix) - return committer.Commit() + opts := &CreateCommentOptions{ + Type: CommentTypeChangeIssueRef, + Doer: doer, + Repo: issue.Repo, + Issue: issue, + OldRef: oldRefFriendly, + NewRef: newRefFriendly, + } + if _, err = CreateComment(ctx, opts); err != nil { + return fmt.Errorf("createComment: %w", err) + } + return nil + }) } // AddDeletePRBranchComment adds delete branch comment for pull request issue @@ -295,64 +260,56 @@ func AddDeletePRBranchComment(ctx context.Context, doer *user_model.User, repo * // UpdateIssueAttachments update attachments by UUIDs for the issue func UpdateIssueAttachments(ctx context.Context, issueID int64, uuids []string) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids) - if err != nil { - return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err) - } - for i := range attachments { - attachments[i].IssueID = issueID - if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil { - return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err) + return db.WithTx(ctx, func(ctx context.Context) error { + attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids) + if err != nil { + return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err) } - } - return committer.Commit() + for i := range attachments { + attachments[i].IssueID = issueID + if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil { + return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err) + } + } + return nil + }) } // ChangeIssueContent changes issue content, as the given user. func ChangeIssueContent(ctx context.Context, issue *Issue, doer *user_model.User, content string, contentVersion int) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - hasContentHistory, err := HasIssueContentHistory(ctx, issue.ID, 0) - if err != nil { - return fmt.Errorf("HasIssueContentHistory: %w", err) - } - if !hasContentHistory { - if err = SaveIssueContentHistory(ctx, issue.PosterID, issue.ID, 0, - issue.CreatedUnix, issue.Content, true); err != nil { - return fmt.Errorf("SaveIssueContentHistory: %w", err) + return db.WithTx(ctx, func(ctx context.Context) error { + hasContentHistory, err := HasIssueContentHistory(ctx, issue.ID, 0) + if err != nil { + return fmt.Errorf("HasIssueContentHistory: %w", err) + } + if !hasContentHistory { + if err = SaveIssueContentHistory(ctx, issue.PosterID, issue.ID, 0, + issue.CreatedUnix, issue.Content, true); err != nil { + return fmt.Errorf("SaveIssueContentHistory: %w", err) + } } - } - - issue.Content = content - issue.ContentVersion = contentVersion + 1 - affected, err := db.GetEngine(ctx).ID(issue.ID).Cols("content", "content_version").Where("content_version = ?", contentVersion).Update(issue) - if err != nil { - return err - } - if affected == 0 { - return ErrIssueAlreadyChanged - } + issue.Content = content + issue.ContentVersion = contentVersion + 1 - if err = SaveIssueContentHistory(ctx, doer.ID, issue.ID, 0, - timeutil.TimeStampNow(), issue.Content, false); err != nil { - return fmt.Errorf("SaveIssueContentHistory: %w", err) - } + affected, err := db.GetEngine(ctx).ID(issue.ID).Cols("content", "content_version").Where("content_version = ?", contentVersion).Update(issue) + if err != nil { + return err + } + if affected == 0 { + return ErrIssueAlreadyChanged + } - if err = issue.AddCrossReferences(ctx, doer, true); err != nil { - return fmt.Errorf("addCrossReferences: %w", err) - } + if err = SaveIssueContentHistory(ctx, doer.ID, issue.ID, 0, + timeutil.TimeStampNow(), issue.Content, false); err != nil { + return fmt.Errorf("SaveIssueContentHistory: %w", err) + } - return committer.Commit() + if err = issue.AddCrossReferences(ctx, doer, true); err != nil { + return fmt.Errorf("addCrossReferences: %w", err) + } + return nil + }) } // NewIssueOptions represents the options of a new issue. @@ -512,23 +469,19 @@ func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeuti if issue.DeadlineUnix == deadlineUnix { return nil } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - // Update the deadline - if err = UpdateIssueCols(ctx, &Issue{ID: issue.ID, DeadlineUnix: deadlineUnix}, "deadline_unix"); err != nil { - return err - } - - // Make the comment - if _, err = createDeadlineComment(ctx, doer, issue, deadlineUnix); err != nil { - return fmt.Errorf("createRemovedDueDateComment: %w", err) - } + return db.WithTx(ctx, func(ctx context.Context) error { + // Update the deadline + if err = UpdateIssueCols(ctx, &Issue{ID: issue.ID, DeadlineUnix: deadlineUnix}, "deadline_unix"); err != nil { + return err + } - return committer.Commit() + // Make the comment + if _, err = createDeadlineComment(ctx, doer, issue, deadlineUnix); err != nil { + return fmt.Errorf("createRemovedDueDateComment: %w", err) + } + return nil + }) } // FindAndUpdateIssueMentions finds users mentioned in the given content string, and saves them in the database. diff --git a/models/issues/label.go b/models/issues/label.go index cfbe100926..25d6f1303e 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -209,24 +209,20 @@ func NewLabel(ctx context.Context, l *Label) error { // NewLabels creates new labels func NewLabels(ctx context.Context, labels ...*Label) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - for _, l := range labels { - color, err := label.NormalizeColor(l.Color) - if err != nil { - return err - } - l.Color = color + return db.WithTx(ctx, func(ctx context.Context) error { + for _, l := range labels { + color, err := label.NormalizeColor(l.Color) + if err != nil { + return err + } + l.Color = color - if err := db.Insert(ctx, l); err != nil { - return err + if err := db.Insert(ctx, l); err != nil { + return err + } } - } - return committer.Commit() + return nil + }) } // UpdateLabel updates label information. @@ -250,35 +246,26 @@ func DeleteLabel(ctx context.Context, id, labelID int64) error { return err } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - sess := db.GetEngine(ctx) - - if l.BelongsToOrg() && l.OrgID != id { - return nil - } - if l.BelongsToRepo() && l.RepoID != id { - return nil - } + return db.WithTx(ctx, func(ctx context.Context) error { + if l.BelongsToOrg() && l.OrgID != id { + return nil + } + if l.BelongsToRepo() && l.RepoID != id { + return nil + } - if _, err = db.DeleteByID[Label](ctx, labelID); err != nil { - return err - } else if _, err = sess. - Where("label_id = ?", labelID). - Delete(new(IssueLabel)); err != nil { - return err - } + if _, err = db.DeleteByID[Label](ctx, labelID); err != nil { + return err + } else if _, err = db.GetEngine(ctx). + Where("label_id = ?", labelID). + Delete(new(IssueLabel)); err != nil { + return err + } - // delete comments about now deleted label_id - if _, err = sess.Where("label_id = ?", labelID).Cols("label_id").Delete(&Comment{}); err != nil { + // delete comments about now deleted label_id + _, err = db.GetEngine(ctx).Where("label_id = ?", labelID).Cols("label_id").Delete(&Comment{}) return err - } - - return committer.Commit() + }) } // GetLabelByID returns a label by given ID. diff --git a/models/issues/milestone.go b/models/issues/milestone.go index 4c9bae58f7..373f39f4ff 100644 --- a/models/issues/milestone.go +++ b/models/issues/milestone.go @@ -105,22 +105,16 @@ func (m *Milestone) State() api.StateType { // NewMilestone creates new milestone of repository. func NewMilestone(ctx context.Context, m *Milestone) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() + return db.WithTx(ctx, func(ctx context.Context) error { + m.Name = strings.TrimSpace(m.Name) - m.Name = strings.TrimSpace(m.Name) - - if err = db.Insert(ctx, m); err != nil { - return err - } + if err = db.Insert(ctx, m); err != nil { + return err + } - if _, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID); err != nil { + _, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID) return err - } - return committer.Commit() + }) } // HasMilestoneByRepoID returns if the milestone exists in the repository. @@ -155,28 +149,23 @@ func GetMilestoneByRepoIDANDName(ctx context.Context, repoID int64, name string) // UpdateMilestone updates information of given milestone. func UpdateMilestone(ctx context.Context, m *Milestone, oldIsClosed bool) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if m.IsClosed && !oldIsClosed { - m.ClosedDateUnix = timeutil.TimeStampNow() - } - - if err := updateMilestone(ctx, m); err != nil { - return err - } + return db.WithTx(ctx, func(ctx context.Context) error { + if m.IsClosed && !oldIsClosed { + m.ClosedDateUnix = timeutil.TimeStampNow() + } - // if IsClosed changed, update milestone numbers of repository - if oldIsClosed != m.IsClosed { - if err := updateRepoMilestoneNum(ctx, m.RepoID); err != nil { + if err := updateMilestone(ctx, m); err != nil { return err } - } - return committer.Commit() + // if IsClosed changed, update milestone numbers of repository + if oldIsClosed != m.IsClosed { + if err := updateRepoMilestoneNum(ctx, m.RepoID); err != nil { + return err + } + } + return nil + }) } func updateMilestone(ctx context.Context, m *Milestone) error { @@ -213,44 +202,28 @@ func UpdateMilestoneCounters(ctx context.Context, id int64) error { // ChangeMilestoneStatusByRepoIDAndID changes a milestone open/closed status if the milestone ID is in the repo. func ChangeMilestoneStatusByRepoIDAndID(ctx context.Context, repoID, milestoneID int64, isClosed bool) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - m := &Milestone{ - ID: milestoneID, - RepoID: repoID, - } - - has, err := db.GetEngine(ctx).ID(milestoneID).Where("repo_id = ?", repoID).Get(m) - if err != nil { - return err - } else if !has { - return ErrMilestoneNotExist{ID: milestoneID, RepoID: repoID} - } + return db.WithTx(ctx, func(ctx context.Context) error { + m := &Milestone{ + ID: milestoneID, + RepoID: repoID, + } - if err := changeMilestoneStatus(ctx, m, isClosed); err != nil { - return err - } + has, err := db.GetEngine(ctx).ID(milestoneID).Where("repo_id = ?", repoID).Get(m) + if err != nil { + return err + } else if !has { + return ErrMilestoneNotExist{ID: milestoneID, RepoID: repoID} + } - return committer.Commit() + return changeMilestoneStatus(ctx, m, isClosed) + }) } // ChangeMilestoneStatus changes the milestone open/closed status. func ChangeMilestoneStatus(ctx context.Context, m *Milestone, isClosed bool) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err := changeMilestoneStatus(ctx, m, isClosed); err != nil { - return err - } - - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + return changeMilestoneStatus(ctx, m, isClosed) + }) } func changeMilestoneStatus(ctx context.Context, m *Milestone, isClosed bool) error { @@ -284,40 +257,34 @@ func DeleteMilestoneByRepoID(ctx context.Context, repoID, id int64) error { return err } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if _, err = db.DeleteByID[Milestone](ctx, m.ID); err != nil { - return err - } + return db.WithTx(ctx, func(ctx context.Context) error { + if _, err = db.DeleteByID[Milestone](ctx, m.ID); err != nil { + return err + } - numMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{ - RepoID: repo.ID, - }) - if err != nil { - return err - } - numClosedMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{ - RepoID: repo.ID, - IsClosed: optional.Some(true), - }) - if err != nil { - return err - } - repo.NumMilestones = int(numMilestones) - repo.NumClosedMilestones = int(numClosedMilestones) + numMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{ + RepoID: repo.ID, + }) + if err != nil { + return err + } + numClosedMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{ + RepoID: repo.ID, + IsClosed: optional.Some(true), + }) + if err != nil { + return err + } + repo.NumMilestones = int(numMilestones) + repo.NumClosedMilestones = int(numClosedMilestones) - if _, err = db.GetEngine(ctx).ID(repo.ID).Cols("num_milestones, num_closed_milestones").Update(repo); err != nil { - return err - } + if _, err = db.GetEngine(ctx).ID(repo.ID).Cols("num_milestones, num_closed_milestones").Update(repo); err != nil { + return err + } - if _, err = db.Exec(ctx, "UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID); err != nil { + _, err = db.Exec(ctx, "UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID) return err - } - return committer.Commit() + }) } func updateRepoMilestoneNum(ctx context.Context, repoID int64) error { @@ -360,22 +327,15 @@ func InsertMilestones(ctx context.Context, ms ...*Milestone) (err error) { return nil } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - sess := db.GetEngine(ctx) - - // to return the id, so we should not use batch insert - for _, m := range ms { - if _, err = sess.NoAutoTime().Insert(m); err != nil { - return err + return db.WithTx(ctx, func(ctx context.Context) error { + // to return the id, so we should not use batch insert + for _, m := range ms { + if _, err = db.GetEngine(ctx).NoAutoTime().Insert(m); err != nil { + return err + } } - } - if _, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + ? WHERE id = ?", len(ms), ms[0].RepoID); err != nil { + _, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + ? WHERE id = ?", len(ms), ms[0].RepoID) return err - } - return committer.Commit() + }) } diff --git a/models/issues/pull.go b/models/issues/pull.go index 4c25b6f0c8..00d7bfe1ca 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -364,17 +364,10 @@ func (pr *PullRequest) GetApprovers(ctx context.Context) string { func (pr *PullRequest) getReviewedByLines(ctx context.Context, writer io.Writer) error { maxReviewers := setting.Repository.PullRequest.DefaultMergeMessageMaxApprovers - if maxReviewers == 0 { return nil } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - // Note: This doesn't page as we only expect a very limited number of reviews reviews, err := FindLatestReviews(ctx, FindReviewOptions{ Types: []ReviewType{ReviewTypeApprove}, @@ -410,7 +403,7 @@ func (pr *PullRequest) getReviewedByLines(ctx context.Context, writer io.Writer) } reviewersWritten++ } - return committer.Commit() + return nil } // GetGitHeadRefName returns git ref for hidden pull request branch @@ -464,45 +457,36 @@ func (pr *PullRequest) IsFromFork() bool { // NewPullRequest creates new pull request with labels for repository. func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID) - if err != nil { - return fmt.Errorf("generate pull request index failed: %w", err) - } - - issue.Index = idx - issue.Title = util.EllipsisDisplayString(issue.Title, 255) - - if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{ - Repo: repo, - Issue: issue, - LabelIDs: labelIDs, - Attachments: uuids, - IsPull: true, - }); err != nil { - if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) { - return err + return db.WithTx(ctx, func(ctx context.Context) error { + idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID) + if err != nil { + return fmt.Errorf("generate pull request index failed: %w", err) } - return fmt.Errorf("newIssue: %w", err) - } - - pr.Index = issue.Index - pr.BaseRepo = repo - pr.IssueID = issue.ID - if err = db.Insert(ctx, pr); err != nil { - return fmt.Errorf("insert pull repo: %w", err) - } - if err = committer.Commit(); err != nil { - return fmt.Errorf("Commit: %w", err) - } + issue.Index = idx + issue.Title = util.EllipsisDisplayString(issue.Title, 255) + + if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{ + Repo: repo, + Issue: issue, + LabelIDs: labelIDs, + Attachments: uuids, + IsPull: true, + }); err != nil { + if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) { + return err + } + return fmt.Errorf("newIssue: %w", err) + } - return nil + pr.Index = issue.Index + pr.BaseRepo = repo + pr.IssueID = issue.ID + if err = db.Insert(ctx, pr); err != nil { + return fmt.Errorf("insert pull repo: %w", err) + } + return nil + }) } // ErrUserMustCollaborator represents an error that the user must be a collaborator to a given repo. @@ -977,22 +961,18 @@ func TokenizeCodeOwnersLine(line string) []string { // InsertPullRequests inserted pull requests func InsertPullRequests(ctx context.Context, prs ...*PullRequest) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - sess := db.GetEngine(ctx) - for _, pr := range prs { - if err := insertIssue(ctx, pr.Issue); err != nil { - return err - } - pr.IssueID = pr.Issue.ID - if _, err := sess.NoAutoTime().Insert(pr); err != nil { - return err + return db.WithTx(ctx, func(ctx context.Context) error { + for _, pr := range prs { + if err := insertIssue(ctx, pr.Issue); err != nil { + return err + } + pr.IssueID = pr.Issue.ID + if _, err := db.GetEngine(ctx).NoAutoTime().Insert(pr); err != nil { + return err + } } - } - return committer.Commit() + return nil + }) } // GetPullRequestByMergedCommit returns a merged pull request by the given commit diff --git a/models/issues/reaction.go b/models/issues/reaction.go index f24001fd23..3b5ad6d7ab 100644 --- a/models/issues/reaction.go +++ b/models/issues/reaction.go @@ -224,21 +224,9 @@ func CreateReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, erro return nil, ErrForbiddenIssueReaction{opts.Type} } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - reaction, err := createReaction(ctx, opts) - if err != nil { - return reaction, err - } - - if err := committer.Commit(); err != nil { - return nil, err - } - return reaction, nil + return db.WithTx2(ctx, func(ctx context.Context) (*Reaction, error) { + return createReaction(ctx, opts) + }) } // DeleteReaction deletes reaction for issue or comment. diff --git a/models/issues/review.go b/models/issues/review.go index 71fdb7456f..b758fa5ffa 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -334,54 +334,51 @@ func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio // CreateReview creates a new review based on opts func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - sess := db.GetEngine(ctx) - - review := &Review{ - Issue: opts.Issue, - IssueID: opts.Issue.ID, - Reviewer: opts.Reviewer, - ReviewerTeam: opts.ReviewerTeam, - Content: opts.Content, - Official: opts.Official, - CommitID: opts.CommitID, - Stale: opts.Stale, - } + return db.WithTx2(ctx, func(ctx context.Context) (*Review, error) { + sess := db.GetEngine(ctx) + + review := &Review{ + Issue: opts.Issue, + IssueID: opts.Issue.ID, + Reviewer: opts.Reviewer, + ReviewerTeam: opts.ReviewerTeam, + Content: opts.Content, + Official: opts.Official, + CommitID: opts.CommitID, + Stale: opts.Stale, + } - if opts.Reviewer != nil { - review.Type = opts.Type - review.ReviewerID = opts.Reviewer.ID + if opts.Reviewer != nil { + review.Type = opts.Type + review.ReviewerID = opts.Reviewer.ID - reviewCond := builder.Eq{"reviewer_id": opts.Reviewer.ID, "issue_id": opts.Issue.ID} - // make sure user review requests are cleared - if opts.Type != ReviewTypePending { - if _, err := sess.Where(reviewCond.And(builder.Eq{"type": ReviewTypeRequest})).Delete(new(Review)); err != nil { - return nil, err + reviewCond := builder.Eq{"reviewer_id": opts.Reviewer.ID, "issue_id": opts.Issue.ID} + // make sure user review requests are cleared + if opts.Type != ReviewTypePending { + if _, err := sess.Where(reviewCond.And(builder.Eq{"type": ReviewTypeRequest})).Delete(new(Review)); err != nil { + return nil, err + } } - } - // make sure if the created review gets dismissed no old review surface - // other types can be ignored, as they don't affect branch protection - if opts.Type == ReviewTypeApprove || opts.Type == ReviewTypeReject { - if _, err := sess.Where(reviewCond.And(builder.In("type", ReviewTypeApprove, ReviewTypeReject))). - Cols("dismissed").Update(&Review{Dismissed: true}); err != nil { - return nil, err + // make sure if the created review gets dismissed no old review surface + // other types can be ignored, as they don't affect branch protection + if opts.Type == ReviewTypeApprove || opts.Type == ReviewTypeReject { + if _, err := sess.Where(reviewCond.And(builder.In("type", ReviewTypeApprove, ReviewTypeReject))). + Cols("dismissed").Update(&Review{Dismissed: true}); err != nil { + return nil, err + } } + } else if opts.ReviewerTeam != nil { + review.Type = ReviewTypeRequest + review.ReviewerTeamID = opts.ReviewerTeam.ID + } else { + return nil, errors.New("provide either reviewer or reviewer team") } - } else if opts.ReviewerTeam != nil { - review.Type = ReviewTypeRequest - review.ReviewerTeamID = opts.ReviewerTeam.ID - } else { - return nil, errors.New("provide either reviewer or reviewer team") - } - if _, err := sess.Insert(review); err != nil { - return nil, err - } - return review, committer.Commit() + if _, err := sess.Insert(review); err != nil { + return nil, err + } + return review, nil + }) } // GetCurrentReview returns the current pending review of reviewer for given issue @@ -605,168 +602,152 @@ func DismissReview(ctx context.Context, review *Review, isDismiss bool) (err err // InsertReviews inserts review and review comments func InsertReviews(ctx context.Context, reviews []*Review) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - sess := db.GetEngine(ctx) + return db.WithTx(ctx, func(ctx context.Context) error { + sess := db.GetEngine(ctx) - for _, review := range reviews { - if _, err := sess.NoAutoTime().Insert(review); err != nil { - return err - } + for _, review := range reviews { + if _, err := sess.NoAutoTime().Insert(review); err != nil { + return err + } - if _, err := sess.NoAutoTime().Insert(&Comment{ - Type: CommentTypeReview, - Content: review.Content, - PosterID: review.ReviewerID, - OriginalAuthor: review.OriginalAuthor, - OriginalAuthorID: review.OriginalAuthorID, - IssueID: review.IssueID, - ReviewID: review.ID, - CreatedUnix: review.CreatedUnix, - UpdatedUnix: review.UpdatedUnix, - }); err != nil { - return err - } + if _, err := sess.NoAutoTime().Insert(&Comment{ + Type: CommentTypeReview, + Content: review.Content, + PosterID: review.ReviewerID, + OriginalAuthor: review.OriginalAuthor, + OriginalAuthorID: review.OriginalAuthorID, + IssueID: review.IssueID, + ReviewID: review.ID, + CreatedUnix: review.CreatedUnix, + UpdatedUnix: review.UpdatedUnix, + }); err != nil { + return err + } - for _, c := range review.Comments { - c.ReviewID = review.ID - } + for _, c := range review.Comments { + c.ReviewID = review.ID + } - if len(review.Comments) > 0 { - if _, err := sess.NoAutoTime().Insert(review.Comments); err != nil { - return err + if len(review.Comments) > 0 { + if _, err := sess.NoAutoTime().Insert(review.Comments); err != nil { + return err + } } - } - if err := UpdateIssueNumComments(ctx, review.IssueID); err != nil { - return err + if err := UpdateIssueNumComments(ctx, review.IssueID); err != nil { + return err + } } - } - - return committer.Commit() + return nil + }) } // AddReviewRequest add a review request from one reviewer func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_model.User) (*Comment, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - sess := db.GetEngine(ctx) + return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) { + sess := db.GetEngine(ctx) - review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID) - if err != nil && !IsErrReviewNotExist(err) { - return nil, err - } - - if review != nil { - // skip it when reviewer has been request to review - if review.Type == ReviewTypeRequest { - return nil, committer.Commit() // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction. - } - - if issue.IsClosed { - return nil, ErrReviewRequestOnClosedPR{} + review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID) + if err != nil && !IsErrReviewNotExist(err) { + return nil, err } - if issue.IsPull { - if err := issue.LoadPullRequest(ctx); err != nil { - return nil, err + if review != nil { + // skip it when reviewer has been request to review + if review.Type == ReviewTypeRequest { + return nil, nil // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction. } - if issue.PullRequest.HasMerged { + + if issue.IsClosed { return nil, ErrReviewRequestOnClosedPR{} } + + if issue.IsPull { + if err := issue.LoadPullRequest(ctx); err != nil { + return nil, err + } + if issue.PullRequest.HasMerged { + return nil, ErrReviewRequestOnClosedPR{} + } + } } - } - // if the reviewer is an official reviewer, - // remove the official flag in the all previous reviews - official, err := IsOfficialReviewer(ctx, issue, reviewer) - if err != nil { - return nil, err - } else if official { - if _, err := sess.Exec("UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_id=?", false, issue.ID, reviewer.ID); err != nil { + // if the reviewer is an official reviewer, + // remove the official flag in the all previous reviews + official, err := IsOfficialReviewer(ctx, issue, reviewer) + if err != nil { return nil, err + } else if official { + if _, err := sess.Exec("UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_id=?", false, issue.ID, reviewer.ID); err != nil { + return nil, err + } } - } - review, err = CreateReview(ctx, CreateReviewOptions{ - Type: ReviewTypeRequest, - Issue: issue, - Reviewer: reviewer, - Official: official, - Stale: false, - }) - if err != nil { - return nil, err - } + review, err = CreateReview(ctx, CreateReviewOptions{ + Type: ReviewTypeRequest, + Issue: issue, + Reviewer: reviewer, + Official: official, + Stale: false, + }) + if err != nil { + return nil, err + } - comment, err := CreateComment(ctx, &CreateCommentOptions{ - Type: CommentTypeReviewRequest, - Doer: doer, - Repo: issue.Repo, - Issue: issue, - RemovedAssignee: false, // Use RemovedAssignee as !isRequest - AssigneeID: reviewer.ID, // Use AssigneeID as reviewer ID - ReviewID: review.ID, - }) - if err != nil { - return nil, err - } + comment, err := CreateComment(ctx, &CreateCommentOptions{ + Type: CommentTypeReviewRequest, + Doer: doer, + Repo: issue.Repo, + Issue: issue, + RemovedAssignee: false, // Use RemovedAssignee as !isRequest + AssigneeID: reviewer.ID, // Use AssigneeID as reviewer ID + ReviewID: review.ID, + }) + if err != nil { + return nil, err + } - // func caller use the created comment to retrieve created review too. - comment.Review = review + // func caller use the created comment to retrieve created review too. + comment.Review = review - return comment, committer.Commit() + return comment, nil + }) } // RemoveReviewRequest remove a review request from one reviewer func RemoveReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_model.User) (*Comment, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID) - if err != nil && !IsErrReviewNotExist(err) { - return nil, err - } + return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) { + review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID) + if err != nil && !IsErrReviewNotExist(err) { + return nil, err + } - if review == nil || review.Type != ReviewTypeRequest { - return nil, nil - } + if review == nil || review.Type != ReviewTypeRequest { + return nil, nil + } - if _, err = db.DeleteByBean(ctx, review); err != nil { - return nil, err - } + if _, err = db.DeleteByBean(ctx, review); err != nil { + return nil, err + } - official, err := IsOfficialReviewer(ctx, issue, reviewer) - if err != nil { - return nil, err - } else if official { - if err := restoreLatestOfficialReview(ctx, issue.ID, reviewer.ID); err != nil { + official, err := IsOfficialReviewer(ctx, issue, reviewer) + if err != nil { return nil, err + } else if official { + if err := restoreLatestOfficialReview(ctx, issue.ID, reviewer.ID); err != nil { + return nil, err + } } - } - comment, err := CreateComment(ctx, &CreateCommentOptions{ - Type: CommentTypeReviewRequest, - Doer: doer, - Repo: issue.Repo, - Issue: issue, - RemovedAssignee: true, // Use RemovedAssignee as !isRequest - AssigneeID: reviewer.ID, // Use AssigneeID as reviewer ID + return CreateComment(ctx, &CreateCommentOptions{ + Type: CommentTypeReviewRequest, + Doer: doer, + Repo: issue.Repo, + Issue: issue, + RemovedAssignee: true, // Use RemovedAssignee as !isRequest + AssigneeID: reviewer.ID, // Use AssigneeID as reviewer ID + }) }) - if err != nil { - return nil, err - } - - return comment, committer.Commit() } // Recalculate the latest official review for reviewer @@ -787,120 +768,112 @@ func restoreLatestOfficialReview(ctx context.Context, issueID, reviewerID int64) // AddTeamReviewRequest add a review request from one team func AddTeamReviewRequest(ctx context.Context, issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() + return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) { + review, err := GetTeamReviewerByIssueIDAndTeamID(ctx, issue.ID, reviewer.ID) + if err != nil && !IsErrReviewNotExist(err) { + return nil, err + } - review, err := GetTeamReviewerByIssueIDAndTeamID(ctx, issue.ID, reviewer.ID) - if err != nil && !IsErrReviewNotExist(err) { - return nil, err - } + // This team already has been requested to review - therefore skip this. + if review != nil { + return nil, nil + } - // This team already has been requested to review - therefore skip this. - if review != nil { - return nil, nil - } + official, err := IsOfficialReviewerTeam(ctx, issue, reviewer) + if err != nil { + return nil, fmt.Errorf("isOfficialReviewerTeam(): %w", err) + } else if !official { + if official, err = IsOfficialReviewer(ctx, issue, doer); err != nil { + return nil, fmt.Errorf("isOfficialReviewer(): %w", err) + } + } - official, err := IsOfficialReviewerTeam(ctx, issue, reviewer) - if err != nil { - return nil, fmt.Errorf("isOfficialReviewerTeam(): %w", err) - } else if !official { - if official, err = IsOfficialReviewer(ctx, issue, doer); err != nil { - return nil, fmt.Errorf("isOfficialReviewer(): %w", err) + if review, err = CreateReview(ctx, CreateReviewOptions{ + Type: ReviewTypeRequest, + Issue: issue, + ReviewerTeam: reviewer, + Official: official, + Stale: false, + }); err != nil { + return nil, err } - } - if review, err = CreateReview(ctx, CreateReviewOptions{ - Type: ReviewTypeRequest, - Issue: issue, - ReviewerTeam: reviewer, - Official: official, - Stale: false, - }); err != nil { - return nil, err - } + if official { + if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_team_id=?", false, issue.ID, reviewer.ID); err != nil { + return nil, err + } + } - if official { - if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_team_id=?", false, issue.ID, reviewer.ID); err != nil { - return nil, err + comment, err := CreateComment(ctx, &CreateCommentOptions{ + Type: CommentTypeReviewRequest, + Doer: doer, + Repo: issue.Repo, + Issue: issue, + RemovedAssignee: false, // Use RemovedAssignee as !isRequest + AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team ID + ReviewID: review.ID, + }) + if err != nil { + return nil, fmt.Errorf("CreateComment(): %w", err) } - } - comment, err := CreateComment(ctx, &CreateCommentOptions{ - Type: CommentTypeReviewRequest, - Doer: doer, - Repo: issue.Repo, - Issue: issue, - RemovedAssignee: false, // Use RemovedAssignee as !isRequest - AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team ID - ReviewID: review.ID, + return comment, nil }) - if err != nil { - return nil, fmt.Errorf("CreateComment(): %w", err) - } - - return comment, committer.Commit() } // RemoveTeamReviewRequest remove a review request from one team func RemoveTeamReviewRequest(ctx context.Context, issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - review, err := GetTeamReviewerByIssueIDAndTeamID(ctx, issue.ID, reviewer.ID) - if err != nil && !IsErrReviewNotExist(err) { - return nil, err - } - - if review == nil { - return nil, nil - } - - if _, err = db.DeleteByBean(ctx, review); err != nil { - return nil, err - } + return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) { + review, err := GetTeamReviewerByIssueIDAndTeamID(ctx, issue.ID, reviewer.ID) + if err != nil && !IsErrReviewNotExist(err) { + return nil, err + } - official, err := IsOfficialReviewerTeam(ctx, issue, reviewer) - if err != nil { - return nil, fmt.Errorf("isOfficialReviewerTeam(): %w", err) - } + if review == nil { + return nil, nil + } - if official { - // recalculate which is the latest official review from that team - review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, -reviewer.ID) - if err != nil && !IsErrReviewNotExist(err) { + if _, err = db.DeleteByBean(ctx, review); err != nil { return nil, err } - if review != nil { - if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE id=?", true, review.ID); err != nil { + official, err := IsOfficialReviewerTeam(ctx, issue, reviewer) + if err != nil { + return nil, fmt.Errorf("isOfficialReviewerTeam(): %w", err) + } + + if official { + // recalculate which is the latest official review from that team + review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, -reviewer.ID) + if err != nil && !IsErrReviewNotExist(err) { return nil, err } + + if review != nil { + if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE id=?", true, review.ID); err != nil { + return nil, err + } + } } - } - if doer == nil { - return nil, committer.Commit() - } + if doer == nil { + return nil, nil + } - comment, err := CreateComment(ctx, &CreateCommentOptions{ - Type: CommentTypeReviewRequest, - Doer: doer, - Repo: issue.Repo, - Issue: issue, - RemovedAssignee: true, // Use RemovedAssignee as !isRequest - AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team ID - }) - if err != nil { - return nil, fmt.Errorf("CreateComment(): %w", err) - } + comment, err := CreateComment(ctx, &CreateCommentOptions{ + Type: CommentTypeReviewRequest, + Doer: doer, + Repo: issue.Repo, + Issue: issue, + RemovedAssignee: true, // Use RemovedAssignee as !isRequest + AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team ID + }) + if err != nil { + return nil, fmt.Errorf("CreateComment(): %w", err) + } - return comment, committer.Commit() + return comment, nil + }) } // MarkConversation Add or remove Conversation mark for a code comment @@ -966,61 +939,56 @@ func CanMarkConversation(ctx context.Context, issue *Issue, doer *user_model.Use // DeleteReview delete a review and it's code comments func DeleteReview(ctx context.Context, r *Review) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if r.ID == 0 { - return errors.New("review is not allowed to be 0") - } - - if r.Type == ReviewTypeRequest { - return errors.New("review request can not be deleted using this method") - } + return db.WithTx(ctx, func(ctx context.Context) error { + if r.ID == 0 { + return errors.New("review is not allowed to be 0") + } - opts := FindCommentsOptions{ - Type: CommentTypeCode, - IssueID: r.IssueID, - ReviewID: r.ID, - } + if r.Type == ReviewTypeRequest { + return errors.New("review request can not be deleted using this method") + } - if _, err := db.Delete[Comment](ctx, opts); err != nil { - return err - } + opts := FindCommentsOptions{ + Type: CommentTypeCode, + IssueID: r.IssueID, + ReviewID: r.ID, + } - opts = FindCommentsOptions{ - Type: CommentTypeReview, - IssueID: r.IssueID, - ReviewID: r.ID, - } + if _, err := db.Delete[Comment](ctx, opts); err != nil { + return err + } - if _, err := db.Delete[Comment](ctx, opts); err != nil { - return err - } + opts = FindCommentsOptions{ + Type: CommentTypeReview, + IssueID: r.IssueID, + ReviewID: r.ID, + } - opts = FindCommentsOptions{ - Type: CommentTypeDismissReview, - IssueID: r.IssueID, - ReviewID: r.ID, - } + if _, err := db.Delete[Comment](ctx, opts); err != nil { + return err + } - if _, err := db.Delete[Comment](ctx, opts); err != nil { - return err - } + opts = FindCommentsOptions{ + Type: CommentTypeDismissReview, + IssueID: r.IssueID, + ReviewID: r.ID, + } - if _, err := db.DeleteByID[Review](ctx, r.ID); err != nil { - return err - } + if _, err := db.Delete[Comment](ctx, opts); err != nil { + return err + } - if r.Official { - if err := restoreLatestOfficialReview(ctx, r.IssueID, r.ReviewerID); err != nil { + if _, err := db.DeleteByID[Review](ctx, r.ID); err != nil { return err } - } - return committer.Commit() + if r.Official { + if err := restoreLatestOfficialReview(ctx, r.IssueID, r.ReviewerID); err != nil { + return err + } + } + return nil + }) } // GetCodeCommentsCount return count of CodeComments a Review has diff --git a/models/issues/tracked_time.go b/models/issues/tracked_time.go index 2afbe272ed..9c11881e44 100644 --- a/models/issues/tracked_time.go +++ b/models/issues/tracked_time.go @@ -168,35 +168,31 @@ func GetTrackedSeconds(ctx context.Context, opts FindTrackedTimesOptions) (track // AddTime will add the given time (in seconds) to the issue func AddTime(ctx context.Context, user *user_model.User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - t, err := addTime(ctx, user, issue, amount, created) - if err != nil { - return nil, err - } + return db.WithTx2(ctx, func(ctx context.Context) (*TrackedTime, error) { + t, err := addTime(ctx, user, issue, amount, created) + if err != nil { + return nil, err + } - if err := issue.LoadRepo(ctx); err != nil { - return nil, err - } + if err := issue.LoadRepo(ctx); err != nil { + return nil, err + } - if _, err := CreateComment(ctx, &CreateCommentOptions{ - Issue: issue, - Repo: issue.Repo, - Doer: user, - // Content before v1.21 did store the formatted string instead of seconds, - // so use "|" as delimiter to mark the new format - Content: fmt.Sprintf("|%d", amount), - Type: CommentTypeAddTimeManual, - TimeID: t.ID, - }); err != nil { - return nil, err - } + if _, err := CreateComment(ctx, &CreateCommentOptions{ + Issue: issue, + Repo: issue.Repo, + Doer: user, + // Content before v1.21 did store the formatted string instead of seconds, + // so use "|" as delimiter to mark the new format + Content: fmt.Sprintf("|%d", amount), + Type: CommentTypeAddTimeManual, + TimeID: t.ID, + }); err != nil { + return nil, err + } - return t, committer.Commit() + return t, nil + }) } func addTime(ctx context.Context, user *user_model.User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) { @@ -241,72 +237,58 @@ func TotalTimesForEachUser(ctx context.Context, options *FindTrackedTimesOptions // DeleteIssueUserTimes deletes times for issue func DeleteIssueUserTimes(ctx context.Context, issue *Issue, user *user_model.User) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - opts := FindTrackedTimesOptions{ - IssueID: issue.ID, - UserID: user.ID, - } + return db.WithTx(ctx, func(ctx context.Context) error { + opts := FindTrackedTimesOptions{ + IssueID: issue.ID, + UserID: user.ID, + } - removedTime, err := deleteTimes(ctx, opts) - if err != nil { - return err - } - if removedTime == 0 { - return db.ErrNotExist{Resource: "tracked_time"} - } + removedTime, err := deleteTimes(ctx, opts) + if err != nil { + return err + } + if removedTime == 0 { + return db.ErrNotExist{Resource: "tracked_time"} + } - if err := issue.LoadRepo(ctx); err != nil { - return err - } - if _, err := CreateComment(ctx, &CreateCommentOptions{ - Issue: issue, - Repo: issue.Repo, - Doer: user, - // Content before v1.21 did store the formatted string instead of seconds, - // so use "|" as delimiter to mark the new format - Content: fmt.Sprintf("|%d", removedTime), - Type: CommentTypeDeleteTimeManual, - }); err != nil { + if err := issue.LoadRepo(ctx); err != nil { + return err + } + _, err = CreateComment(ctx, &CreateCommentOptions{ + Issue: issue, + Repo: issue.Repo, + Doer: user, + // Content before v1.21 did store the formatted string instead of seconds, + // so use "|" as delimiter to mark the new format + Content: fmt.Sprintf("|%d", removedTime), + Type: CommentTypeDeleteTimeManual, + }) return err - } - - return committer.Commit() + }) } // DeleteTime delete a specific Time func DeleteTime(ctx context.Context, t *TrackedTime) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err := t.LoadAttributes(ctx); err != nil { - return err - } + return db.WithTx(ctx, func(ctx context.Context) error { + if err := t.LoadAttributes(ctx); err != nil { + return err + } - if err := deleteTime(ctx, t); err != nil { - return err - } + if err := deleteTime(ctx, t); err != nil { + return err + } - if _, err := CreateComment(ctx, &CreateCommentOptions{ - Issue: t.Issue, - Repo: t.Issue.Repo, - Doer: t.User, - // Content before v1.21 did store the formatted string instead of seconds, - // so use "|" as delimiter to mark the new format - Content: fmt.Sprintf("|%d", t.Time), - Type: CommentTypeDeleteTimeManual, - }); err != nil { + _, err := CreateComment(ctx, &CreateCommentOptions{ + Issue: t.Issue, + Repo: t.Issue.Repo, + Doer: t.User, + // Content before v1.21 did store the formatted string instead of seconds, + // so use "|" as delimiter to mark the new format + Content: fmt.Sprintf("|%d", t.Time), + Type: CommentTypeDeleteTimeManual, + }) return err - } - - return committer.Commit() + }) } func deleteTimes(ctx context.Context, opts FindTrackedTimesOptions) (removedTime int64, err error) { diff --git a/models/organization/org.go b/models/organization/org.go index 0f3aef146c..5eba004d69 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -310,74 +310,69 @@ func CreateOrganization(ctx context.Context, org *Organization, owner *user_mode org.NumMembers = 1 org.Type = user_model.UserTypeOrganization - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err = user_model.DeleteUserRedirect(ctx, org.Name); err != nil { - return err - } - - if err = db.Insert(ctx, org); err != nil { - return fmt.Errorf("insert organization: %w", err) - } - if err = user_model.GenerateRandomAvatar(ctx, org.AsUser()); err != nil { - return fmt.Errorf("generate random avatar: %w", err) - } + return db.WithTx(ctx, func(ctx context.Context) error { + if err = user_model.DeleteUserRedirect(ctx, org.Name); err != nil { + return err + } - // Add initial creator to organization and owner team. - if err = db.Insert(ctx, &OrgUser{ - UID: owner.ID, - OrgID: org.ID, - IsPublic: setting.Service.DefaultOrgMemberVisible, - }); err != nil { - return fmt.Errorf("insert org-user relation: %w", err) - } + if err = db.Insert(ctx, org); err != nil { + return fmt.Errorf("insert organization: %w", err) + } + if err = user_model.GenerateRandomAvatar(ctx, org.AsUser()); err != nil { + return fmt.Errorf("generate random avatar: %w", err) + } - // Create default owner team. - t := &Team{ - OrgID: org.ID, - LowerName: strings.ToLower(OwnerTeamName), - Name: OwnerTeamName, - AccessMode: perm.AccessModeOwner, - NumMembers: 1, - IncludesAllRepositories: true, - CanCreateOrgRepo: true, - } - if err = db.Insert(ctx, t); err != nil { - return fmt.Errorf("insert owner team: %w", err) - } + // Add initial creator to organization and owner team. + if err = db.Insert(ctx, &OrgUser{ + UID: owner.ID, + OrgID: org.ID, + IsPublic: setting.Service.DefaultOrgMemberVisible, + }); err != nil { + return fmt.Errorf("insert org-user relation: %w", err) + } - // insert units for team - units := make([]TeamUnit, 0, len(unit.AllRepoUnitTypes)) - for _, tp := range unit.AllRepoUnitTypes { - up := perm.AccessModeOwner - if tp == unit.TypeExternalTracker || tp == unit.TypeExternalWiki { - up = perm.AccessModeRead + // Create default owner team. + t := &Team{ + OrgID: org.ID, + LowerName: strings.ToLower(OwnerTeamName), + Name: OwnerTeamName, + AccessMode: perm.AccessModeOwner, + NumMembers: 1, + IncludesAllRepositories: true, + CanCreateOrgRepo: true, + } + if err = db.Insert(ctx, t); err != nil { + return fmt.Errorf("insert owner team: %w", err) } - units = append(units, TeamUnit{ - OrgID: org.ID, - TeamID: t.ID, - Type: tp, - AccessMode: up, - }) - } - if err = db.Insert(ctx, &units); err != nil { - return err - } + // insert units for team + units := make([]TeamUnit, 0, len(unit.AllRepoUnitTypes)) + for _, tp := range unit.AllRepoUnitTypes { + up := perm.AccessModeOwner + if tp == unit.TypeExternalTracker || tp == unit.TypeExternalWiki { + up = perm.AccessModeRead + } + units = append(units, TeamUnit{ + OrgID: org.ID, + TeamID: t.ID, + Type: tp, + AccessMode: up, + }) + } - if err = db.Insert(ctx, &TeamUser{ - UID: owner.ID, - OrgID: org.ID, - TeamID: t.ID, - }); err != nil { - return fmt.Errorf("insert team-user relation: %w", err) - } + if err = db.Insert(ctx, &units); err != nil { + return err + } - return committer.Commit() + if err = db.Insert(ctx, &TeamUser{ + UID: owner.ID, + OrgID: org.ID, + TeamID: t.ID, + }); err != nil { + return fmt.Errorf("insert team-user relation: %w", err) + } + return nil + }) } // GetOrgByName returns organization by given name. @@ -499,31 +494,26 @@ func AddOrgUser(ctx context.Context, orgID, uid int64) error { return err } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - // check in transaction - isAlreadyMember, err = IsOrganizationMember(ctx, orgID, uid) - if err != nil || isAlreadyMember { - return err - } - - ou := &OrgUser{ - UID: uid, - OrgID: orgID, - IsPublic: setting.Service.DefaultOrgMemberVisible, - } + return db.WithTx(ctx, func(ctx context.Context) error { + // check in transaction + isAlreadyMember, err = IsOrganizationMember(ctx, orgID, uid) + if err != nil || isAlreadyMember { + return err + } - if err := db.Insert(ctx, ou); err != nil { - return err - } else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members = num_members + 1 WHERE id = ?", orgID); err != nil { - return err - } + ou := &OrgUser{ + UID: uid, + OrgID: orgID, + IsPublic: setting.Service.DefaultOrgMemberVisible, + } - return committer.Commit() + if err := db.Insert(ctx, ou); err != nil { + return err + } else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members = num_members + 1 WHERE id = ?", orgID); err != nil { + return err + } + return nil + }) } // GetOrgByID returns the user object by given ID if exists. diff --git a/models/organization/team_unit.go b/models/organization/team_unit.go index 3087b70770..c6ec6b39b2 100644 --- a/models/organization/team_unit.go +++ b/models/organization/team_unit.go @@ -31,21 +31,16 @@ func getUnitsByTeamID(ctx context.Context, teamID int64) (units []*TeamUnit, err // UpdateTeamUnits updates a teams's units func UpdateTeamUnits(ctx context.Context, team *Team, units []TeamUnit) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if _, err = db.GetEngine(ctx).Where("team_id = ?", team.ID).Delete(new(TeamUnit)); err != nil { - return err - } - - if len(units) > 0 { - if err = db.Insert(ctx, units); err != nil { + return db.WithTx(ctx, func(ctx context.Context) error { + if _, err = db.GetEngine(ctx).Where("team_id = ?", team.ID).Delete(new(TeamUnit)); err != nil { return err } - } - return committer.Commit() + if len(units) > 0 { + if err = db.Insert(ctx, units); err != nil { + return err + } + } + return nil + }) } diff --git a/models/project/project.go b/models/project/project.go index f516466854..c003664fa3 100644 --- a/models/project/project.go +++ b/models/project/project.go @@ -359,41 +359,25 @@ func updateRepositoryProjectCount(ctx context.Context, repoID int64) error { // ChangeProjectStatusByRepoIDAndID toggles a project between opened and closed func ChangeProjectStatusByRepoIDAndID(ctx context.Context, repoID, projectID int64, isClosed bool) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - p := new(Project) - - has, err := db.GetEngine(ctx).ID(projectID).Where("repo_id = ?", repoID).Get(p) - if err != nil { - return err - } else if !has { - return ErrProjectNotExist{ID: projectID, RepoID: repoID} - } + return db.WithTx(ctx, func(ctx context.Context) error { + p := new(Project) - if err := changeProjectStatus(ctx, p, isClosed); err != nil { - return err - } + has, err := db.GetEngine(ctx).ID(projectID).Where("repo_id = ?", repoID).Get(p) + if err != nil { + return err + } else if !has { + return ErrProjectNotExist{ID: projectID, RepoID: repoID} + } - return committer.Commit() + return changeProjectStatus(ctx, p, isClosed) + }) } // ChangeProjectStatus toggle a project between opened and closed func ChangeProjectStatus(ctx context.Context, p *Project, isClosed bool) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err := changeProjectStatus(ctx, p, isClosed); err != nil { - return err - } - - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + return changeProjectStatus(ctx, p, isClosed) + }) } func changeProjectStatus(ctx context.Context, p *Project, isClosed bool) error { diff --git a/models/repo.go b/models/repo.go index 9bc67079a9..522debb9fe 100644 --- a/models/repo.go +++ b/models/repo.go @@ -290,19 +290,14 @@ func UpdateRepoStats(ctx context.Context, id int64) error { } func updateUserStarNumbers(ctx context.Context, users []user_model.User) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - for _, user := range users { - if _, err = db.Exec(ctx, "UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?", user.ID, user.ID); err != nil { - return err + return db.WithTx(ctx, func(ctx context.Context) error { + for _, user := range users { + if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?", user.ID, user.ID); err != nil { + return err + } } - } - - return committer.Commit() + return nil + }) } // DoctorUserStarNum recalculate Stars number for all user diff --git a/models/repo/language_stats.go b/models/repo/language_stats.go index 7db8cd4dd2..1cddd25f1d 100644 --- a/models/repo/language_stats.go +++ b/models/repo/language_stats.go @@ -141,101 +141,90 @@ func GetTopLanguageStats(ctx context.Context, repo *Repository, limit int) (Lang // UpdateLanguageStats updates the language statistics for repository func UpdateLanguageStats(ctx context.Context, repo *Repository, commitID string, stats map[string]int64) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - sess := db.GetEngine(ctx) + return db.WithTx(ctx, func(ctx context.Context) error { + sess := db.GetEngine(ctx) - oldstats, err := GetLanguageStats(ctx, repo) - if err != nil { - return err - } - var topLang string - var s int64 - for lang, size := range stats { - if size > s { - s = size - topLang = lang + oldstats, err := GetLanguageStats(ctx, repo) + if err != nil { + return err + } + var topLang string + var s int64 + for lang, size := range stats { + if size > s { + s = size + topLang = lang + } } - } - for lang, size := range stats { - upd := false - for _, s := range oldstats { - // Update already existing language - if strings.EqualFold(s.Language, lang) { - s.CommitID = commitID - s.IsPrimary = lang == topLang - s.Size = size - if _, err := sess.ID(s.ID).Cols("`commit_id`", "`size`", "`is_primary`").Update(s); err != nil { + for lang, size := range stats { + upd := false + for _, s := range oldstats { + // Update already existing language + if strings.EqualFold(s.Language, lang) { + s.CommitID = commitID + s.IsPrimary = lang == topLang + s.Size = size + if _, err := sess.ID(s.ID).Cols("`commit_id`", "`size`", "`is_primary`").Update(s); err != nil { + return err + } + upd = true + break + } + } + // Insert new language + if !upd { + if err := db.Insert(ctx, &LanguageStat{ + RepoID: repo.ID, + CommitID: commitID, + IsPrimary: lang == topLang, + Language: lang, + Size: size, + }); err != nil { return err } - upd = true - break } } - // Insert new language - if !upd { - if err := db.Insert(ctx, &LanguageStat{ - RepoID: repo.ID, - CommitID: commitID, - IsPrimary: lang == topLang, - Language: lang, - Size: size, - }); err != nil { - return err + // Delete old languages + statsToDelete := make([]int64, 0, len(oldstats)) + for _, s := range oldstats { + if s.CommitID != commitID { + statsToDelete = append(statsToDelete, s.ID) } } - } - // Delete old languages - statsToDelete := make([]int64, 0, len(oldstats)) - for _, s := range oldstats { - if s.CommitID != commitID { - statsToDelete = append(statsToDelete, s.ID) - } - } - if len(statsToDelete) > 0 { - if _, err := sess.In("`id`", statsToDelete).Delete(&LanguageStat{}); err != nil { - return err + if len(statsToDelete) > 0 { + if _, err := sess.In("`id`", statsToDelete).Delete(&LanguageStat{}); err != nil { + return err + } } - } - // Update indexer status - if err = UpdateIndexerStatus(ctx, repo, RepoIndexerTypeStats, commitID); err != nil { - return err - } - - return committer.Commit() + // Update indexer status + return UpdateIndexerStatus(ctx, repo, RepoIndexerTypeStats, commitID) + }) } // CopyLanguageStat Copy originalRepo language stat information to destRepo (use for forked repo) func CopyLanguageStat(ctx context.Context, originalRepo, destRepo *Repository) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - RepoLang := make(LanguageStatList, 0, 6) - if err := db.GetEngine(ctx).Where("`repo_id` = ?", originalRepo.ID).Desc("`size`").Find(&RepoLang); err != nil { - return err - } - if len(RepoLang) > 0 { - for i := range RepoLang { - RepoLang[i].ID = 0 - RepoLang[i].RepoID = destRepo.ID - RepoLang[i].CreatedUnix = timeutil.TimeStampNow() - } - // update destRepo's indexer status - tmpCommitID := RepoLang[0].CommitID - if err := UpdateIndexerStatus(ctx, destRepo, RepoIndexerTypeStats, tmpCommitID); err != nil { + return db.WithTx(ctx, func(ctx context.Context) error { + RepoLang := make(LanguageStatList, 0, 6) + if err := db.GetEngine(ctx).Where("`repo_id` = ?", originalRepo.ID).Desc("`size`").Find(&RepoLang); err != nil { return err } - if err := db.Insert(ctx, &RepoLang); err != nil { - return err + if len(RepoLang) > 0 { + for i := range RepoLang { + RepoLang[i].ID = 0 + RepoLang[i].RepoID = destRepo.ID + RepoLang[i].CreatedUnix = timeutil.TimeStampNow() + } + // update destRepo's indexer status + tmpCommitID := RepoLang[0].CommitID + if err := UpdateIndexerStatus(ctx, destRepo, RepoIndexerTypeStats, tmpCommitID); err != nil { + return err + } + if err := db.Insert(ctx, &RepoLang); err != nil { + return err + } } - } - return committer.Commit() + return nil + }) } diff --git a/models/repo/release.go b/models/repo/release.go index 59f4caf5aa..0db57503ce 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -472,30 +472,24 @@ func (r *Release) GetExternalID() int64 { return r.OriginalAuthorID } // InsertReleases migrates release func InsertReleases(ctx context.Context, rels ...*Release) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - sess := db.GetEngine(ctx) - - for _, rel := range rels { - if _, err := sess.NoAutoTime().Insert(rel); err != nil { - return err - } - - if len(rel.Attachments) > 0 { - for i := range rel.Attachments { - rel.Attachments[i].ReleaseID = rel.ID + return db.WithTx(ctx, func(ctx context.Context) error { + for _, rel := range rels { + if _, err := db.GetEngine(ctx).NoAutoTime().Insert(rel); err != nil { + return err } - if _, err := sess.NoAutoTime().Insert(rel.Attachments); err != nil { - return err + if len(rel.Attachments) > 0 { + for i := range rel.Attachments { + rel.Attachments[i].ReleaseID = rel.ID + } + + if _, err := db.GetEngine(ctx).NoAutoTime().Insert(rel.Attachments); err != nil { + return err + } } } - } - - return committer.Commit() + return nil + }) } func FindTagsByCommitIDs(ctx context.Context, repoID int64, commitIDs ...string) (map[string][]*Release, error) { diff --git a/models/repo/star.go b/models/repo/star.go index 4c66855525..bc865f8373 100644 --- a/models/repo/star.go +++ b/models/repo/star.go @@ -25,48 +25,45 @@ func init() { // StarRepo or unstar repository. func StarRepo(ctx context.Context, doer *user_model.User, repo *Repository, star bool) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - staring := IsStaring(ctx, doer.ID, repo.ID) - - if star { - if user_model.IsUserBlockedBy(ctx, doer, repo.OwnerID) { - return user_model.ErrBlockedUser - } - - if staring { - return nil - } - - if err := db.Insert(ctx, &Star{UID: doer.ID, RepoID: repo.ID}); err != nil { - return err - } - if _, err := db.Exec(ctx, "UPDATE `repository` SET num_stars = num_stars + 1 WHERE id = ?", repo.ID); err != nil { - return err + return db.WithTx(ctx, func(ctx context.Context) error { + staring := IsStaring(ctx, doer.ID, repo.ID) + + if star { + if user_model.IsUserBlockedBy(ctx, doer, repo.OwnerID) { + return user_model.ErrBlockedUser + } + + if staring { + return nil + } + + if err := db.Insert(ctx, &Star{UID: doer.ID, RepoID: repo.ID}); err != nil { + return err + } + if _, err := db.Exec(ctx, "UPDATE `repository` SET num_stars = num_stars + 1 WHERE id = ?", repo.ID); err != nil { + return err + } + if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars = num_stars + 1 WHERE id = ?", doer.ID); err != nil { + return err + } + } else { + if !staring { + return nil + } + + if _, err := db.DeleteByBean(ctx, &Star{UID: doer.ID, RepoID: repo.ID}); err != nil { + return err + } + if _, err := db.Exec(ctx, "UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repo.ID); err != nil { + return err + } + if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars = num_stars - 1 WHERE id = ?", doer.ID); err != nil { + return err + } } - if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars = num_stars + 1 WHERE id = ?", doer.ID); err != nil { - return err - } - } else { - if !staring { - return nil - } - - if _, err := db.DeleteByBean(ctx, &Star{UID: doer.ID, RepoID: repo.ID}); err != nil { - return err - } - if _, err := db.Exec(ctx, "UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repo.ID); err != nil { - return err - } - if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars = num_stars - 1 WHERE id = ?", doer.ID); err != nil { - return err - } - } - return committer.Commit() + return nil + }) } // IsStaring checks if user has starred given repository. diff --git a/models/repo/topic.go b/models/repo/topic.go index 430a60f603..baeae01efa 100644 --- a/models/repo/topic.go +++ b/models/repo/topic.go @@ -227,32 +227,26 @@ func GetRepoTopicByName(ctx context.Context, repoID int64, topicName string) (*T // AddTopic adds a topic name to a repository (if it does not already have it) func AddTopic(ctx context.Context, repoID int64, topicName string) (*Topic, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - sess := db.GetEngine(ctx) - - topic, err := GetRepoTopicByName(ctx, repoID, topicName) - if err != nil { - return nil, err - } - if topic != nil { - // Repo already have topic - return topic, nil - } - - topic, err = addTopicByNameToRepo(ctx, repoID, topicName) - if err != nil { - return nil, err - } + return db.WithTx2(ctx, func(ctx context.Context) (*Topic, error) { + topic, err := GetRepoTopicByName(ctx, repoID, topicName) + if err != nil { + return nil, err + } + if topic != nil { + // Repo already have topic + return topic, nil + } - if err = syncTopicsInRepository(sess, repoID); err != nil { - return nil, err - } + topic, err = addTopicByNameToRepo(ctx, repoID, topicName) + if err != nil { + return nil, err + } - return topic, committer.Commit() + if err = syncTopicsInRepository(ctx, repoID); err != nil { + return nil, err + } + return topic, nil + }) } // DeleteTopic removes a topic name from a repository (if it has it) @@ -266,14 +260,15 @@ func DeleteTopic(ctx context.Context, repoID int64, topicName string) (*Topic, e return nil, nil } - err = removeTopicFromRepo(ctx, repoID, topic) - if err != nil { - return nil, err - } - - err = syncTopicsInRepository(db.GetEngine(ctx), repoID) - - return topic, err + return db.WithTx2(ctx, func(ctx context.Context) (*Topic, error) { + if err = removeTopicFromRepo(ctx, repoID, topic); err != nil { + return nil, err + } + if err = syncTopicsInRepository(ctx, repoID); err != nil { + return nil, err + } + return topic, nil + }) } // SaveTopics save topics to a repository @@ -285,64 +280,55 @@ func SaveTopics(ctx context.Context, repoID int64, topicNames ...string) error { return err } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - sess := db.GetEngine(ctx) + return db.WithTx(ctx, func(ctx context.Context) error { + var addedTopicNames []string + for _, topicName := range topicNames { + if strings.TrimSpace(topicName) == "" { + continue + } - var addedTopicNames []string - for _, topicName := range topicNames { - if strings.TrimSpace(topicName) == "" { - continue + var found bool + for _, t := range topics { + if strings.EqualFold(topicName, t.Name) { + found = true + break + } + } + if !found { + addedTopicNames = append(addedTopicNames, topicName) + } } - var found bool + var removeTopics []*Topic for _, t := range topics { - if strings.EqualFold(topicName, t.Name) { - found = true - break + var found bool + for _, topicName := range topicNames { + if strings.EqualFold(topicName, t.Name) { + found = true + break + } } - } - if !found { - addedTopicNames = append(addedTopicNames, topicName) - } - } - - var removeTopics []*Topic - for _, t := range topics { - var found bool - for _, topicName := range topicNames { - if strings.EqualFold(topicName, t.Name) { - found = true - break + if !found { + removeTopics = append(removeTopics, t) } } - if !found { - removeTopics = append(removeTopics, t) - } - } - for _, topicName := range addedTopicNames { - _, err := addTopicByNameToRepo(ctx, repoID, topicName) - if err != nil { - return err + for _, topicName := range addedTopicNames { + _, err := addTopicByNameToRepo(ctx, repoID, topicName) + if err != nil { + return err + } } - } - for _, topic := range removeTopics { - err := removeTopicFromRepo(ctx, repoID, topic) - if err != nil { - return err + for _, topic := range removeTopics { + err := removeTopicFromRepo(ctx, repoID, topic) + if err != nil { + return err + } } - } - if err := syncTopicsInRepository(sess, repoID); err != nil { - return err - } - - return committer.Commit() + return syncTopicsInRepository(ctx, repoID) + }) } // GenerateTopics generates topics from a template repository @@ -353,19 +339,19 @@ func GenerateTopics(ctx context.Context, templateRepo, generateRepo *Repository) } } - return syncTopicsInRepository(db.GetEngine(ctx), generateRepo.ID) + return syncTopicsInRepository(ctx, generateRepo.ID) } // syncTopicsInRepository makes sure topics in the topics table are copied into the topics field of the repository -func syncTopicsInRepository(sess db.Engine, repoID int64) error { +func syncTopicsInRepository(ctx context.Context, repoID int64) error { topicNames := make([]string, 0, 25) - if err := sess.Table("topic").Cols("name"). + if err := db.GetEngine(ctx).Table("topic").Cols("name"). Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id"). Where("repo_topic.repo_id = ?", repoID).Asc("topic.name").Find(&topicNames); err != nil { return err } - if _, err := sess.ID(repoID).Cols("topics").Update(&Repository{ + if _, err := db.GetEngine(ctx).ID(repoID).Cols("topics").Update(&Repository{ Topics: topicNames, }); err != nil { return err diff --git a/models/repo/update.go b/models/repo/update.go index 64065f11c4..3228ae11a4 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -19,11 +19,6 @@ func UpdateRepositoryOwnerNames(ctx context.Context, ownerID int64, ownerName st if ownerID == 0 { return nil } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() if _, err := db.GetEngine(ctx).Where("owner_id = ?", ownerID).Cols("owner_name").NoAutoTime().Update(&Repository{ OwnerName: ownerName, @@ -31,7 +26,7 @@ func UpdateRepositoryOwnerNames(ctx context.Context, ownerID int64, ownerName st return err } - return committer.Commit() + return nil } // UpdateRepositoryUpdatedTime updates a repository's updated time diff --git a/models/repo/upload.go b/models/repo/upload.go index 20a8fa26fe..f7d4749842 100644 --- a/models/repo/upload.go +++ b/models/repo/upload.go @@ -117,12 +117,6 @@ func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) { return nil } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - ids := make([]int64, len(uploads)) for i := range uploads { ids[i] = uploads[i].ID @@ -131,10 +125,6 @@ func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) { return fmt.Errorf("delete uploads: %w", err) } - if err = committer.Commit(); err != nil { - return err - } - for _, upload := range uploads { localPath := upload.LocalPath() isFile, err := util.IsFile(localPath) diff --git a/models/user/email_address.go b/models/user/email_address.go index 2ba6a56450..cb423945f8 100644 --- a/models/user/email_address.go +++ b/models/user/email_address.go @@ -256,15 +256,9 @@ func IsEmailUsed(ctx context.Context, email string) (bool, error) { // ActivateEmail activates the email address to given user. func ActivateEmail(ctx context.Context, email *EmailAddress) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - if err := updateActivation(ctx, email, true); err != nil { - return err - } - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + return updateActivation(ctx, email, true) + }) } func updateActivation(ctx context.Context, email *EmailAddress, activate bool) error { @@ -305,33 +299,30 @@ func makeEmailPrimaryInternal(ctx context.Context, emailID int64, isActive bool) return ErrUserNotExist{UID: email.UID} } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - sess := db.GetEngine(ctx) + return db.WithTx(ctx, func(ctx context.Context) error { + sess := db.GetEngine(ctx) - // 1. Update user table - user.Email = email.Email - if _, err = sess.ID(user.ID).Cols("email").Update(user); err != nil { - return err - } + // 1. Update user table + user.Email = email.Email + if _, err := sess.ID(user.ID).Cols("email").Update(user); err != nil { + return err + } - // 2. Update old primary email - if _, err = sess.Where("uid=? AND is_primary=?", email.UID, true).Cols("is_primary").Update(&EmailAddress{ - IsPrimary: false, - }); err != nil { - return err - } + // 2. Update old primary email + if _, err := sess.Where("uid=? AND is_primary=?", email.UID, true).Cols("is_primary").Update(&EmailAddress{ + IsPrimary: false, + }); err != nil { + return err + } - // 3. update new primary email - email.IsPrimary = true - if _, err = sess.ID(email.ID).Cols("is_primary").Update(email); err != nil { - return err - } + // 3. update new primary email + email.IsPrimary = true + if _, err := sess.ID(email.ID).Cols("is_primary").Update(email); err != nil { + return err + } - return committer.Commit() + return nil + }) } // ChangeInactivePrimaryEmail replaces the inactive primary email of a given user diff --git a/models/user/follow.go b/models/user/follow.go index cf9672109a..e098caab5b 100644 --- a/models/user/follow.go +++ b/models/user/follow.go @@ -38,24 +38,20 @@ func FollowUser(ctx context.Context, user, follow *User) (err error) { return ErrBlockedUser } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err = db.Insert(ctx, &Follow{UserID: user.ID, FollowID: follow.ID}); err != nil { - return err - } - - if _, err = db.Exec(ctx, "UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", follow.ID); err != nil { - return err - } - - if _, err = db.Exec(ctx, "UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", user.ID); err != nil { - return err - } - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + if err = db.Insert(ctx, &Follow{UserID: user.ID, FollowID: follow.ID}); err != nil { + return err + } + + if _, err = db.Exec(ctx, "UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", follow.ID); err != nil { + return err + } + + if _, err = db.Exec(ctx, "UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", user.ID); err != nil { + return err + } + return nil + }) } // UnfollowUser unmarks someone as another's follower. @@ -64,22 +60,18 @@ func UnfollowUser(ctx context.Context, userID, followID int64) (err error) { return nil } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() + return db.WithTx(ctx, func(ctx context.Context) error { + if _, err = db.DeleteByBean(ctx, &Follow{UserID: userID, FollowID: followID}); err != nil { + return err + } - if _, err = db.DeleteByBean(ctx, &Follow{UserID: userID, FollowID: followID}); err != nil { - return err - } + if _, err = db.Exec(ctx, "UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?", followID); err != nil { + return err + } - if _, err = db.Exec(ctx, "UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?", followID); err != nil { - return err - } - - if _, err = db.Exec(ctx, "UPDATE `user` SET num_following = num_following - 1 WHERE id = ?", userID); err != nil { - return err - } - return committer.Commit() + if _, err = db.Exec(ctx, "UPDATE `user` SET num_following = num_following - 1 WHERE id = ?", userID); err != nil { + return err + } + return nil + }) } diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go index b234d9ffee..7d4b2e2237 100644 --- a/models/webhook/webhook.go +++ b/models/webhook/webhook.go @@ -319,21 +319,16 @@ func UpdateWebhookLastStatus(ctx context.Context, w *Webhook) error { // DeleteWebhookByID uses argument bean as query condition, // ID must be specified and do not assign unnecessary fields. func DeleteWebhookByID(ctx context.Context, id int64) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if count, err := db.DeleteByID[Webhook](ctx, id); err != nil { - return err - } else if count == 0 { - return ErrWebhookNotExist{ID: id} - } else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: id}); err != nil { - return err - } - - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + if count, err := db.DeleteByID[Webhook](ctx, id); err != nil { + return err + } else if count == 0 { + return ErrWebhookNotExist{ID: id} + } else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: id}); err != nil { + return err + } + return nil + }) } // DeleteWebhookByRepoID deletes webhook of repository by given ID. diff --git a/modules/structs/issue.go b/modules/structs/issue.go index df0be8f9ec..322ac1e4ca 100644 --- a/modules/structs/issue.go +++ b/modules/structs/issue.go @@ -262,7 +262,8 @@ func (it IssueTemplate) Type() IssueTemplateType { // IssueMeta basic issue information // swagger:model type IssueMeta struct { - Index int64 `json:"index"` + Index int64 `json:"index"` + // owner of the issue's repo Owner string `json:"owner"` Name string `json:"repo"` } diff --git a/modules/structs/repo.go b/modules/structs/repo.go index aca5d9c3f4..f2e11b1542 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -48,15 +48,16 @@ type ExternalWiki struct { // Repository represents a repository type Repository struct { - ID int64 `json:"id"` - Owner *User `json:"owner"` - Name string `json:"name"` - FullName string `json:"full_name"` - Description string `json:"description"` - Empty bool `json:"empty"` - Private bool `json:"private"` - Fork bool `json:"fork"` - Template bool `json:"template"` + ID int64 `json:"id"` + Owner *User `json:"owner"` + Name string `json:"name"` + FullName string `json:"full_name"` + Description string `json:"description"` + Empty bool `json:"empty"` + Private bool `json:"private"` + Fork bool `json:"fork"` + Template bool `json:"template"` + // the original repository if this repository is a fork, otherwise null Parent *Repository `json:"parent,omitempty"` Mirror bool `json:"mirror"` Size int `json:"size"` @@ -225,15 +226,13 @@ type EditRepoOption struct { EnablePrune *bool `json:"enable_prune,omitempty"` } -// GenerateRepoOption options when creating repository using a template +// GenerateRepoOption options when creating a repository using a template // swagger:model type GenerateRepoOption struct { - // The organization or person who will own the new repository + // the organization's name or individual user's name who will own the new repository // // required: true Owner string `json:"owner"` - // Name of the repository to create - // // required: true // unique: true Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"` @@ -352,9 +351,9 @@ func (gt GitServiceType) Title() string { type MigrateRepoOptions struct { // required: true CloneAddr string `json:"clone_addr" binding:"Required"` - // deprecated (only for backwards compatibility) + // deprecated (only for backwards compatibility, use repo_owner instead) RepoOwnerID int64 `json:"uid"` - // Name of User or Organisation who will own Repo after migration + // the organization's name or individual user's name who will own the migrated repository RepoOwner string `json:"repo_owner"` // required: true RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index c1a3d37037..65ba4e9cd3 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -44,7 +44,7 @@ webauthn_use_twofa = Use a two-factor code from your phone webauthn_error = Could not read your security key. webauthn_unsupported_browser = Your browser does not currently support WebAuthn. webauthn_error_unknown = An unknown error occurred. Please retry. -webauthn_error_insecure = WebAuthn only supports secure connections. For testing over HTTP, you can use the origin "localhost" or "127.0.0.1" +webauthn_error_insecure = WebAuthn only supports secure connections. For testing over HTTP, you can use the origin "localhost" or "127.0.0.1". webauthn_error_unable_to_process = The server could not process your request. webauthn_error_duplicated = The security key is not permitted for this request. Please make sure that the key is not already registered. webauthn_error_empty = You must set a name for this key. @@ -168,7 +168,7 @@ no_results_found = No results found. internal_error_skipped = Internal error occurred but is skipped: %s [search] -search = Search... +search = Search… type_tooltip = Search type fuzzy = Fuzzy fuzzy_tooltip = Include results that closely match the search term @@ -178,23 +178,23 @@ regexp = Regexp regexp_tooltip = Include only results that match the regexp search term exact = Exact exact_tooltip = Include only results that match the exact search term -repo_kind = Search repos... -user_kind = Search users... -org_kind = Search orgs... -team_kind = Search teams... -code_kind = Search code... +repo_kind = Search repos… +user_kind = Search users… +org_kind = Search orgs… +team_kind = Search teams… +code_kind = Search code… code_search_unavailable = Code search is currently not available. Please contact the site administrator. code_search_by_git_grep = Current code search results are provided by "git grep". There might be better results if site administrator enables Repository Indexer. -package_kind = Search packages... -project_kind = Search projects... -branch_kind = Search branches... -tag_kind = Search tags... +package_kind = Search packages… +project_kind = Search projects… +branch_kind = Search branches… +tag_kind = Search tags… tag_tooltip = Search for matching tags. Use '%' to match any sequence of numbers. -commit_kind = Search commits... -runner_kind = Search runners... +commit_kind = Search commits… +runner_kind = Search runners… no_results = No matching results found. -issue_kind = Search issues... -pull_kind = Search pulls... +issue_kind = Search issues… +pull_kind = Search pull requests… keyword_search_unavailable = Searching by keyword is currently not available. Please contact the site administrator. [aria] @@ -230,8 +230,8 @@ buttons.enable_monospace_font = Enable monospace font buttons.disable_monospace_font = Disable monospace font [filter] -string.asc = A - Z -string.desc = Z - A +string.asc = A–Z +string.desc = Z–A [error] occurred = An error occurred @@ -252,7 +252,7 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s"> [install] install = Installation -installing_desc = Installing now, please wait... +installing_desc = Installing now, please wait… title = Initial Configuration docker_helper = If you run Gitea inside Docker, please read the <a target="_blank" rel="noopener noreferrer" href="%s">documentation</a> before changing any settings. require_db_desc = Gitea requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol). @@ -269,16 +269,16 @@ path = Path sqlite_helper = File path for the SQLite3 database.<br>Enter an absolute path if you run Gitea as a service. reinstall_error = You are trying to install into an existing Gitea database reinstall_confirm_message = Re-installing with an existing Gitea database can cause multiple problems. In most cases, you should use your existing "app.ini" to run Gitea. If you know what you are doing, confirm the following: -reinstall_confirm_check_1 = The data encrypted by the SECRET_KEY in app.ini may be lost: users may not be able to log in with 2FA/OTP & mirrors may not function correctly. By checking this box you confirm that the current app.ini file contains the correct the SECRET_KEY. -reinstall_confirm_check_2 = The repositories and settings may need to be re-synchronized. By checking this box you confirm that you will resynchronize the hooks for the repositories and authorized_keys file manually. You confirm that you will ensure that repository and mirror settings are correct. +reinstall_confirm_check_1 = The data encrypted by the SECRET_KEY in app.ini may be lost: users may not be able to log in with 2FA/OTP and mirrors may not function correctly. By checking this box, you confirm that the current app.ini file contains the correct the SECRET_KEY. +reinstall_confirm_check_2 = The repositories and settings may need to be resynchronized. By checking this box, you confirm that you will resynchronize the hooks for the repositories and authorized_keys file manually. You confirm that you will ensure that repository and mirror settings are correct. reinstall_confirm_check_3 = You confirm that you are absolutely sure that this Gitea is running with the correct app.ini location and that you are sure that you have to re-install. You confirm that you acknowledge the above risks. err_empty_db_path = The SQLite3 database path cannot be empty. no_admin_and_disable_registration = You cannot disable user self-registration without creating an administrator account. err_empty_admin_password = The administrator password cannot be empty. -err_empty_admin_email = The administrator email cannot be empty. -err_admin_name_is_reserved = Administrator Username is invalid, username is reserved -err_admin_name_pattern_not_allowed = Administrator username is invalid, the username matches a reserved pattern -err_admin_name_is_invalid = Administrator Username is invalid +err_empty_admin_email = The administrator email address cannot be empty. +err_admin_name_is_reserved = Administrator username is invalid. Username is reserved. +err_admin_name_pattern_not_allowed = Administrator username is invalid. The username matches a reserved pattern. +err_admin_name_is_invalid = Administrator username is invalid general_title = General Settings app_name = Site Title @@ -294,7 +294,7 @@ domain_helper = Domain or host address for the server. ssh_port = SSH Server Port ssh_port_helper = Port number your SSH server listens on. Leave empty to disable. http_port = Gitea HTTP Listen Port -http_port_helper = Port number the Giteas web server will listen on. +http_port_helper = Port number the Gitea web server will listen on. app_url = Gitea Base URL app_url_helper = Base address for HTTP(S) clone URLs and email notifications. log_root_path = Log Path @@ -430,8 +430,8 @@ allow_password_change = Require user to change password (recommended) reset_password_mail_sent_prompt = A confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the account recovery process. active_your_account = Activate Your Account account_activated = Account has been activated -prohibit_login = Sign In Prohibited -prohibit_login_desc = Your account is prohibited from signing in, please contact your site administrator. +prohibit_login = Sign-In Prohibited +prohibit_login_desc = Your account is prohibited from signing in. Please contact your site administrator. resent_limit_prompt = You have already requested an activation email recently. Please wait 3 minutes and try again. has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (<b>%s</b>). If you haven't received a confirmation email or need to resend a new one, please click on the button below. change_unconfirmed_mail_address = If your registration email address is incorrect, you can change it here and resend a new confirmation email. @@ -452,7 +452,7 @@ use_scratch_code = Use a scratch code twofa_scratch_used = You have used your scratch code. You have been redirected to the two-factor settings page so you may remove your device enrollment or generate a new scratch code. twofa_passcode_incorrect = Your passcode is incorrect. If you misplaced your device, use your scratch code to sign in. twofa_scratch_token_incorrect = Your scratch code is incorrect. -twofa_required = You must set up Two-Factor Authentication to get access to repositories, or try to log in again. +twofa_required = You must set up two-factor authentication to get access to repositories, or try to log in again. login_userpass = Sign In login_openid = OpenID oauth_signup_tab = Register New Account @@ -471,17 +471,17 @@ openid_connect_desc = The chosen OpenID URI is unknown. Associate it with a new openid_register_title = Create new account openid_register_desc = The chosen OpenID URI is unknown. Associate it with a new account here. openid_signin_desc = Enter your OpenID URI. For example: alice.openid.example.org or https://openid.example.org/alice. -disable_forgot_password_mail = Account recovery is disabled because no email is set up. Please contact your site administrator. -disable_forgot_password_mail_admin = Account recovery is only available when email is set up. Please set up email to enable account recovery. +disable_forgot_password_mail = Account recovery is disabled because no email address is set up. Please contact your site administrator. +disable_forgot_password_mail_admin = Account recovery is only available when an email address is set up. email_domain_blacklisted = You cannot register with your email address. authorize_application = Authorize Application authorize_redirect_notice = You will be redirected to %s if you authorize this application. authorize_application_created_by = This application was created by %s. -authorize_application_description = If you grant access, it will be able to access and write to all your account information, including private repos and organisations. +authorize_application_description = If you grant access, it will be able to access and write to all your account information, including private repos and organizations. authorize_application_with_scopes = With scopes: %s authorize_title = Authorize "%s" to access your account? authorization_failed = Authorization failed -authorization_failed_desc = The authorization failed because we detected an invalid request. Please contact the maintainer of the app you have tried to authorize. +authorization_failed_desc = The authorization failed because we detected an invalid request. Please contact the maintainer of the app you tried to authorize. sspi_auth_failed = SSPI authentication failed password_pwned = The password you chose is on a <a target="_blank" rel="noopener noreferrer" href="%s">list of stolen passwords</a> previously exposed in public data breaches. Please try again with a different password and consider changing this password elsewhere too. password_pwned_err = Could not complete request to HaveIBeenPwned @@ -545,7 +545,7 @@ release.download.targz = Source Code (TAR.GZ) repo.transfer.subject_to = %s would like to transfer "%s" to %s repo.transfer.subject_to_you = %s would like to transfer "%s" to you repo.transfer.to_you = you -repo.transfer.body = To accept or reject it visit %s or just ignore it. +repo.transfer.body = To accept or reject it, visit %s or just ignore it. repo.collaborator.added.subject = %s added you to %s repo.collaborator.added.text = You have been added as a collaborator of repository: @@ -597,7 +597,7 @@ url_error = `"%s" is not a valid URL.` include_error = ` must contain substring "%s".` glob_pattern_error = ` glob pattern is invalid: %s.` regex_pattern_error = ` regex pattern is invalid: %s.` -username_error = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-'), underscore ('_') and dot ('.'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.` +username_error = ` can only contain alphanumeric characters ('0-9','a-z','A-Z'), dash ('-'), underscore ('_') and dot ('.'). It cannot begin or end with non-alphanumeric characters, and consecutive non-alphanumeric characters are also forbidden.` invalid_group_team_map_error = ` mapping is invalid: %s` unknown_error = Unknown error: captcha_incorrect = The CAPTCHA code is incorrect. @@ -612,17 +612,17 @@ username_has_not_been_changed = Username has not been changed repo_name_been_taken = The repository name is already used. repository_force_private = Force Private is enabled: private repositories cannot be made public. repository_files_already_exist = Files already exist for this repository. Contact the system administrator. -repository_files_already_exist.adopt = Files already exist for this repository and can only be Adopted. +repository_files_already_exist.adopt = Files already exist for this repository and can only be adopted. repository_files_already_exist.delete = Files already exist for this repository. You must delete them. repository_files_already_exist.adopt_or_delete = Files already exist for this repository. Either adopt them or delete them. visit_rate_limit = Remote visit addressed rate limitation. -2fa_auth_required = Remote visit required two factors authentication. +2fa_auth_required = Remote visit required two-factor authentication. org_name_been_taken = The organization name is already taken. team_name_been_taken = The team name is already taken. team_no_units_error = Allow access to at least one repository section. email_been_used = The email address is already used. email_invalid = The email address is invalid. -email_domain_is_not_allowed = The domain of user email <b>%s</b> conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST. Please ensure your operation is expected. +email_domain_is_not_allowed = The domain of user email address <b>%s</b> conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST. Please ensure your operation is expected. openid_been_used = The OpenID address "%s" is already used. username_password_incorrect = Username or password is incorrect. password_complexity = Password does not pass complexity requirements: @@ -647,14 +647,14 @@ invalid_ssh_key = Cannot verify your SSH key: %s invalid_gpg_key = Cannot verify your GPG key: %s invalid_ssh_principal = Invalid principal: %s must_use_public_key = The key you provided is a private key. Please do not upload your private key anywhere. Use your public key instead. -unable_verify_ssh_key = "Cannot verify the SSH key, double-check it for mistakes." +unable_verify_ssh_key = "Cannot verify the SSH key. Double-check it for mistakes." auth_failed = Authentication failed: %v -still_own_repo = "Your account owns one or more repositories, delete or transfer them first." -still_has_org = "Your account is a member of one or more organizations, leave them first." -still_own_packages = "Your account owns one or more packages, delete them first." -org_still_own_repo = "This organization still owns one or more repositories, delete or transfer them first." -org_still_own_packages = "This organization still owns one or more packages, delete them first." +still_own_repo = "Your account owns one or more repositories. Delete or transfer them first." +still_has_org = "Your account is a member of one or more organizations. Leave them first." +still_own_packages = "Your account owns one or more packages. Delete them first." +org_still_own_repo = "This organization still owns one or more repositories. Delete or transfer them first." +org_still_own_packages = "This organization still owns one or more packages. Delete them first." target_branch_not_exist = Target branch does not exist. target_ref_not_exist = Target ref does not exist %s @@ -685,7 +685,7 @@ settings = User Settings form.name_reserved = The username "%s" is reserved. form.name_pattern_not_allowed = The pattern "%s" is not allowed in a username. -form.name_chars_not_allowed = User name "%s" contains invalid characters. +form.name_chars_not_allowed = Username "%s" contains invalid characters. block.block = Block block.block.user = Block user @@ -702,7 +702,7 @@ block.info_3 = send you notifications by @mentioning your username block.info_4 = inviting you as a collaborator to their repositories block.info_5 = starring, forking or watching on repositories block.info_6 = opening and commenting on issues or pull requests -block.info_7 = reacting on your comments in issues or pull requests +block.info_7 = reacting to your comments in issues or pull requests block.user_to_block = User to block block.note = Note block.note.title = Optional note: @@ -753,7 +753,7 @@ cancel = Cancel language = Language ui = Theme hidden_comment_types = Hidden comment types -hidden_comment_types_description = Comment types checked here will not be shown inside issue pages. Checking "Label" for example removes all "{user} added/removed {label}" comments. +hidden_comment_types_description = Comment types checked here will not be shown on issue pages. Checking "Label", for example, removes all "{user} added/removed {label}" comments. hidden_comment_types.ref_tooltip = Comments where this issue was referenced from another issue/commit/… hidden_comment_types.issue_ref_tooltip = Comments where the user changes the branch/tag associated with the issue comment_type_group_reference = Reference @@ -801,15 +801,15 @@ manage_themes = Select default theme manage_openid = Manage OpenID Addresses email_desc = Your primary email address will be used for notifications, password recovery and, provided that it is not hidden, web-based Git operations. theme_desc = This will be your default theme across the site. -theme_colorblindness_help = Colorblindness Theme Support -theme_colorblindness_prompt = Gitea just gets some themes with basic colorblindness support, which only have a few colors defined. The work is still in progress. More improvements could be done by defining more colors in the theme CSS files. +theme_colorblindness_help = Color blindness Theme Support +theme_colorblindness_prompt = Gitea only has a few themes with basic color blindness support, which only have a few colors defined. The work is still in progress. More improvements could be made by defining more colors in the theme CSS files. primary = Primary activated = Activated requires_activation = Requires activation primary_email = Make Primary activate_email = Send Activation activations_pending = Activations Pending -can_not_add_email_activations_pending = There is a pending activation, try again in a few minutes if you want to add a new email. +can_not_add_email_activations_pending = There is a pending activation. Try again in a few minutes if you want to add a new email address. delete_email = Remove email_deletion = Remove Email Address email_deletion_desc = This email address and related information will be removed from your account. Git commits by this email address will remain unchanged. Continue? @@ -855,7 +855,7 @@ gpg_key_matched_identities_long=The embedded identities in this key match the fo gpg_key_verified=Verified Key gpg_key_verified_long=Key has been verified with a token and can be used to verify commits matching any activated email addresses for this user in addition to any matched identities for this key. gpg_key_verify=Verify -gpg_invalid_token_signature = The provided GPG key, signature and token do not match or token is out-of-date. +gpg_invalid_token_signature = The provided GPG key, signature and token do not match, or the token is out-of-date. gpg_token_required = You must provide a signature for the below token gpg_token = Token gpg_token_help = You can generate a signature using: @@ -865,7 +865,7 @@ verify_gpg_key_success = GPG key "%s" has been verified. ssh_key_verified=Verified Key ssh_key_verified_long=Key has been verified with a token and can be used to verify commits matching any activated email addresses for this user. ssh_key_verify=Verify -ssh_invalid_token_signature = The provided SSH key, signature or token do not match or token is out-of-date. +ssh_invalid_token_signature = The provided SSH key, signature or token do not match, or the token is out-of-date. ssh_token_required = You must provide a signature for the below token ssh_token = Token ssh_token_help = You can generate a signature using: @@ -886,7 +886,7 @@ gpg_key_deletion = Remove GPG Key ssh_principal_deletion = Remove SSH Certificate Principal ssh_key_deletion_desc = Removing an SSH key revokes its access to your account. Continue? gpg_key_deletion_desc = Removing a GPG key un-verifies commits signed by it. Continue? -ssh_principal_deletion_desc = Removing a SSH Certificate Principal revokes its access to your account. Continue? +ssh_principal_deletion_desc = Removing an SSH Certificate Principal revokes its access to your account. Continue? ssh_key_deletion_success = The SSH key has been removed. gpg_key_deletion_success = The GPG key has been removed. ssh_principal_deletion_success = The principal has been removed. @@ -948,7 +948,7 @@ create_oauth2_application_button = Create Application create_oauth2_application_success = You have successfully created a new OAuth2 application. update_oauth2_application_success = You have successfully updated the OAuth2 application. oauth2_application_name = Application Name -oauth2_confidential_client = Confidential Client. Select for apps that keep the secret confidential, such as web apps. Do not select for native apps including desktop and mobile apps. +oauth2_confidential_client = Confidential Client. Select for apps that keep the secret confidential, such as web apps. Do not select for native apps, including desktop and mobile apps. oauth2_skip_secondary_authorization = Skip authorization for public clients after granting access once. <strong>May pose a security risk.</strong> oauth2_redirect_uris = Redirect URIs. Please use a new line for every URI. save_application = Save @@ -963,10 +963,10 @@ oauth2_application_remove_description = Removing an OAuth2 application will prev oauth2_application_locked = Gitea pre-registers some OAuth2 applications on startup if enabled in config. To prevent unexpected behavior, these can neither be edited nor removed. Please refer to the OAuth2 documentation for more information. authorized_oauth2_applications = Authorized OAuth2 Applications -authorized_oauth2_applications_description = You have granted access to your personal Gitea account to these third party applications. Please revoke access for applications you no longer need. +authorized_oauth2_applications_description = You have granted access to your personal Gitea account to these third-party applications. Please revoke access for applications you no longer need. revoke_key = Revoke revoke_oauth2_grant = Revoke Access -revoke_oauth2_grant_description = Revoking access for this third party application will prevent this application from accessing your data. Are you sure? +revoke_oauth2_grant_description = Revoking access for this third-party application will prevent this application from accessing your data. Are you sure? revoke_oauth2_grant_success = Access revoked successfully. twofa_desc = To protect your account against password theft, you can use a smartphone or another device for receiving time-based one-time passwords ("TOTP"). @@ -976,7 +976,7 @@ twofa_not_enrolled = Your account is not currently enrolled in two-factor authen twofa_disable = Disable Two-Factor Authentication twofa_scratch_token_regenerate = Regenerate Single-Use Recovery Key twofa_scratch_token_regenerated = Your single-use recovery key is now %s. Store it in a safe place, as it will not be shown again. -twofa_enroll = Enroll into Two-Factor Authentication +twofa_enroll = Enroll in Two-Factor Authentication twofa_disable_note = You can disable two-factor authentication if needed. twofa_disable_desc = Disabling two-factor authentication will make your account less secure. Continue? regenerate_scratch_token_desc = If you misplaced your recovery key or have already used it to sign in, you can reset it here. @@ -992,7 +992,7 @@ webauthn_desc = Security keys are hardware devices containing cryptographic keys webauthn_register_key = Add Security Key webauthn_nickname = Nickname webauthn_delete_key = Remove Security Key -webauthn_delete_key_desc = If you remove a security key you can no longer sign in with it. Continue? +webauthn_delete_key_desc = If you remove a security key, you can no longer sign in with it. Continue? webauthn_key_loss_warning = If you lose your security keys, you will lose access to your account. webauthn_alternative_tip = You may want to configure an additional authentication method. @@ -1109,7 +1109,7 @@ mirror_address_protocol_invalid = The provided URL is invalid. Only http(s):// o mirror_lfs = Large File Storage (LFS) mirror_lfs_desc = Activate mirroring of LFS data. mirror_lfs_endpoint = LFS Endpoint -mirror_lfs_endpoint_desc = Sync will attempt to use the clone url to <a target="_blank" rel="noopener noreferrer" href="%s">determine the LFS server</a>. You can also specify a custom endpoint if the repository LFS data is stored somewhere else. +mirror_lfs_endpoint_desc = Sync will attempt to use the clone URL to <a target="_blank" rel="noopener noreferrer" href="%s">determine the LFS server</a>. You can also specify a custom endpoint if the repository LFS data is stored somewhere else. mirror_last_synced = Last Synchronized mirror_password_placeholder = (Unchanged) mirror_password_blank_placeholder = (Unset) @@ -1122,7 +1122,7 @@ stars = Stars reactions_more = and %d more unit_disabled = The site administrator has disabled this repository section. language_other = Other -adopt_search = Enter username to search for unadopted repositories... (leave blank to find all) +adopt_search = Enter username to search for unadopted repositories… (leave blank to find all) adopt_preexisting_label = Adopt Files adopt_preexisting = Adopt pre-existing files adopt_preexisting_content = Create repository from %s @@ -1164,8 +1164,8 @@ template.issue_labels = Issue Labels template.one_item = Must select at least one template item template.invalid = Must select a template repository -archive.title = This repo is archived. You can view files and clone it. You cannot open issues, pull requests or push a commit. -archive.title_date = This repository has been archived on %s. You can view files and clone it. You cannot open issues, pull requests or push a commit. +archive.title = This repo is archived. You can view files and clone it. You cannot open issues or pull requests or push a commit. +archive.title_date = This repository has been archived on %s. You can view files and clone it. You cannot open issues or pull requests or push a commit. archive.issue.nocomment = This repo is archived. You cannot comment on issues. archive.pull.nocomment = This repo is archived. You cannot comment on pull requests. @@ -1182,7 +1182,7 @@ migrate_options_lfs = Migrate LFS files migrate_options_lfs_endpoint.label = LFS Endpoint migrate_options_lfs_endpoint.description = Migration will attempt to use your Git remote to <a target="_blank" rel="noopener noreferrer" href="%s">determine the LFS server</a>. You can also specify a custom endpoint if the repository LFS data is stored somewhere else. migrate_options_lfs_endpoint.description.local = A local server path is supported too. -migrate_options_lfs_endpoint.placeholder = If left blank, the endpoint will be derived from the clone URL +migrate_options_lfs_endpoint.placeholder = If left blank, the endpoint will be derived from the clone URL. migrate_items = Migration Items migrate_items_wiki = Wiki migrate_items_milestones = Milestones @@ -1194,10 +1194,10 @@ migrate_items_releases = Releases migrate_repo = Migrate Repository migrate.clone_address = Migrate / Clone From URL migrate.clone_address_desc = The HTTP(S) or Git 'clone' URL of an existing repository -migrate.github_token_desc = You can put one or more tokens here separated by commas to make migrating faster by circumventing the GitHub API rate limit. WARNING: Abusing this feature may violate the service provider's policy and may lead to getting your account(s) blocked. +migrate.github_token_desc = You can put one or more tokens here, separated by commas, to make migrating faster by circumventing the GitHub API rate limit. WARNING: Abusing this feature may violate the service provider's policy and may lead to getting your account(s) blocked. migrate.clone_local_path = or a local server path migrate.permission_denied = You are not allowed to import local repositories. -migrate.permission_denied_blocked = You cannot import from disallowed hosts, please ask the admin to check ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS settings. +migrate.permission_denied_blocked = You cannot import from disallowed hosts. Please ask the admin to check ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS settings. migrate.invalid_local_path = "The local path is invalid. It doesn't exist or is not a directory." migrate.invalid_lfs_endpoint = The LFS endpoint is not valid. migrate.failed = Migration failed: %v @@ -1205,7 +1205,7 @@ migrate.migrate_items_options = Access Token is required to migrate additional i migrated_from = Migrated from <a href="%[1]s">%[2]s</a> migrated_from_fake = Migrated From %[1]s migrate.migrate = Migrate From %s -migrate.migrating = Migrating from <b>%s</b> ... +migrate.migrating = Migrating from <b>%s</b>… migrate.migrating_failed = Migrating from <b>%s</b> failed. migrate.migrating_failed.error = Failed to migrate: %s migrate.migrating_failed_no_addr = Migration failed. @@ -1254,7 +1254,7 @@ clone_this_repo = Clone this repository cite_this_repo = Cite this repository create_new_repo_command = Creating a new repository on the command line push_exist_repo = Pushing an existing repository from the command line -empty_message = This repository does not contain any content. +empty_message = This repository does not have any content. broken_message = The Git data underlying this repository cannot be read. Contact the administrator of this instance or delete this repository. no_branch = This repository doesn't have any branches. @@ -1273,7 +1273,7 @@ projects = Projects packages = Packages actions = Actions labels = Labels -org_labels_desc = Organization level labels that can be used with <strong>all repositories</strong> under this organization +org_labels_desc = Organization-level labels that can be used with <strong>all repositories</strong> under this organization org_labels_desc_manage = manage milestone = Milestone @@ -1370,17 +1370,17 @@ editor.new_branch_name_desc = New branch name… editor.cancel = Cancel editor.filename_cannot_be_empty = The filename cannot be empty. editor.filename_is_invalid = The filename is invalid: "%s". -editor.commit_email = Commit email -editor.invalid_commit_email = The email for the commit is invalid. +editor.commit_email = Commit email address +editor.invalid_commit_email = The email address for the commit is invalid. editor.branch_does_not_exist = Branch "%s" does not exist in this repository. editor.branch_already_exists = Branch "%s" already exists in this repository. editor.directory_is_a_file = Directory name "%s" is already used as a filename in this repository. -editor.file_is_a_symlink = `"%s" is a symbolic link. Symbolic links cannot be edited in the web editor` +editor.file_is_a_symlink = `"%s" is a symbolic link. Symbolic links cannot be edited in the web editor.` editor.filename_is_a_directory = Filename "%s" is already used as a directory name in this repository. editor.file_modifying_no_longer_exists = The file being modified, "%s", no longer exists in this repository. editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit Changes again</strong> to overwrite them. editor.file_already_exists = A file named "%s" already exists in this repository. -editor.commit_id_not_matching = The Commit ID does not match the ID when you began editing. Commit into a patch branch and then merge. +editor.commit_id_not_matching = The Commit ID does not match the ID when you began editing. Commit into a patch branch and then merge. editor.push_out_of_date = The push appears to be out of date. editor.commit_empty_file_header = Commit an empty file editor.commit_empty_file_text = The file you're about to commit is empty. Proceed? @@ -1406,7 +1406,7 @@ editor.fork_create_description = You cannot edit this repository directly. Inste editor.fork_edit_description = You cannot edit this repository directly. The changes will be written to your fork <b>%s</b>, so you can create a pull request. editor.fork_not_editable = You have forked this repository but your fork is not editable. editor.fork_failed_to_push_branch = Failed to push branch %s to your repository. -editor.fork_branch_exists = Branch "%s" already exists in your fork, please choose a new branch name. +editor.fork_branch_exists = Branch "%s" already exists in your fork. Please choose a new branch name. commits.desc = Browse source code change history. commits.commits = Commits @@ -1508,7 +1508,7 @@ issues.new.clear_assignees = Clear assignees issues.new.no_assignees = No Assignees issues.new.no_reviewers = No Reviewers issues.new.blocked_user = Cannot create issue because you are blocked by the repository owner. -issues.edit.already_changed = Unable to save changes to the issue. It appears the content has already been changed by another user. Please refresh the page and try editing again to avoid overwriting their changes +issues.edit.already_changed = Unable to save changes to the issue. It appears the content has already been changed by another user. Please refresh the page and try editing again to avoid overwriting their changes. issues.edit.blocked_user = Cannot edit content because you are blocked by the poster or repository owner. issues.choose.get_started = Get Started issues.choose.open_external_link = Open @@ -1580,7 +1580,7 @@ issues.filter_type.reviewed_by_you = Reviewed by you issues.filter_sort = Sort issues.filter_sort.latest = Newest issues.filter_sort.oldest = Oldest -issues.filter_sort.recentupdate = Recently updated +issues.filter_sort.recentupdate = Most recently updated issues.filter_sort.leastupdate = Least recently updated issues.filter_sort.mostcomment = Most commented issues.filter_sort.leastcomment = Least commented @@ -1780,9 +1780,9 @@ issues.dependency.pr_closing_blockedby = Closing this pull request is blocked by issues.dependency.issue_closing_blockedby = Closing this issue is blocked by the following issues issues.dependency.issue_close_blocks = This issue blocks closing of the following issues issues.dependency.pr_close_blocks = This pull request blocks closing of the following issues -issues.dependency.issue_close_blocked = You need to close all issues blocking this issue before you can close it. +issues.dependency.issue_close_blocked = You need to close all issues that are blocking this issue before you can close it. issues.dependency.issue_batch_close_blocked = "Cannot batch close issues that you choose, because issue #%d still has open dependencies" -issues.dependency.pr_close_blocked = You need to close all issues blocking this pull request before you can merge it. +issues.dependency.pr_close_blocked = You need to close all issues that are blocking this pull request before you can merge it. issues.dependency.blocks_short = Blocks issues.dependency.blocked_by_short = Depends on issues.dependency.remove_header = Remove Dependency @@ -1793,13 +1793,13 @@ issues.dependency.add_error_same_issue = You cannot make an issue depend on itse issues.dependency.add_error_dep_issue_not_exist = Dependent issue does not exist. issues.dependency.add_error_dep_not_exist = Dependency does not exist. issues.dependency.add_error_dep_exists = Dependency already exists. -issues.dependency.add_error_cannot_create_circular = You cannot create a dependency with two issues blocking each other. +issues.dependency.add_error_cannot_create_circular = You cannot create a dependency with two issues that block each other. issues.dependency.add_error_dep_not_same_repo = Both issues must be in the same repository. issues.review.self.approval = You cannot approve your own pull request. issues.review.self.rejection = You cannot request changes on your own pull request. issues.review.approve = "approved these changes %s" issues.review.comment = "reviewed %s" -issues.review.dismissed = "dismissed %s’s review %s" +issues.review.dismissed = "dismissed %s's review %s" issues.review.dismissed_label = Dismissed issues.review.left_comment = left a comment issues.review.content.empty = You need to leave a comment indicating the requested change(s). @@ -1807,7 +1807,7 @@ issues.review.reject = "requested changes %s" issues.review.wait = "was requested for review %s" issues.review.add_review_request = "requested review from %s %s" issues.review.remove_review_request = "removed review request for %s %s" -issues.review.remove_review_request_self = "refused to review %s" +issues.review.remove_review_request_self = "declined to review %s" issues.review.pending = Pending issues.review.pending.tooltip = This comment is not currently visible to other users. To submit your pending comments, select "%s" -> "%s/%s/%s" at the top of the page. issues.review.review = Review @@ -1829,7 +1829,7 @@ issues.review.requested = Review pending issues.review.rejected = Changes requested issues.review.stale = Updated since approval issues.review.unofficial = Uncounted approval -issues.assignee.error = Not all assignees was added due to an unexpected error. +issues.assignee.error = Not all assignees were added, due to an unexpected error. issues.reference_issue.body = Body issues.content_history.deleted = deleted issues.content_history.edited = edited @@ -1846,7 +1846,7 @@ pulls.desc = Enable pull requests and code reviews. pulls.new = New Pull Request pulls.new.blocked_user = Cannot create pull request because you are blocked by the repository owner. pulls.new.must_collaborator = You must be a collaborator to create pull request. -pulls.edit.already_changed = Unable to save changes to the pull request. It appears the content has already been changed by another user. Please refresh the page and try editing again to avoid overwriting their changes +pulls.edit.already_changed = Unable to save changes to the pull request. It appears the content has already been changed by another user. Please refresh the page and try editing again to avoid overwriting their changes. pulls.view = View Pull Request pulls.compare_changes = New Pull Request pulls.allow_edits_from_maintainers = Allow edits from maintainers @@ -1867,7 +1867,7 @@ pulls.show_all_commits = Show all commits pulls.show_changes_since_your_last_review = Show changes since your last review pulls.showing_only_single_commit = Showing only changes of commit %[1]s pulls.showing_specified_commit_range = Showing only changes between %[1]s..%[2]s -pulls.select_commit_hold_shift_for_range = Select commit. Hold shift + click to select a range +pulls.select_commit_hold_shift_for_range = Select commit. Hold Shift and click to select a range. pulls.review_only_possible_for_full_diff = Review is only possible when viewing the full diff pulls.filter_changes_by_commit = Filter by commit pulls.nothing_to_compare = These branches are equal. There is no need to create a pull request. @@ -1896,7 +1896,7 @@ pulls.add_prefix = Add <strong>%s</strong> prefix pulls.remove_prefix = Remove <strong>%s</strong> prefix pulls.data_broken = This pull request is broken due to missing fork information. pulls.files_conflicted = This pull request has changes conflicting with the target branch. -pulls.is_checking = Checking for merge conflicts ... +pulls.is_checking = Checking for merge conflicts… pulls.is_ancestor = "This branch is already included in the target branch. There is nothing to merge." pulls.is_empty = "The changes on this branch are already on the target branch. This will be an empty commit." pulls.required_status_check_failed = Some required checks were not successful. @@ -1920,16 +1920,16 @@ pulls.reject_count_1 = "%d change request" pulls.reject_count_n = "%d change requests" pulls.waiting_count_1 = "%d waiting review" pulls.waiting_count_n = "%d waiting reviews" -pulls.wrong_commit_id = "commit id must be a commit id on the target branch" +pulls.wrong_commit_id = "commit ID must be a commit ID on the target branch" pulls.no_merge_desc = This pull request cannot be merged because all repository merge options are disabled. pulls.no_merge_helper = Enable merge options in the repository settings or merge the pull request manually. pulls.no_merge_wip = This pull request cannot be merged because it is marked as being a work in progress. -pulls.no_merge_not_ready = This pull request is not ready to be merged, check review status and status checks. +pulls.no_merge_not_ready = This pull request is not ready to be merged. Check review status and status checks. pulls.no_merge_access = You are not authorized to merge this pull request. pulls.merge_pull_request = Create merge commit -pulls.rebase_merge_pull_request = Rebase then fast-forward -pulls.rebase_merge_commit_pull_request = Rebase then create merge commit +pulls.rebase_merge_pull_request = Rebase, then fast-forward +pulls.rebase_merge_commit_pull_request = Rebase, then create merge commit pulls.squash_merge_pull_request = Create squash commit pulls.fast_forward_only_merge_pull_request = Fast-forward only pulls.merge_manually = Manually merged @@ -1937,17 +1937,17 @@ pulls.merge_commit_id = The merge commit ID pulls.require_signed_wont_sign = The branch requires signed commits but this merge will not be signed pulls.invalid_merge_option = You cannot use this merge option for this pull request. -pulls.merge_conflict = Merge Failed: There was a conflict whilst merging. Hint: Try a different strategy +pulls.merge_conflict = Merge Failed: There was a conflict while merging. Hint: Try a different strategy. pulls.merge_conflict_summary = Error Message -pulls.rebase_conflict = Merge Failed: There was a conflict whilst rebasing commit: %[1]s. Hint: Try a different strategy +pulls.rebase_conflict = Merge Failed: There was a conflict while rebasing commit: %[1]s. Hint: Try a different strategy. pulls.rebase_conflict_summary = Error Message -pulls.unrelated_histories = Merge Failed: The merge head and base do not share a common history. Hint: Try a different strategy -pulls.merge_out_of_date = Merge Failed: Whilst generating the merge, the base was updated. Hint: Try again. -pulls.head_out_of_date = Merge Failed: Whilst generating the merge, the head was updated. Hint: Try again. -pulls.has_merged = Failed: The pull request has been merged, you cannot merge again or change the target branch. +pulls.unrelated_histories = Merge Failed: The merge head and base do not share a common history. Hint: Try a different strategy. +pulls.merge_out_of_date = Merge Failed: While generating the merge, the base was updated. Hint: Try again. +pulls.head_out_of_date = Merge Failed: While generating the merge, the head was updated. Hint: Try again. +pulls.has_merged = Failed: The pull request has been merged. You cannot merge again or change the target branch. pulls.push_rejected = Push Failed: The push was rejected. Review the Git Hooks for this repository. pulls.push_rejected_summary = Full Rejection Message -pulls.push_rejected_no_message = Push Failed: The push was rejected but there was no remote message. Review the Git Hooks for this repository +pulls.push_rejected_no_message = Push Failed: The push was rejected but there was no remote message. Review the Git Hooks for this repository. pulls.open_unmerged_pull_exists = `You cannot perform a reopen operation because there is a pending pull request (#%d) with identical properties.` pulls.status_checking = Some checks are pending pulls.status_checks_success = All checks were successful @@ -1973,7 +1973,7 @@ pulls.cmd_instruction_merge_title = Merge pulls.cmd_instruction_merge_desc = Merge the changes and update on Gitea. pulls.cmd_instruction_merge_warning = Warning: This operation cannot merge pull request because "autodetect manual merge" is not enabled. pulls.clear_merge_message = Clear merge message -pulls.clear_merge_message_hint = Clearing the merge message will only remove the commit message content and keep generated git trailers such as "Co-Authored-By …". +pulls.clear_merge_message_hint = Clearing the merge message will only remove the commit message content and keep generated git trailers such as "Co-Authored-By…". pulls.auto_merge_button_when_succeed = (When checks succeed) pulls.auto_merge_when_succeed = Auto merge when all checks succeed @@ -2039,7 +2039,7 @@ signing.wont_sign.nokey = There is no key available to sign this commit. signing.wont_sign.never = Commits are never signed. signing.wont_sign.always = Commits are always signed. signing.wont_sign.pubkey = The commit will not be signed because you do not have a public key associated with your account. -signing.wont_sign.twofa = You must have two factor authentication enabled to have commits signed. +signing.wont_sign.twofa = You must have two-factor authentication enabled to have commits signed. signing.wont_sign.parentsigned = The commit will not be signed as the parent commit is not signed. signing.wont_sign.basesigned = The merge will not be signed as the base commit is not signed. signing.wont_sign.headsigned = The merge will not be signed as the head commit is not signed. @@ -2153,13 +2153,13 @@ contributors.contribution_type.additions = Additions contributors.contribution_type.deletions = Deletions settings = Settings -settings.desc = Settings is where you can manage the settings for the repository +settings.desc = Settings is where you can manage the settings for the repository. settings.options = Repository settings.public_access = Public Access settings.public_access_desc = Configure public visitor's access permissions to override the defaults of this repository. settings.public_access.docs.not_set = Not Set: no extra public access permission. The visitor's permission follows the repository's visibility and member permissions. settings.public_access.docs.anonymous_read = Anonymous Read: users who are not logged in can access the unit with read permission. -settings.public_access.docs.everyone_read = Everyone Read: all logged-in users can access the unit with read permission. Read permission of issues/pull-requests units also means users can create new issues/pull-requests. +settings.public_access.docs.everyone_read = Everyone Read: all logged-in users can access the unit with read permission. Read permission of issue/pull-request units also means users can create new issues/pull requests. settings.public_access.docs.everyone_write = Everyone Write: all logged-in users have write permission to the unit. Only Wiki unit supports this permission. settings.collaboration = Collaborators settings.collaboration.admin = Administrator @@ -2177,7 +2177,7 @@ settings.mirror_settings.docs.disabled_pull_mirror.instructions = Set up your pr settings.mirror_settings.docs.disabled_push_mirror.instructions = Set up your project to automatically pull commits, tags and branches from another repository. settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning = Right now, this can only be done in the "New Migration" menu. For more information, please consult: settings.mirror_settings.docs.disabled_push_mirror.info = Push mirrors have been disabled by your site administrator. -settings.mirror_settings.docs.no_new_mirrors = Your repository is mirroring changes to or from another repository. Please keep in mind that you can't create any new mirrors at this time. +settings.mirror_settings.docs.no_new_mirrors = Your repository is mirroring changes to or from another repository. Please keep in mind that you currently can't create any new mirrors. settings.mirror_settings.docs.can_still_use = Although you can't modify existing mirrors or create new ones, you may still use your existing mirror. settings.mirror_settings.docs.pull_mirror_instructions = To set up a pull mirror, please consult: settings.mirror_settings.docs.more_information_if_disabled = You can find out more about push and pull mirrors here: @@ -2252,7 +2252,7 @@ settings.admin_indexer_commit_sha = Last Indexed SHA settings.admin_indexer_unindexed = Unindexed settings.reindex_button = Add to Reindex Queue settings.reindex_requested=Reindex Requested -settings.admin_enable_close_issues_via_commit_in_any_branch = Close an issue via a commit made in a non default branch +settings.admin_enable_close_issues_via_commit_in_any_branch = Close an issue via a commit made in a non-default branch settings.danger_zone = Danger Zone settings.new_owner_has_same_repo = The new owner already has a repository with same name. Please choose another name. settings.convert = Convert to Regular Repository @@ -2274,7 +2274,7 @@ settings.transfer_abort_invalid = You cannot cancel a non existent repository tr settings.transfer_abort_success = The repository transfer to %s was successfully canceled. settings.transfer_desc = Transfer this repository to a user or to an organization for which you have administrator rights. settings.transfer_form_title = Enter the repository name as confirmation: -settings.transfer_in_progress = There is currently an ongoing transfer. Please cancel it if you will like to transfer this repository to another user. +settings.transfer_in_progress = There is currently an ongoing transfer. Please cancel it if you would like to transfer this repository to another user. settings.transfer_notices_1 = - You will lose access to the repository if you transfer it to an individual user. settings.transfer_notices_2 = - You will keep access to the repository if you transfer it to an organization that you (co-)own. settings.transfer_notices_3 = - If the repository is private and is transferred to an individual user, this action makes sure that the user does have at least read permission (and changes permissions if necessary). @@ -2289,13 +2289,13 @@ settings.trust_model.default = Default Trust Model settings.trust_model.default.desc= Use the default repository trust model for this installation. settings.trust_model.collaborator = Collaborator settings.trust_model.collaborator.long = Collaborator: Trust signatures by collaborators -settings.trust_model.collaborator.desc = Valid signatures by collaborators of this repository will be marked "trusted" - (whether they match the committer or not). Otherwise, valid signatures will be marked "untrusted" if the signature matches the committer and "unmatched" if not. +settings.trust_model.collaborator.desc = Valid signatures by collaborators of this repository will be marked "trusted", whether they match the committer or not. Otherwise, valid signatures will be marked "untrusted" if the signature matches the committer and "unmatched" if not. settings.trust_model.committer = Committer -settings.trust_model.committer.long = Committer: Trust signatures that match committers (This matches GitHub and will force Gitea signed commits to have Gitea as the committer) -settings.trust_model.committer.desc = Valid signatures will only be marked "trusted" if they match the committer, otherwise they will be marked "unmatched". This forces Gitea to be the committer on signed commits with the actual committer marked as Co-authored-by: and Co-committed-by: trailer in the commit. The default Gitea key must match a User in the database. +settings.trust_model.committer.long = Committer: Trust signatures that match committers. This matches GitHub's behavior and will force commits signed by Gitea to have Gitea as the committer. +settings.trust_model.committer.desc = Valid signatures will only be marked "trusted" if they match the committer, otherwise they will be marked "unmatched". This forces Gitea to be the committer on signed commits, with the actual committer marked as Co-authored-by: and Co-committed-by: trailer in the commit. The default Gitea key must match a user in the database. settings.trust_model.collaboratorcommitter = Collaborator+Committer settings.trust_model.collaboratorcommitter.long = Collaborator+Committer: Trust signatures by collaborators which match the committer -settings.trust_model.collaboratorcommitter.desc = Valid signatures by collaborators of this repository will be marked "trusted" if they match the committer. Otherwise, valid signatures will be marked "untrusted" if the signature matches the committer and "unmatched" otherwise. This will force Gitea to be marked as the committer on signed commits with the actual committer marked as Co-Authored-By: and Co-Committed-By: trailer in the commit. The default Gitea key must match a User in the database. +settings.trust_model.collaboratorcommitter.desc = Valid signatures by collaborators of this repository will be marked "trusted" if they match the committer. Otherwise, valid signatures will be marked "untrusted" if the signature matches the committer and "unmatched" otherwise. This will force Gitea to be marked as the committer on signed commits, with the actual committer marked as Co-Authored-By: and Co-Committed-By: trailer in the commit. The default Gitea key must match a user in the database. settings.wiki_delete = Delete Wiki Data settings.wiki_delete_desc = Deleting repository wiki data is permanent and cannot be undone. settings.wiki_delete_notices_1 = - This will permanently delete and disable the repository wiki for %s. @@ -2304,7 +2304,7 @@ settings.wiki_deletion_success = The repository wiki data has been deleted. settings.delete = Delete This Repository settings.delete_desc = Deleting a repository is permanent and cannot be undone. settings.delete_notices_1 = - This operation <strong>CANNOT</strong> be undone. -settings.delete_notices_2 = - This operation will permanently delete the <strong>%s</strong> repository including code, issues, comments, wiki data and collaborator settings. +settings.delete_notices_2 = - This operation will permanently delete the <strong>%s</strong> repository, including code, issues, comments, wiki data and collaborator settings. settings.delete_notices_fork_1 = - Forks of this repository will become independent after deletion. settings.deletion_success = The repository has been deleted. settings.update_settings_success = The repository settings have been updated. @@ -2326,8 +2326,8 @@ settings.team_not_in_organization = The team is not in the same organization as settings.teams = Teams settings.add_team = Add Team settings.add_team_duplicate = Team already has the repository -settings.add_team_success = The team now have access to the repository. -settings.change_team_permission_tip = Team's permission is set on the team setting page and can't be changed per repository +settings.add_team_success = The team now has access to the repository. +settings.change_team_permission_tip = Team's permission is set on the team settings page and can't be changed per repository settings.delete_team_tip = This team has access to all repositories and can't be removed settings.remove_team_success = The team's access to the repository has been removed. settings.add_webhook = Add Webhook @@ -2592,7 +2592,7 @@ settings.archive.tagsettings_unavailable = Tag settings are not available if the settings.archive.mirrors_unavailable = Mirrors are not available if the repo is archived. settings.unarchive.button = Unarchive repo settings.unarchive.header = Unarchive this repo -settings.unarchive.text = Unarchiving the repo will restore its ability to receive commits and pushes, as well as new issues and pull-requests. +settings.unarchive.text = Unarchiving the repo will restore its ability to receive commits and pushes, as well as new issues and pull requests. settings.unarchive.success = The repo was successfully unarchived. settings.unarchive.error = An error occurred while trying to unarchive the repo. See the log for more details. settings.update_avatar_success = The repository avatar has been updated. @@ -2610,11 +2610,11 @@ settings.lfs_invalid_locking_path=Invalid path: %s settings.lfs_invalid_lock_directory=Cannot lock directory: %s settings.lfs_lock_already_exists=Lock already exists: %s settings.lfs_lock=Lock -settings.lfs_lock_path=Filepath to lock... +settings.lfs_lock_path=Filepath to lock… settings.lfs_locks_no_locks=No Locks settings.lfs_lock_file_no_exist=Locked file does not exist in default branch settings.lfs_force_unlock=Force Unlock -settings.lfs_pointers.found=Found %d blob pointer(s) - %d associated, %d unassociated (%d missing from store) +settings.lfs_pointers.found=Found %d blob pointer(s) — %d associated, %d unassociated (%d missing from store) settings.lfs_pointers.sha=Blob SHA settings.lfs_pointers.oid=OID settings.lfs_pointers.inRepo=In Repo @@ -2772,7 +2772,7 @@ branch.new_branch_from = Create new branch from "%s" branch.renamed = Branch %s was renamed to %s. branch.rename_default_or_protected_branch_error = Only admins can rename default or protected branches. branch.rename_protected_branch_failed = This branch is protected by glob-based protection rules. -branch.commits_divergence_from = Commits divergence: %[1]d behind and %[2]d ahead of %[3]s +branch.commits_divergence_from = Commit divergence: %[1]d behind and %[2]d ahead of %[3]s branch.commits_no_divergence = The same as branch %[1]s tag.create_tag = Create tag %s @@ -2797,7 +2797,7 @@ error.csv.invalid_field_count = Can't render this file because it has a wrong nu error.broken_git_hook = Git hooks of this repository seem to be broken. Please follow the <a target="_blank" rel="noreferrer" href="%s">documentation</a> to fix them, then push some commits to refresh the status. [graphs] -component_loading = Loading %s... +component_loading = Loading %s… component_loading_failed = Could not load %s component_loading_info = This might take a bit… component_failed_to_load = An unexpected error happened. @@ -2836,7 +2836,7 @@ form.create_org_not_allowed = You are not allowed to create an organization. settings = Settings settings.options = Organization settings.full_name = Full Name -settings.email = Contact Email +settings.email = Contact Email Address settings.website = Website settings.location = Location settings.permission = Permissions @@ -2853,10 +2853,10 @@ settings.update_setting_success = Organization settings have been updated. settings.rename = Rename Organization settings.rename_desc = Changing the organization name will also change your organization's URL and free the old name. -settings.rename_success = Organization %[1]s have been renamed to %[2]s successfully. -settings.rename_no_change = Organization name is no change. +settings.rename_success = Organization %[1]s has been renamed to %[2]s successfully. +settings.rename_no_change = Organization name is not changed. settings.rename_new_org_name = New Organization Name -settings.rename_failed = Rename Organization failed because of internal error +settings.rename_failed = Renaming organization failed because of an internal error settings.rename_notices_1 = This operation <strong>CANNOT</strong> be undone. settings.rename_notices_2 = The old name will redirect until it is claimed. @@ -2866,11 +2866,11 @@ settings.delete_account = Delete This Organization settings.delete_prompt = The organization will be permanently removed. This <strong>CANNOT</strong> be undone! settings.name_confirm = Enter the organization name as confirmation: settings.delete_notices_1 = This operation <strong>CANNOT</strong> be undone. -settings.delete_notices_2 = This operation will permanently delete all the <strong>repositories</strong> of <strong>%s</strong> including code, issues, comments, wiki data and collaborator settings. +settings.delete_notices_2 = This operation will permanently delete all the <strong>repositories</strong> of <strong>%s</strong>, including code, issues, comments, wiki data and collaborator settings. settings.delete_notices_3 = This operation will permanently delete all the <strong>packages</strong> of <strong>%s</strong>. settings.delete_notices_4 = This operation will permanently delete all the <strong>projects</strong> of <strong>%s</strong>. settings.confirm_delete_account = Confirm Deletion -settings.delete_failed = Delete Organization failed due to an internal error +settings.delete_failed = Deleting organization failed due to an internal error settings.delete_successful = Organization <b>%s</b> has been deleted successfully. settings.hooks_desc = Add webhooks which will be triggered for <strong>all repositories</strong> under this organization. @@ -2927,7 +2927,7 @@ teams.remove_all_repos_title = Remove all team repositories teams.remove_all_repos_desc = This will remove all repositories from the team. teams.add_all_repos_title = Add all repositories teams.add_all_repos_desc = This will add all the organization's repositories to the team. -teams.add_nonexistent_repo = "The repository you're trying to add doesn't exist, please create it first." +teams.add_nonexistent_repo = "The repository you're trying to add doesn't exist. Please create it first." teams.add_duplicate_users = User is already a team member. teams.repos.none = No repositories could be accessed by this team. teams.members.none = No members on this team. @@ -2968,7 +2968,7 @@ repositories = Repositories hooks = Webhooks integrations = Integrations authentication = Authentication Sources -emails = User Emails +emails = User Email Addresses config = Configuration config_summary = Summary config_settings = Settings @@ -3000,11 +3000,11 @@ dashboard.cron.cancelled=Cron: %[1]s canceled: %[3]s dashboard.cron.error=Error in Cron: %s: %[3]s dashboard.cron.finished=Cron: %[1]s has finished dashboard.delete_inactive_accounts = Delete all unactivated accounts -dashboard.delete_inactive_accounts.started = Delete all unactivated accounts task started. +dashboard.delete_inactive_accounts.started = Task to delete all unactivated accounts started dashboard.delete_repo_archives = "Delete all repositories' archives (ZIP, TAR.GZ, etc..)" -dashboard.delete_repo_archives.started = Delete all repository archives task started. +dashboard.delete_repo_archives.started = Task to delete all repository archives started dashboard.delete_missing_repos = Delete all repositories missing their Git files -dashboard.delete_missing_repos.started = Delete all repositories missing their Git files task started. +dashboard.delete_missing_repos.started = Task to delete all repositories missing their Git files started dashboard.delete_generated_repository_avatars = Delete generated repository avatars dashboard.sync_repo_branches = Sync missed branches from git data to databases dashboard.sync_repo_tags = Sync tags from git data to database @@ -3012,12 +3012,12 @@ dashboard.update_mirrors = Update Mirrors dashboard.repo_health_check = Health check all repositories dashboard.check_repo_stats = Check all repository statistics dashboard.archive_cleanup = Delete old repository archives -dashboard.deleted_branches_cleanup = Clean-up deleted branches +dashboard.deleted_branches_cleanup = Clean up deleted branches dashboard.update_migration_poster_id = Update migration poster IDs -dashboard.git_gc_repos = Garbage collect all repositories -dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys. -dashboard.resync_all_sshprincipals = Update the '.ssh/authorized_principals' file with Gitea SSH principals. -dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories. +dashboard.git_gc_repos = Garbage-collect all repositories +dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys +dashboard.resync_all_sshprincipals = Update the '.ssh/authorized_principals' file with Gitea SSH principals +dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for which records exist dashboard.sync_external_users = Synchronize external user data dashboard.cleanup_hook_task_table = Clean up hook_task table @@ -3053,10 +3053,10 @@ dashboard.total_gc_pause = Total GC Pause dashboard.last_gc_pause = Last GC Pause dashboard.gc_times = GC Times dashboard.delete_old_actions = Delete all old activities from database -dashboard.delete_old_actions.started = Delete all old activities from database started. +dashboard.delete_old_actions.started = Deletion of all old activities from database started dashboard.update_checker = Update checker dashboard.delete_old_system_notices = Delete all old system notices from database -dashboard.gc_lfs = Garbage collect LFS meta objects +dashboard.gc_lfs = Garbage-collect LFS meta objects dashboard.stop_zombie_tasks = Stop actions zombie tasks dashboard.stop_endless_tasks = Stop actions endless tasks dashboard.cancel_abandoned_jobs = Cancel actions abandoned jobs @@ -3080,7 +3080,7 @@ users.2fa = 2FA users.repos = Repos users.created = Created users.last_login = Last Sign-In -users.never_login = Never Signed-In +users.never_login = Never Signed In users.send_register_notify = Send User Registration Notification users.new_success = The user account "%s" has been created. users.edit = Edit @@ -3107,7 +3107,7 @@ users.still_own_repo = This user still owns one or more repositories. Delete or users.still_has_org = This user is a member of an organization. Remove the user from any organizations first. users.purge = Purge User users.purge_help = Forcibly delete user and any repositories, organizations, and packages owned by the user. All comments will be deleted too. -users.still_own_packages = This user still owns one or more packages, delete these packages first. +users.still_own_packages = This user still owns one or more packages. Delete these packages first. users.deletion_success = The user account has been deleted. users.reset_2fa = Reset 2FA users.list_status_filter.menu_text = Filter @@ -3127,11 +3127,11 @@ users.details = User Details emails.email_manage_panel = User Email Management emails.primary = Primary emails.activated = Activated -emails.filter_sort.email = Email -emails.filter_sort.email_reverse = Email (reverse) -emails.filter_sort.name = User Name -emails.filter_sort.name_reverse = User Name (reverse) -emails.updated = Email updated +emails.filter_sort.email = Email address +emails.filter_sort.email_reverse = Email address (reverse) +emails.filter_sort.name = Username +emails.filter_sort.name_reverse = Username (reverse) +emails.updated = Email address updated emails.not_updated = Failed to update the requested email address: %v emails.duplicate_active = This email address is already active for a different user. emails.change_email_header = Update Email Properties @@ -3139,7 +3139,7 @@ emails.change_email_text = Are you sure you want to update this email address? emails.delete = Delete Email emails.delete_desc = Are you sure you want to delete this email address? emails.deletion_success = The email address has been deleted. -emails.delete_primary_email_error = You can not delete the primary email. +emails.delete_primary_email_error = You cannot delete the primary email address. orgs.org_manage_panel = Organization Management orgs.name = Name @@ -3253,29 +3253,29 @@ auths.oauth2_required_claim_name_helper = Set this name to restrict login from t auths.oauth2_required_claim_value = Required Claim Value auths.oauth2_required_claim_value_helper = Set this value to restrict login from this source to users with a claim with this name and value auths.oauth2_group_claim_name = Claim name providing group names for this source. (Optional) -auths.oauth2_full_name_claim_name = Full Name Claim Name. (Optional, if set, the user's full name will always be synchronized with this claim) +auths.oauth2_full_name_claim_name = Full Name Claim Name. (Optional; if set, the user's full name will always be synchronized with this claim) auths.oauth2_ssh_public_key_claim_name = SSH Public Key Claim Name -auths.oauth2_admin_group = Group Claim value for administrator users. (Optional - requires claim name above) -auths.oauth2_restricted_group = Group Claim value for restricted users. (Optional - requires claim name above) -auths.oauth2_map_group_to_team = Map claimed groups to Organization teams. (Optional - requires claim name above) +auths.oauth2_admin_group = Group Claim value for administrator users. (Optional — requires claim name above) +auths.oauth2_restricted_group = Group Claim value for restricted users. (Optional — requires claim name above) +auths.oauth2_map_group_to_team = Map claimed groups to Organization teams. (Optional — requires claim name above) auths.oauth2_map_group_to_team_removal = Remove users from synchronized teams if user does not belong to corresponding group. auths.enable_auto_register = Enable Auto Registration auths.sspi_auto_create_users = Automatically create users -auths.sspi_auto_create_users_helper = Allow SSPI auth method to automatically create new accounts for users that login for the first time +auths.sspi_auto_create_users_helper = Allow SSPI auth method to automatically create new accounts for users that log in for the first time auths.sspi_auto_activate_users = Automatically activate users auths.sspi_auto_activate_users_helper = Allow SSPI auth method to automatically activate new users auths.sspi_strip_domain_names = Remove domain names from usernames -auths.sspi_strip_domain_names_helper = If checked, domain names will be removed from logon names (eg. "DOMAIN\user" and "user@example.org" both will become just "user"). +auths.sspi_strip_domain_names_helper = If checked, domain names will be removed from logon names (e.g. "DOMAIN\user" and "user@example.org" both will become just "user"). auths.sspi_separator_replacement = Separator to use instead of \, / and @ -auths.sspi_separator_replacement_helper = The character to use to replace the separators of down-level logon names (eg. the \ in "DOMAIN\user") and user principal names (eg. the @ in "user@example.org"). +auths.sspi_separator_replacement_helper = The character to use to replace the separators of down-level logon names (e.g. the \ in "DOMAIN\user") and user principal names (e.g. the @ in "user@example.org"). auths.sspi_default_language = Default user language -auths.sspi_default_language_helper = Default language for users automatically created by SSPI auth method. Leave empty if you prefer language to be automatically detected. +auths.sspi_default_language_helper = Default language for users automatically created by SSPI auth method. Leave empty if you prefer the language to be automatically detected. auths.tips = Tips auths.tips.oauth2.general = OAuth2 Authentication auths.tips.oauth2.general.tip = When registering a new OAuth2 authentication, the callback/redirect URL should be: auths.tip.oauth2_provider = OAuth2 Provider auths.tip.bitbucket = Register a new OAuth consumer on %s and add the permission 'Account' - 'Read' -auths.tip.nextcloud = Register a new OAuth consumer on your instance using the following menu "Settings -> Security -> OAuth 2.0 client" +auths.tip.nextcloud = Register a new OAuth consumer on your instance by selecting "Settings -> Security -> OAuth 2.0 client" in the menu auths.tip.dropbox = Create a new application at %s auths.tip.facebook = Register a new application at %s and add the product "Facebook Login" auths.tip.github = Register a new OAuth application on %s @@ -3385,7 +3385,7 @@ config.mailer_sendmail_path = Sendmail Path config.mailer_sendmail_args = Extra Arguments to Sendmail config.mailer_sendmail_timeout = Sendmail Timeout config.mailer_use_dummy = Dummy -config.test_email_placeholder = Email (e.g. test@example.com) +config.test_email_placeholder = Email Address (e.g. test@example.com) config.send_test_mail = Send Testing Email config.send_test_mail_submit = Send config.test_mail_failed = Failed to send a testing email to "%s": %v @@ -3474,7 +3474,7 @@ monitor.queue.numberinqueue = Number in Queue monitor.queue.review_add = Review / Add Workers monitor.queue.settings.title = Pool Settings monitor.queue.settings.desc = Pools dynamically grow in response to their worker queue blocking. -monitor.queue.settings.maxnumberworkers = Max Number of workers +monitor.queue.settings.maxnumberworkers = Max number of workers monitor.queue.settings.maxnumberworkers.placeholder = Currently %[1]d monitor.queue.settings.maxnumberworkers.error = Max number of workers must be a number monitor.queue.settings.submit = Update Settings @@ -3500,10 +3500,10 @@ notices.delete_success = The system notices have been deleted. self_check.no_problem_found = No problem found yet. self_check.startup_warnings = Startup warnings: self_check.database_collation_mismatch = Expect database to use collation: %s -self_check.database_collation_case_insensitive = Database is using a collation %s, which is an insensitive collation. Although Gitea could work with it, there might be some rare cases which don't work as expected. -self_check.database_inconsistent_collation_columns = Database is using collation %s, but these columns are using mismatched collations. It might cause some unexpected problems. -self_check.database_fix_mysql = For MySQL/MariaDB users, you could use the "gitea doctor convert" command to fix the collation problems, or you could also fix the problem by "ALTER ... COLLATE ..." SQLs manually. -self_check.database_fix_mssql = For MSSQL users, you could only fix the problem by "ALTER ... COLLATE ..." SQLs manually at the moment. +self_check.database_collation_case_insensitive = Database is using collation %s, which is a case-insensitive collation. Although Gitea could work with it, there might be some rare cases which don't work as expected. +self_check.database_inconsistent_collation_columns = Database is using collation %s, but these columns are using mismatched collations. This might cause some unexpected problems. +self_check.database_fix_mysql = For MySQL/MariaDB users, you could use the "gitea doctor convert" command to fix the collation problems, or you could also fix the problem manually with "ALTER ... COLLATE ..." SQL queries. +self_check.database_fix_mssql = For MSSQL users, you could only fix the problem manually with "ALTER ... COLLATE ..." SQL queries at the moment. self_check.location_origin_mismatch = Current URL (%[1]s) doesn't match the URL seen by Gitea (%[2]s). If you are using a reverse proxy, please make sure the "Host" and "X-Forwarded-Proto" headers are set correctly. [action] @@ -3587,8 +3587,8 @@ error.no_committer_account = No account linked to committer's email address error.no_gpg_keys_found = "No known key found for this signature in database" error.not_signed_commit = "Not a signed commit" error.failed_retrieval_gpg_keys = "Failed to retrieve any key attached to the committer's account" -error.probable_bad_signature = "WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS." -error.probable_bad_default_signature = "WARNING! Although the default key has this ID it does not verify this commit! This commit is SUSPICIOUS." +error.probable_bad_signature = "WARNING! Although there is a key with this ID in the database, it does not verify this commit! This commit is SUSPICIOUS." +error.probable_bad_default_signature = "WARNING! Although the default key has this ID, it does not verify this commit! This commit is SUSPICIOUS." [units] unit = Unit @@ -3627,7 +3627,7 @@ versions.view_all = View all dependency.id = ID dependency.version = Version search_in_external_registry = Search in %s -alpine.registry = Setup this registry by adding the url in your <code>/etc/apk/repositories</code> file: +alpine.registry = Set up this registry by adding the URL in your <code>/etc/apk/repositories</code> file: alpine.registry.key = Download the registry public RSA key into the <code>/etc/apk/keys/</code> folder to verify the index signature: alpine.registry.info = Choose $branch and $repository from the list below. alpine.install = To install the package, run the following command: @@ -3640,18 +3640,18 @@ arch.install = Sync package with pacman: arch.repository = Repository Info arch.repository.repositories = Repositories arch.repository.architectures = Architectures -cargo.registry = Setup this registry in the Cargo configuration file (for example <code>~/.cargo/config.toml</code>): +cargo.registry = Set up this registry in the Cargo configuration file (for example <code>~/.cargo/config.toml</code>): cargo.install = To install the package using Cargo, run the following command: -chef.registry = Setup this registry in your <code>~/.chef/config.rb</code> file: +chef.registry = Set up this registry in your <code>~/.chef/config.rb</code> file: chef.install = To install the package, run the following command: -composer.registry = Setup this registry in your <code>~/.composer/config.json</code> file: +composer.registry = Set up this registry in your <code>~/.composer/config.json</code> file: composer.install = To install the package using Composer, run the following command: composer.dependencies = Dependencies composer.dependencies.development = Development Dependencies conan.details.repository = Repository -conan.registry = Setup this registry from the command line: +conan.registry = Set up this registry from the command line: conan.install = To install the package using Conan, run the following command: -conda.registry = Setup this registry as a Conda repository in your <code>.condarc</code> file: +conda.registry = Set up this registry as a Conda repository in your <code>.condarc</code> file: conda.install = To install the package using Conda, run the following command: container.details.type = Image Type container.details.platform = Platform @@ -3663,9 +3663,9 @@ container.layers = Image Layers container.labels = Labels container.labels.key = Key container.labels.value = Value -cran.registry = Setup this registry in your <code>Rprofile.site</code> file: +cran.registry = Set up this registry in your <code>Rprofile.site</code> file: cran.install = To install the package, run the following command: -debian.registry = Setup this registry from the command line: +debian.registry = Set up this registry from the command line: debian.registry.info = Choose $distribution and $component from the list below. debian.install = To install the package, run the following command: debian.repository = Repository Info @@ -3674,16 +3674,16 @@ debian.repository.components = Components debian.repository.architectures = Architectures generic.download = Download package from the command line: go.install = Install the package from the command line: -helm.registry = Setup this registry from the command line: +helm.registry = Set up this registry from the command line: helm.install = To install the package, run the following command: -maven.registry = Setup this registry in your project <code>pom.xml</code> file: -maven.install = To use the package include the following in the <code>dependencies</code> block in the <code>pom.xml</code> file: +maven.registry = Set up this registry in your project <code>pom.xml</code> file: +maven.install = To use the package, include the following in the <code>dependencies</code> block in the <code>pom.xml</code> file: maven.install2 = Run via command line: maven.download = To download the dependency, run via command line: -nuget.registry = Setup this registry from the command line: +nuget.registry = Set up this registry from the command line: nuget.install = To install the package using NuGet, run the following command: nuget.dependency.framework = Target Framework -npm.registry = Setup this registry in your project <code>.npmrc</code> file: +npm.registry = Set up this registry in your project <code>.npmrc</code> file: npm.install = To install the package using npm, run the following command: npm.install2 = or add it to the package.json file: npm.dependencies = Dependencies @@ -3695,7 +3695,7 @@ npm.details.tag = Tag pub.install = To install the package using Dart, run the following command: pypi.requires = Requires Python pypi.install = To install the package using pip, run the following command: -rpm.registry = Setup this registry from the command line: +rpm.registry = Set up this registry from the command line: rpm.distros.redhat = on RedHat based distributions rpm.distros.suse = on SUSE based distributions rpm.install = To install the package, run the following command: @@ -3708,7 +3708,7 @@ rubygems.dependencies.runtime = Runtime Dependencies rubygems.dependencies.development = Development Dependencies rubygems.required.ruby = Requires Ruby version rubygems.required.rubygems = Requires RubyGem version -swift.registry = Setup this registry from the command line: +swift.registry = Set up this registry from the command line: swift.install = Add the package in your <code>Package.swift</code> file: swift.install2 = and run the following command: vagrant.install = To add a Vagrant box, run the following command: diff --git a/poetry.toml b/poetry.toml deleted file mode 100644 index 6eda9f8eff..0000000000 --- a/poetry.toml +++ /dev/null @@ -1,3 +0,0 @@ -[virtualenvs] -in-project = true -options.no-pip = true diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index ef0c5cc199..99eef2f53b 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -46,7 +46,7 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: owner of the repository + // description: owner of the repo // type: string // required: true // - name: repo @@ -216,7 +216,7 @@ func (Action) GetVariable(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -270,7 +270,7 @@ func (Action) DeleteVariable(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -319,7 +319,7 @@ func (Action) CreateVariable(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -386,7 +386,7 @@ func (Action) UpdateVariable(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -458,7 +458,7 @@ func (Action) ListVariables(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -660,7 +660,7 @@ func (Action) ListWorkflowJobs(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -704,7 +704,7 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -1110,7 +1110,7 @@ func GetWorkflowRun(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -1156,7 +1156,7 @@ func ListWorkflowRunJobs(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -1215,7 +1215,7 @@ func GetWorkflowJob(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -1261,7 +1261,7 @@ func GetArtifactsOfRun(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -1330,7 +1330,7 @@ func DeleteActionRun(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -1382,7 +1382,7 @@ func GetArtifacts(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -1443,7 +1443,7 @@ func GetArtifact(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -1492,7 +1492,7 @@ func DeleteArtifact(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo @@ -1559,7 +1559,7 @@ func DownloadArtifact(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo diff --git a/routers/api/v1/repo/actions_run.go b/routers/api/v1/repo/actions_run.go index c6d18af6aa..a12a6fdd6d 100644 --- a/routers/api/v1/repo/actions_run.go +++ b/routers/api/v1/repo/actions_run.go @@ -21,7 +21,7 @@ func DownloadActionsRunJobLogs(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: name of the owner + // description: owner of the repo // type: string // required: true // - name: repo diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index e05b9b165c..09729200d5 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -52,7 +52,7 @@ func ListPullRequests(ctx *context.APIContext) { // parameters: // - name: owner // in: path - // description: Owner of the repo + // description: owner of the repo // type: string // required: true // - name: repo diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 292b267c01..4b88ed0b78 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -329,7 +329,7 @@ func Generate(ctx *context.APIContext) { // parameters: // - name: template_owner // in: path - // description: name of the template repository owner + // description: owner of the template repository // type: string // required: true // - name: template_repo diff --git a/services/asymkey/deploy_key.go b/services/asymkey/deploy_key.go index 9e5a6d6292..04023f9ffb 100644 --- a/services/asymkey/deploy_key.go +++ b/services/asymkey/deploy_key.go @@ -49,28 +49,21 @@ func deleteDeployKeyFromDB(ctx context.Context, key *asymkey_model.DeployKey) er // DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed. // Permissions check should be done outside. func DeleteDeployKey(ctx context.Context, repo *repo_model.Repository, id int64) error { - dbCtx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - key, err := asymkey_model.GetDeployKeyByID(ctx, id) - if err != nil { - if asymkey_model.IsErrDeployKeyNotExist(err) { - return nil + if err := db.WithTx(ctx, func(ctx context.Context) error { + key, err := asymkey_model.GetDeployKeyByID(ctx, id) + if err != nil { + if asymkey_model.IsErrDeployKeyNotExist(err) { + return nil + } + return fmt.Errorf("GetDeployKeyByID: %w", err) } - return fmt.Errorf("GetDeployKeyByID: %w", err) - } - if key.RepoID != repo.ID { - return fmt.Errorf("deploy key %d does not belong to repository %d", id, repo.ID) - } + if key.RepoID != repo.ID { + return fmt.Errorf("deploy key %d does not belong to repository %d", id, repo.ID) + } - if err := deleteDeployKeyFromDB(dbCtx, key); err != nil { - return err - } - if err := committer.Commit(); err != nil { + return deleteDeployKeyFromDB(ctx, key) + }); err != nil { return err } diff --git a/services/asymkey/ssh_key.go b/services/asymkey/ssh_key.go index da57059d4b..01fa7ff15f 100644 --- a/services/asymkey/ssh_key.go +++ b/services/asymkey/ssh_key.go @@ -27,20 +27,9 @@ func DeletePublicKey(ctx context.Context, doer *user_model.User, id int64) (err } } - dbCtx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if _, err = db.DeleteByID[asymkey_model.PublicKey](dbCtx, id); err != nil { - return err - } - - if err = committer.Commit(); err != nil { + if _, err = db.DeleteByID[asymkey_model.PublicKey](ctx, id); err != nil { return err } - committer.Close() if key.Type == asymkey_model.KeyTypePrincipal { return RewriteAllPrincipalKeys(ctx) diff --git a/services/asymkey/ssh_key_principals.go b/services/asymkey/ssh_key_principals.go index 5ed5cfa782..6493c1cc51 100644 --- a/services/asymkey/ssh_key_principals.go +++ b/services/asymkey/ssh_key_principals.go @@ -14,24 +14,6 @@ import ( // AddPrincipalKey adds new principal to database and authorized_principals file. func AddPrincipalKey(ctx context.Context, ownerID int64, content string, authSourceID int64) (*asymkey_model.PublicKey, error) { - dbCtx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - // Principals cannot be duplicated. - has, err := db.GetEngine(dbCtx). - Where("content = ? AND type = ?", content, asymkey_model.KeyTypePrincipal). - Get(new(asymkey_model.PublicKey)) - if err != nil { - return nil, err - } else if has { - return nil, asymkey_model.ErrKeyAlreadyExist{ - Content: content, - } - } - key := &asymkey_model.PublicKey{ OwnerID: ownerID, Name: content, @@ -40,15 +22,27 @@ func AddPrincipalKey(ctx context.Context, ownerID int64, content string, authSou Type: asymkey_model.KeyTypePrincipal, LoginSourceID: authSourceID, } - if err = db.Insert(dbCtx, key); err != nil { - return nil, fmt.Errorf("addKey: %w", err) - } - if err = committer.Commit(); err != nil { + if err := db.WithTx(ctx, func(ctx context.Context) error { + // Principals cannot be duplicated. + has, err := db.GetEngine(ctx). + Where("content = ? AND type = ?", content, asymkey_model.KeyTypePrincipal). + Get(new(asymkey_model.PublicKey)) + if err != nil { + return err + } else if has { + return asymkey_model.ErrKeyAlreadyExist{ + Content: content, + } + } + + if err = db.Insert(ctx, key); err != nil { + return fmt.Errorf("addKey: %w", err) + } + return nil + }); err != nil { return nil, err } - committer.Close() - return key, RewriteAllPrincipalKeys(ctx) } diff --git a/services/issue/label.go b/services/issue/label.go index 6b8070d8aa..e30983df37 100644 --- a/services/issue/label.go +++ b/services/issue/label.go @@ -46,32 +46,24 @@ func AddLabels(ctx context.Context, issue *issues_model.Issue, doer *user_model. // RemoveLabel removes a label from issue by given ID. func RemoveLabel(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, label *issues_model.Label) error { - dbCtx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err := issue.LoadRepo(dbCtx); err != nil { - return err - } - - perm, err := access_model.GetUserRepoPermission(dbCtx, issue.Repo, doer) - if err != nil { - return err - } - if !perm.CanWriteIssuesOrPulls(issue.IsPull) { - if label.OrgID > 0 { - return issues_model.ErrOrgLabelNotExist{} + if err := db.WithTx(ctx, func(ctx context.Context) error { + if err := issue.LoadRepo(ctx); err != nil { + return err } - return issues_model.ErrRepoLabelNotExist{} - } - if err := issues_model.DeleteIssueLabel(dbCtx, issue, label, doer); err != nil { - return err - } + perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) + if err != nil { + return err + } + if !perm.CanWriteIssuesOrPulls(issue.IsPull) { + if label.OrgID > 0 { + return issues_model.ErrOrgLabelNotExist{} + } + return issues_model.ErrRepoLabelNotExist{} + } - if err := committer.Commit(); err != nil { + return issues_model.DeleteIssueLabel(ctx, issue, label, doer) + }); err != nil { return err } diff --git a/services/issue/milestone.go b/services/issue/milestone.go index afca70794d..05aefad752 100644 --- a/services/issue/milestone.go +++ b/services/issue/milestone.go @@ -69,21 +69,12 @@ func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *is // ChangeMilestoneAssign changes assignment of milestone for issue. func ChangeMilestoneAssign(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, oldMilestoneID int64) (err error) { - dbCtx, committer, err := db.TxContext(ctx) - if err != nil { + if err := db.WithTx(ctx, func(dbCtx context.Context) error { + return changeMilestoneAssign(dbCtx, doer, issue, oldMilestoneID) + }); err != nil { return err } - defer committer.Close() - - if err = changeMilestoneAssign(dbCtx, doer, issue, oldMilestoneID); err != nil { - return err - } - - if err = committer.Commit(); err != nil { - return fmt.Errorf("Commit: %w", err) - } notify_service.IssueChangeMilestone(ctx, doer, issue, oldMilestoneID) - return nil } diff --git a/services/issue/status.go b/services/issue/status.go index f9d7dca841..fa59df93ba 100644 --- a/services/issue/status.go +++ b/services/issue/status.go @@ -15,30 +15,24 @@ import ( // CloseIssue close an issue. func CloseIssue(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, commitID string) error { - dbCtx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - comment, err := issues_model.CloseIssue(dbCtx, issue, doer) - if err != nil { - if issues_model.IsErrDependenciesLeft(err) { - if _, err := issues_model.FinishIssueStopwatch(dbCtx, doer, issue); err != nil { - log.Error("Unable to stop stopwatch for issue[%d]#%d: %v", issue.ID, issue.Index, err) + var comment *issues_model.Comment + if err := db.WithTx(ctx, func(ctx context.Context) error { + var err error + comment, err = issues_model.CloseIssue(ctx, issue, doer) + if err != nil { + if issues_model.IsErrDependenciesLeft(err) { + if _, err := issues_model.FinishIssueStopwatch(ctx, doer, issue); err != nil { + log.Error("Unable to stop stopwatch for issue[%d]#%d: %v", issue.ID, issue.Index, err) + } } + return err } - return err - } - if _, err := issues_model.FinishIssueStopwatch(dbCtx, doer, issue); err != nil { + _, err = issues_model.FinishIssueStopwatch(ctx, doer, issue) return err - } - - if err := committer.Commit(); err != nil { + }); err != nil { return err } - committer.Close() notify_service.IssueChangeStatus(ctx, doer, commitID, issue, comment, true) diff --git a/services/org/team.go b/services/org/team.go index 6890dafd90..773bd11f49 100644 --- a/services/org/team.go +++ b/services/org/team.go @@ -54,39 +54,33 @@ func NewTeam(ctx context.Context, t *organization.Team) (err error) { return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName} } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err = db.Insert(ctx, t); err != nil { - return err - } - - // insert units for team - if len(t.Units) > 0 { - for _, unit := range t.Units { - unit.TeamID = t.ID - } - if err = db.Insert(ctx, &t.Units); err != nil { + return db.WithTx(ctx, func(ctx context.Context) error { + if err = db.Insert(ctx, t); err != nil { return err } - } - // Add all repositories to the team if it has access to all of them. - if t.IncludesAllRepositories { - err = repo_service.AddAllRepositoriesToTeam(ctx, t) - if err != nil { - return fmt.Errorf("addAllRepositories: %w", err) + // insert units for team + if len(t.Units) > 0 { + for _, unit := range t.Units { + unit.TeamID = t.ID + } + if err = db.Insert(ctx, &t.Units); err != nil { + return err + } + } + + // Add all repositories to the team if it has access to all of them. + if t.IncludesAllRepositories { + err = repo_service.AddAllRepositoriesToTeam(ctx, t) + if err != nil { + return fmt.Errorf("addAllRepositories: %w", err) + } } - } - // Update organization number of teams. - if _, err = db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil { + // Update organization number of teams. + _, err = db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID) return err - } - return committer.Commit() + }) } // UpdateTeam updates information of team. @@ -99,128 +93,117 @@ func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeA t.Description = t.Description[:255] } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - t.LowerName = strings.ToLower(t.Name) - has, err := db.Exist[organization.Team](ctx, builder.Eq{ - "org_id": t.OrgID, - "lower_name": t.LowerName, - }.And(builder.Neq{"id": t.ID}), - ) - if err != nil { - return err - } else if has { - return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName} - } - - sess := db.GetEngine(ctx) - if _, err = sess.ID(t.ID).Cols("name", "lower_name", "description", - "can_create_org_repo", "authorize", "includes_all_repositories").Update(t); err != nil { - return fmt.Errorf("update: %w", err) - } - - // update units for team - if len(t.Units) > 0 { - for _, unit := range t.Units { - unit.TeamID = t.ID - } - // Delete team-unit. - if _, err := sess. - Where("team_id=?", t.ID). - Delete(new(organization.TeamUnit)); err != nil { + return db.WithTx(ctx, func(ctx context.Context) error { + t.LowerName = strings.ToLower(t.Name) + has, err := db.Exist[organization.Team](ctx, builder.Eq{ + "org_id": t.OrgID, + "lower_name": t.LowerName, + }.And(builder.Neq{"id": t.ID}), + ) + if err != nil { return err + } else if has { + return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName} } - if _, err = sess.Cols("org_id", "team_id", "type", "access_mode").Insert(&t.Units); err != nil { - return err + + sess := db.GetEngine(ctx) + if _, err = sess.ID(t.ID).Cols("name", "lower_name", "description", + "can_create_org_repo", "authorize", "includes_all_repositories").Update(t); err != nil { + return fmt.Errorf("update: %w", err) } - } - // Update access for team members if needed. - if authChanged { - repos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{ - TeamID: t.ID, - }) - if err != nil { - return fmt.Errorf("GetTeamRepositories: %w", err) + // update units for team + if len(t.Units) > 0 { + for _, unit := range t.Units { + unit.TeamID = t.ID + } + // Delete team-unit. + if _, err := sess. + Where("team_id=?", t.ID). + Delete(new(organization.TeamUnit)); err != nil { + return err + } + if _, err = sess.Cols("org_id", "team_id", "type", "access_mode").Insert(&t.Units); err != nil { + return err + } } - for _, repo := range repos { - if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil { - return fmt.Errorf("recalculateTeamAccesses: %w", err) + // Update access for team members if needed. + if authChanged { + repos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{ + TeamID: t.ID, + }) + if err != nil { + return fmt.Errorf("GetTeamRepositories: %w", err) + } + + for _, repo := range repos { + if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil { + return fmt.Errorf("recalculateTeamAccesses: %w", err) + } } } - } - // Add all repositories to the team if it has access to all of them. - if includeAllChanged && t.IncludesAllRepositories { - err = repo_service.AddAllRepositoriesToTeam(ctx, t) - if err != nil { - return fmt.Errorf("addAllRepositories: %w", err) + // Add all repositories to the team if it has access to all of them. + if includeAllChanged && t.IncludesAllRepositories { + err = repo_service.AddAllRepositoriesToTeam(ctx, t) + if err != nil { + return fmt.Errorf("addAllRepositories: %w", err) + } } - } - return committer.Commit() + return nil + }) } // DeleteTeam deletes given team. // It's caller's responsibility to assign organization ID. func DeleteTeam(ctx context.Context, t *organization.Team) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err := t.LoadMembers(ctx); err != nil { - return err - } - - // update branch protections - { - protections := make([]*git_model.ProtectedBranch, 0, 10) - err := db.GetEngine(ctx).In("repo_id", - builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})). - Find(&protections) - if err != nil { - return fmt.Errorf("findProtectedBranches: %w", err) + return db.WithTx(ctx, func(ctx context.Context) error { + if err := t.LoadMembers(ctx); err != nil { + return err } - for _, p := range protections { - if err := git_model.RemoveTeamIDFromProtectedBranch(ctx, p, t.ID); err != nil { - return err + + // update branch protections + { + protections := make([]*git_model.ProtectedBranch, 0, 10) + err := db.GetEngine(ctx).In("repo_id", + builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})). + Find(&protections) + if err != nil { + return fmt.Errorf("findProtectedBranches: %w", err) + } + for _, p := range protections { + if err := git_model.RemoveTeamIDFromProtectedBranch(ctx, p, t.ID); err != nil { + return err + } } } - } - - if err := repo_service.RemoveAllRepositoriesFromTeam(ctx, t); err != nil { - return err - } - if err := db.DeleteBeans(ctx, - &organization.Team{ID: t.ID}, - &organization.TeamUser{OrgID: t.OrgID, TeamID: t.ID}, - &organization.TeamUnit{TeamID: t.ID}, - &organization.TeamInvite{TeamID: t.ID}, - &issues_model.Review{Type: issues_model.ReviewTypeRequest, ReviewerTeamID: t.ID}, // batch delete the binding relationship between team and PR (request review from team) - ); err != nil { - return err - } + if err := repo_service.RemoveAllRepositoriesFromTeam(ctx, t); err != nil { + return err + } - for _, tm := range t.Members { - if err := removeInvalidOrgUser(ctx, t.OrgID, tm); err != nil { + if err := db.DeleteBeans(ctx, + &organization.Team{ID: t.ID}, + &organization.TeamUser{OrgID: t.OrgID, TeamID: t.ID}, + &organization.TeamUnit{TeamID: t.ID}, + &organization.TeamInvite{TeamID: t.ID}, + &issues_model.Review{Type: issues_model.ReviewTypeRequest, ReviewerTeamID: t.ID}, // batch delete the binding relationship between team and PR (request review from team) + ); err != nil { return err } - } - // Update organization number of teams. - if _, err := db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil { - return err - } + for _, tm := range t.Members { + if err := removeInvalidOrgUser(ctx, t.OrgID, tm); err != nil { + return err + } + } - return committer.Commit() + // Update organization number of teams. + _, err := db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID) + return err + }) } // AddTeamMember adds new membership of given team to given organization, @@ -363,13 +346,7 @@ func removeInvalidOrgUser(ctx context.Context, orgID int64, user *user_model.Use // RemoveTeamMember removes member from given team of given organization. func RemoveTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - if err := removeTeamMember(ctx, team, user); err != nil { - return err - } - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + return removeTeamMember(ctx, team, user) + }) } diff --git a/services/packages/cleanup/cleanup.go b/services/packages/cleanup/cleanup.go index 959babe7cd..ec860db1bb 100644 --- a/services/packages/cleanup/cleanup.go +++ b/services/packages/cleanup/cleanup.go @@ -164,42 +164,38 @@ func ExecuteCleanupRules(ctx context.Context) error { }) } -func CleanupExpiredData(outerCtx context.Context, olderThan time.Duration) error { - ctx, committer, err := db.TxContext(outerCtx) - if err != nil { - return err - } - defer committer.Close() - - if err := container_service.Cleanup(ctx, olderThan); err != nil { - return err - } - - ps, err := packages_model.FindUnreferencedPackages(ctx) - if err != nil { - return err - } - for _, p := range ps { - if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypePackage, p.ID); err != nil { +func CleanupExpiredData(ctx context.Context, olderThan time.Duration) error { + pbs := make([]*packages_model.PackageBlob, 0, 100) + if err := db.WithTx(ctx, func(ctx context.Context) error { + if err := container_service.Cleanup(ctx, olderThan); err != nil { return err } - if err := packages_model.DeletePackageByID(ctx, p.ID); err != nil { + + ps, err := packages_model.FindUnreferencedPackages(ctx) + if err != nil { return err } - } - - pbs, err := packages_model.FindExpiredUnreferencedBlobs(ctx, olderThan) - if err != nil { - return err - } + for _, p := range ps { + if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypePackage, p.ID); err != nil { + return err + } + if err := packages_model.DeletePackageByID(ctx, p.ID); err != nil { + return err + } + } - for _, pb := range pbs { - if err := packages_model.DeleteBlobByID(ctx, pb.ID); err != nil { + pbs, err = packages_model.FindExpiredUnreferencedBlobs(ctx, olderThan) + if err != nil { return err } - } - if err := committer.Commit(); err != nil { + for _, pb := range pbs { + if err := packages_model.DeleteBlobByID(ctx, pb.ID); err != nil { + return err + } + } + return nil + }); err != nil { return err } diff --git a/services/packages/packages.go b/services/packages/packages.go index 4b16ee7285..22b26b6563 100644 --- a/services/packages/packages.go +++ b/services/packages/packages.go @@ -469,24 +469,15 @@ func RemovePackageVersionByNameAndVersion(ctx context.Context, doer *user_model. // RemovePackageVersion deletes the package version and all associated files func RemovePackageVersion(ctx context.Context, doer *user_model.User, pv *packages_model.PackageVersion) error { - dbCtx, committer, err := db.TxContext(ctx) + pd, err := packages_model.GetPackageDescriptor(ctx, pv) if err != nil { return err } - defer committer.Close() - - pd, err := packages_model.GetPackageDescriptor(dbCtx, pv) - if err != nil { - return err - } - - log.Trace("Deleting package: %v", pv.ID) - if err := DeletePackageVersionAndReferences(dbCtx, pv); err != nil { - return err - } - - if err := committer.Commit(); err != nil { + if err := db.WithTx(ctx, func(ctx context.Context) error { + log.Trace("Deleting package: %v", pv.ID) + return DeletePackageVersionAndReferences(ctx, pv) + }); err != nil { return err } diff --git a/services/pull/merge.go b/services/pull/merge.go index cd9aeb2ad1..a941c20435 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -687,48 +687,40 @@ func SetMerged(ctx context.Context, pr *issues_model.PullRequest, mergedCommitID return false, fmt.Errorf("unable to merge PullRequest[%d], some required fields are empty", pr.Index) } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return false, err - } - defer committer.Close() - - pr.Issue = nil - if err := pr.LoadIssue(ctx); err != nil { - return false, err - } - - if err := pr.Issue.LoadRepo(ctx); err != nil { - return false, err - } + return db.WithTx2(ctx, func(ctx context.Context) (bool, error) { + pr.Issue = nil + if err := pr.LoadIssue(ctx); err != nil { + return false, err + } - if err := pr.Issue.Repo.LoadOwner(ctx); err != nil { - return false, err - } + if err := pr.Issue.LoadRepo(ctx); err != nil { + return false, err + } - // Removing an auto merge pull and ignore if not exist - if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) { - return false, fmt.Errorf("DeleteScheduledAutoMerge[%d]: %v", pr.ID, err) - } + if err := pr.Issue.Repo.LoadOwner(ctx); err != nil { + return false, err + } - // Set issue as closed - if _, err := issues_model.SetIssueAsClosed(ctx, pr.Issue, pr.Merger, true); err != nil { - return false, fmt.Errorf("ChangeIssueStatus: %w", err) - } + // Removing an auto merge pull and ignore if not exist + if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) { + return false, fmt.Errorf("DeleteScheduledAutoMerge[%d]: %v", pr.ID, err) + } - // We need to save all of the data used to compute this merge as it may have already been changed by testPullRequestBranchMergeable. FIXME: need to set some state to prevent testPullRequestBranchMergeable from running whilst we are merging. - if cnt, err := db.GetEngine(ctx).Where("id = ?", pr.ID). - And("has_merged = ?", false). - Cols("has_merged, status, merge_base, merged_commit_id, merger_id, merged_unix, conflicted_files"). - Update(pr); err != nil { - return false, fmt.Errorf("failed to update pr[%d]: %w", pr.ID, err) - } else if cnt != 1 { - return false, issues_model.ErrIssueAlreadyChanged - } + // Set issue as closed + if _, err := issues_model.SetIssueAsClosed(ctx, pr.Issue, pr.Merger, true); err != nil { + return false, fmt.Errorf("ChangeIssueStatus: %w", err) + } - if err := committer.Commit(); err != nil { - return false, err - } + // We need to save all of the data used to compute this merge as it may have already been changed by testPullRequestBranchMergeable. FIXME: need to set some state to prevent testPullRequestBranchMergeable from running whilst we are merging. + if cnt, err := db.GetEngine(ctx).Where("id = ?", pr.ID). + And("has_merged = ?", false). + Cols("has_merged, status, merge_base, merged_commit_id, merger_id, merged_unix, conflicted_files"). + Update(pr); err != nil { + return false, fmt.Errorf("failed to update pr[%d]: %w", pr.ID, err) + } else if cnt != 1 { + return false, issues_model.ErrIssueAlreadyChanged + } - return true, nil + return true, nil + }) } diff --git a/services/repository/avatar.go b/services/repository/avatar.go index 26bf6da465..998ac42230 100644 --- a/services/repository/avatar.go +++ b/services/repository/avatar.go @@ -29,35 +29,30 @@ func UploadAvatar(ctx context.Context, repo *repo_model.Repository, data []byte) return nil } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - oldAvatarPath := repo.CustomAvatarRelativePath() - - // Users can upload the same image to other repo - prefix it with ID - // Then repo will be removed - only it avatar file will be removed - repo.Avatar = newAvatar - if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "avatar"); err != nil { - return fmt.Errorf("UploadAvatar: Update repository avatar: %w", err) - } - - if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error { - _, err := w.Write(avatarData) - return err - }); err != nil { - return fmt.Errorf("UploadAvatar %s failed: Failed to remove old repo avatar %s: %w", repo.RepoPath(), newAvatar, err) - } + return db.WithTx(ctx, func(ctx context.Context) error { + oldAvatarPath := repo.CustomAvatarRelativePath() + + // Users can upload the same image to other repo - prefix it with ID + // Then repo will be removed - only it avatar file will be removed + repo.Avatar = newAvatar + if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "avatar"); err != nil { + return fmt.Errorf("UploadAvatar: Update repository avatar: %w", err) + } - if len(oldAvatarPath) > 0 { - if err := storage.RepoAvatars.Delete(oldAvatarPath); err != nil { - return fmt.Errorf("UploadAvatar: Failed to remove old repo avatar %s: %w", oldAvatarPath, err) + if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error { + _, err := w.Write(avatarData) + return err + }); err != nil { + return fmt.Errorf("UploadAvatar %s failed: Failed to remove old repo avatar %s: %w", repo.RepoPath(), newAvatar, err) } - } - return committer.Commit() + if len(oldAvatarPath) > 0 { + if err := storage.RepoAvatars.Delete(oldAvatarPath); err != nil { + return fmt.Errorf("UploadAvatar: Failed to remove old repo avatar %s: %w", oldAvatarPath, err) + } + } + return nil + }) } // DeleteAvatar deletes the repos's custom avatar. @@ -70,22 +65,17 @@ func DeleteAvatar(ctx context.Context, repo *repo_model.Repository) error { avatarPath := repo.CustomAvatarRelativePath() log.Trace("DeleteAvatar[%d]: %s", repo.ID, avatarPath) - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - repo.Avatar = "" - if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "avatar"); err != nil { - return fmt.Errorf("DeleteAvatar: Update repository avatar: %w", err) - } - - if err := storage.RepoAvatars.Delete(avatarPath); err != nil { - return fmt.Errorf("DeleteAvatar: Failed to remove %s: %w", avatarPath, err) - } + return db.WithTx(ctx, func(ctx context.Context) error { + repo.Avatar = "" + if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "avatar"); err != nil { + return fmt.Errorf("DeleteAvatar: Update repository avatar: %w", err) + } - return committer.Commit() + if err := storage.RepoAvatars.Delete(avatarPath); err != nil { + return fmt.Errorf("DeleteAvatar: Failed to remove %s: %w", avatarPath, err) + } + return nil + }) } // RemoveRandomAvatars removes the randomly generated avatars that were created for repositories diff --git a/services/repository/collaboration.go b/services/repository/collaboration.go index b5fc523623..53b3c2e203 100644 --- a/services/repository/collaboration.go +++ b/services/repository/collaboration.go @@ -71,40 +71,32 @@ func DeleteCollaboration(ctx context.Context, repo *repo_model.Repository, colla UserID: collaborator.ID, } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if has, err := db.GetEngine(ctx).Delete(collaboration); err != nil { - return err - } else if has == 0 { - return committer.Commit() - } - - if err := repo.LoadOwner(ctx); err != nil { - return err - } + return db.WithTx(ctx, func(ctx context.Context) error { + if has, err := db.GetEngine(ctx).Delete(collaboration); err != nil { + return err + } else if has == 0 { + return nil + } - if err = access_model.RecalculateAccesses(ctx, repo); err != nil { - return err - } + if err := repo.LoadOwner(ctx); err != nil { + return err + } - if err = repo_model.WatchRepo(ctx, collaborator, repo, false); err != nil { - return err - } + if err = access_model.RecalculateAccesses(ctx, repo); err != nil { + return err + } - if err = ReconsiderWatches(ctx, repo, collaborator); err != nil { - return err - } + if err = repo_model.WatchRepo(ctx, collaborator, repo, false); err != nil { + return err + } - // Unassign a user from any issue (s)he has been assigned to in the repository - if err := ReconsiderRepoIssuesAssignee(ctx, repo, collaborator); err != nil { - return err - } + if err = ReconsiderWatches(ctx, repo, collaborator); err != nil { + return err + } - return committer.Commit() + // Unassign a user from any issue (s)he has been assigned to in the repository + return ReconsiderRepoIssuesAssignee(ctx, repo, collaborator) + }) } func ReconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Repository, user *user_model.User) error { diff --git a/services/repository/repo_team.go b/services/repository/repo_team.go index 672ee49fea..8ea186f8cc 100644 --- a/services/repository/repo_team.go +++ b/services/repository/repo_team.go @@ -86,17 +86,9 @@ func RemoveAllRepositoriesFromTeam(ctx context.Context, t *organization.Team) (e return nil } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err = removeAllRepositoriesFromTeam(ctx, t); err != nil { - return err - } - - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + return removeAllRepositoriesFromTeam(ctx, t) + }) } // removeAllRepositoriesFromTeam removes all repositories from team and recalculates access @@ -167,17 +159,9 @@ func RemoveRepositoryFromTeam(ctx context.Context, t *organization.Team, repoID return err } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err = removeRepositoryFromTeam(ctx, t, repo, true); err != nil { - return err - } - - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + return removeRepositoryFromTeam(ctx, t, repo, true) + }) } // removeRepositoryFromTeam removes a repository from a team and recalculates access diff --git a/services/repository/setting.go b/services/repository/setting.go index e0c787dd2d..b6873691eb 100644 --- a/services/repository/setting.go +++ b/services/repository/setting.go @@ -16,41 +16,37 @@ import ( // UpdateRepositoryUnits updates a repository's units func UpdateRepositoryUnits(ctx context.Context, repo *repo_model.Repository, units []repo_model.RepoUnit, deleteUnitTypes []unit.Type) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - // Delete existing settings of units before adding again - for _, u := range units { - deleteUnitTypes = append(deleteUnitTypes, u.Type) - } - - if slices.Contains(deleteUnitTypes, unit.TypeActions) { - if err := actions_service.CleanRepoScheduleTasks(ctx, repo); err != nil { - log.Error("CleanRepoScheduleTasks: %v", err) + return db.WithTx(ctx, func(ctx context.Context) error { + // Delete existing settings of units before adding again + for _, u := range units { + deleteUnitTypes = append(deleteUnitTypes, u.Type) } - } - for _, u := range units { - if u.Type == unit.TypeActions { - if err := actions_service.DetectAndHandleSchedules(ctx, repo); err != nil { - log.Error("DetectAndHandleSchedules: %v", err) + if slices.Contains(deleteUnitTypes, unit.TypeActions) { + if err := actions_service.CleanRepoScheduleTasks(ctx, repo); err != nil { + log.Error("CleanRepoScheduleTasks: %v", err) } - break } - } - if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(repo_model.RepoUnit)); err != nil { - return err - } + for _, u := range units { + if u.Type == unit.TypeActions { + if err := actions_service.DetectAndHandleSchedules(ctx, repo); err != nil { + log.Error("DetectAndHandleSchedules: %v", err) + } + break + } + } - if len(units) > 0 { - if err = db.Insert(ctx, units); err != nil { + if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(repo_model.RepoUnit)); err != nil { return err } - } - return committer.Commit() + if len(units) > 0 { + if err = db.Insert(ctx, units); err != nil { + return err + } + } + + return nil + }) } diff --git a/services/user/avatar.go b/services/user/avatar.go index 3f87466eaa..df188e5adc 100644 --- a/services/user/avatar.go +++ b/services/user/avatar.go @@ -24,26 +24,22 @@ func UploadAvatar(ctx context.Context, u *user_model.User, data []byte) error { return err } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - u.UseCustomAvatar = true - u.Avatar = avatar.HashAvatar(u.ID, data) - if err = user_model.UpdateUserCols(ctx, u, "use_custom_avatar", "avatar"); err != nil { - return fmt.Errorf("updateUser: %w", err) - } + return db.WithTx(ctx, func(ctx context.Context) error { + u.UseCustomAvatar = true + u.Avatar = avatar.HashAvatar(u.ID, data) + if err = user_model.UpdateUserCols(ctx, u, "use_custom_avatar", "avatar"); err != nil { + return fmt.Errorf("updateUser: %w", err) + } - if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error { - _, err := w.Write(avatarData) - return err - }); err != nil { - return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err) - } + if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error { + _, err := w.Write(avatarData) + return err + }); err != nil { + return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err) + } - return committer.Commit() + return nil + }) } // DeleteAvatar deletes the user's custom avatar. diff --git a/templates/swagger/ui.tmpl b/templates/swagger/ui.tmpl index c48ba82fe0..4ff3472807 100644 --- a/templates/swagger/ui.tmpl +++ b/templates/swagger/ui.tmpl @@ -5,6 +5,7 @@ <link href="{{AssetUrlPrefix}}/css/swagger.css?v={{AssetVersion}}" rel="stylesheet"> </head> <body> + {{/* TODO: add Help & Glossary to help users understand the API, and explain some concepts like "Owner" */}} <a class="swagger-back-link" href="{{AppSubUrl}}/">{{svg "octicon-reply"}}{{ctx.Locale.Tr "return_to_gitea"}}</a> <div id="swagger-ui" data-source="{{AppSubUrl}}/swagger.{{.APIJSONVersion}}.json"></div> <footer class="page-footer"></footer> diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 323e0d64ac..35c743dcd4 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -4585,7 +4585,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -4630,7 +4630,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -4674,7 +4674,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -4720,7 +4720,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -4766,7 +4766,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -4823,7 +4823,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -4869,7 +4869,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -5108,7 +5108,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -5189,7 +5189,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -5233,7 +5233,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -5279,7 +5279,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -5331,7 +5331,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -5395,7 +5395,7 @@ "parameters": [ { "type": "string", - "description": "owner of the repository", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -5609,7 +5609,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -5660,7 +5660,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -5704,7 +5704,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -5758,7 +5758,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -5812,7 +5812,7 @@ "parameters": [ { "type": "string", - "description": "name of the owner", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -13424,7 +13424,7 @@ "parameters": [ { "type": "string", - "description": "Owner of the repo", + "description": "owner of the repo", "name": "owner", "in": "path", "required": true @@ -17463,7 +17463,7 @@ "parameters": [ { "type": "string", - "description": "name of the template repository owner", + "description": "owner of the template repository", "name": "template_owner", "in": "path", "required": true @@ -24968,7 +24968,7 @@ "x-go-package": "code.gitea.io/gitea/modules/structs" }, "GenerateRepoOption": { - "description": "GenerateRepoOption options when creating repository using a template", + "description": "GenerateRepoOption options when creating a repository using a template", "type": "object", "required": [ "owner", @@ -25006,13 +25006,12 @@ "x-go-name": "Labels" }, "name": { - "description": "Name of the repository to create", "type": "string", "uniqueItems": true, "x-go-name": "Name" }, "owner": { - "description": "The organization or person who will own the new repository", + "description": "the organization's name or individual user's name who will own the new repository", "type": "string", "x-go-name": "Owner" }, @@ -25545,6 +25544,7 @@ "x-go-name": "Index" }, "owner": { + "description": "owner of the issue's repo", "type": "string", "x-go-name": "Owner" }, @@ -25929,7 +25929,7 @@ "x-go-name": "RepoName" }, "repo_owner": { - "description": "Name of User or Organisation who will own Repo after migration", + "description": "the organization's name or individual user's name who will own the migrated repository", "type": "string", "x-go-name": "RepoOwner" }, @@ -25949,7 +25949,7 @@ "x-go-name": "Service" }, "uid": { - "description": "deprecated (only for backwards compatibility)", + "description": "deprecated (only for backwards compatibility, use repo_owner instead)", "type": "integer", "format": "int64", "x-go-name": "RepoOwnerID" diff --git a/tests/integration/editor_test.go b/tests/integration/editor_test.go index ac47ed0094..ac1fb2abff 100644 --- a/tests/integration/editor_test.go +++ b/tests/integration/editor_test.go @@ -415,7 +415,7 @@ func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, b req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s-1/_edit/%s/%s?from_base_branch=%s", user, repo, branch, filePath, branch), editRequestForm) resp = session.MakeRequest(t, req, http.StatusBadRequest) respJSON := test.ParseJSONError(resp.Body.Bytes()) - assert.Equal(t, `Branch "master" already exists in your fork, please choose a new branch name.`, respJSON.ErrorMessage) + assert.Equal(t, `Branch "master" already exists in your fork. Please choose a new branch name.`, respJSON.ErrorMessage) // change a file in the forked repo (should succeed) newBranchName := htmlDoc.GetInputValueByName("new_branch_name") |