Merge branch 'main' into lunny/refactor_org_setting

This commit is contained in:
Lunny Xiao 2025-07-23 10:11:28 -07:00
commit a3d88b14f6
68 changed files with 2252 additions and 2876 deletions

View File

@ -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
}
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 run.Repo == nil {
repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
if err != nil {
return err
}
run.Repo = repo
}
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) {

View File

@ -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
}
specRow.Next = timeutil.TimeStamp(schedule.Next(now).Unix())
// Insert the new schedule spec row
if err = db.Insert(ctx, specRow); err != nil {
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
}
}
}
// Commit transaction
return committer.Commit()
// 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
}
}
}
return nil
})
}
func DeleteScheduleTaskByRepo(ctx context.Context, id 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 {
if _, err := db.GetEngine(ctx).Delete(&ActionSchedule{RepoID: id}); err != nil {
return err
}
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) {

View File

@ -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()
return db.WithTx2(ctx, func(ctx context.Context) (*ActionTask, error) {
e := db.GetEngine(ctx)
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")
}
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
}
// 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 := 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 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
}
}
if task.Status.IsDone() {
// the state is final, do nothing
return task, nil
}
// 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 := 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 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
}
}
if err := committer.Commit(); err != nil {
return nil, err
}
return task, nil
})
}
func StopTask(ctx context.Context, taskID int64, status Status) error {

View File

@ -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
}
}
}
return committer.Commit()
// 3. increase repo
if repoID > 0 {
if err := increaseTasksVersionByScope(ctx, 0, repoID); err != nil {
log.Error("IncreaseTasksVersionByScope(Repo): %v", err)
return err
}
}
return nil
})
}

View File

@ -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 {

View File

@ -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 db.WithTx(ctx, func(ctx context.Context) error {
_, err = deleteGPGKey(ctx, key.KeyID)
return err
}
defer committer.Close()
if _, err = deleteGPGKey(ctx, key.KeyID); err != nil {
return err
}
return committer.Commit()
})
}
func FindGPGKeyWithSubKeys(ctx context.Context, keyID string) ([]*GPGKey, error) {

View File

@ -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()
return db.WithTx2(ctx, func(ctx context.Context) (string, error) {
key := new(GPGKey)
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
}
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)

View File

@ -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()
return db.WithTx2(ctx, func(ctx context.Context) (*PublicKey, error) {
if err := checkKeyFingerprint(ctx, fingerprint); err != nil {
return nil, err
}
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
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
}
if _, err = db.DeleteByID[PublicKey](ctx, key.ID); err != nil {
log.Error("DeleteByID[PublicKey]: %v", err)
continue
}
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.

View File

@ -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, ""}
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)
}
}
} 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)
}
}
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.

View File

@ -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()
return db.WithTx2(ctx, func(ctx context.Context) (string, error) {
key := new(PublicKey)
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{}
}
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,
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,
}
}
}
}
key.Verified = true
if _, err := db.GetEngine(ctx).ID(key.ID).Cols("verified").Update(key); err != nil {
return "", err
}
key.Verified = true
if _, err := db.GetEngine(ctx).ID(key.ID).Cols("verified").Update(key); err != nil {
return "", err
}
if err := committer.Commit(); err != nil {
return "", err
}
return key.Fingerprint, nil
return key.Fingerprint, nil
})
}

View File

@ -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()
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, 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)
})
}
//////////////////////////////////////////////////////

View File

@ -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()
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`": 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 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 {
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

View File

@ -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()
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)
}
// 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.Insert(ctx, opts.CommitStatus); err != nil {
return fmt.Errorf("insert CommitStatus[%s, %s]: %w", opts.Repo.FullName(), opts.SHA, err)
}
// 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)
}
return committer.Commit()
return nil
})
}
// SignCommitWithStatuses represents a commit with validation of signature and status state.

View File

@ -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.

View File

@ -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()
return db.WithTx2(ctx, func(ctx context.Context) (*LFSLock, error) {
if err := CheckLFSAccessForRepo(ctx, lock.OwnerID, repo, perm.AccessModeWrite); err != nil {
return nil, err
}
if err := CheckLFSAccessForRepo(dbCtx, 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(ctx, repo, lock.Path)
if err == nil {
return l, ErrLFSLockAlreadyExist{lock.RepoID, lock.Path}
}
if !IsErrLFSLockNotExist(err) {
return nil, err
}
l, err := GetLFSLock(dbCtx, repo, lock.Path)
if err == nil {
return l, ErrLFSLockAlreadyExist{lock.RepoID, lock.Path}
}
if !IsErrLFSLockNotExist(err) {
return nil, err
}
if err := db.Insert(ctx, lock); err != nil {
return nil, err
}
if err := db.Insert(dbCtx, 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()
return db.WithTx2(ctx, func(ctx context.Context) (*LFSLock, error) {
lock, err := GetLFSLockByID(ctx, id)
if err != nil {
return nil, err
}
lock, err := GetLFSLockByID(dbCtx, id)
if err != nil {
return nil, err
}
if err := CheckLFSAccessForRepo(ctx, u.ID, repo, perm.AccessModeWrite); err != nil {
return nil, err
}
if err := CheckLFSAccessForRepo(dbCtx, 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(ctx).ID(id).Delete(new(LFSLock)); err != nil {
return nil, err
}
if _, err := db.GetEngine(dbCtx).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

View File

@ -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
}
var commentMetaData *CommentMetaData
if opts.ProjectColumnTitle != "" {
commentMetaData = &CommentMetaData{
ProjectColumnID: opts.ProjectColumnID,
ProjectColumnTitle: opts.ProjectColumnTitle,
ProjectTitle: opts.ProjectTitle,
return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) {
var LabelID int64
if opts.Label != nil {
LabelID = opts.Label.ID
}
}
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
}
var commentMetaData *CommentMetaData
if opts.ProjectColumnTitle != "" {
commentMetaData = &CommentMetaData{
ProjectColumnID: opts.ProjectColumnID,
ProjectColumnTitle: opts.ProjectColumnTitle,
ProjectTitle: opts.ProjectTitle,
}
}
if err = opts.Repo.LoadOwner(ctx); 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 = updateCommentInfos(ctx, opts, comment); err != nil {
return nil, err
}
if err = opts.Repo.LoadOwner(ctx); 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 = updateCommentInfos(ctx, opts, comment); err != nil {
return nil, err
}
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)
return db.WithTx(ctx, func(ctx context.Context) error {
c.ContentVersion = contentVersion + 1
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 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
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 _, 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 {
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
})
}

View File

@ -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()
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}
}
// 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
}
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()
// 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()
return db.WithTx(ctx, func(ctx context.Context) error {
var issueDepToDelete IssueDependency
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}
}
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
}
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}
}
// 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()
// Add comment referencing the removed dependency
return createIssueDependencyComment(ctx, user, issue, dep, false)
})
}
// Check if the dependency already exists

View File

@ -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 {

View File

@ -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)
})
}

View File

@ -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()
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
}
// Do NOT add invalid labels
if issue.RepoID != label.RepoID && issue.Repo.OwnerID != label.OrgID {
return nil
}
// Do NOT add invalid labels
if issue.RepoID != label.RepoID && issue.Repo.OwnerID != label.OrgID {
return nil
}
if err = RemoveDuplicateExclusiveIssueLabels(ctx, issue, label, doer); err != nil {
return nil
}
if err = RemoveDuplicateExclusiveIssueLabels(ctx, issue, label, doer); err != nil {
return nil
}
if err = newIssueLabel(ctx, issue, label, doer); err != nil {
return err
}
if err = newIssueLabel(ctx, issue, label, doer); err != nil {
return err
}
issue.isLabelsLoaded = false
issue.Labels = nil
if err = issue.LoadLabels(ctx); 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()
return db.WithTx(ctx, func(ctx context.Context) error {
if err = newIssueLabels(ctx, issue, labels, doer); err != nil {
return err
}
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 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()
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 := 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{}
}
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
}
if err = committer.Commit(); err != nil {
return fmt.Errorf("Commit: %w", err)
}
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++
}
}
toAdd = append(toAdd, labels[addIndex:]...)
toRemove = append(toRemove, issue.Labels[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)
if len(toAdd) > 0 {
if err = newIssueLabels(ctx, issue, toAdd, doer); err != nil {
return fmt.Errorf("addLabels: %w", err)
}
addIndex++
} else {
toRemove = append(toRemove, removeLabel)
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)
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)
})
}

View File

@ -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()
return db.WithTx(ctx, func(ctx context.Context) error {
if err := UpdateIssueCols(ctx, opts.Issue, "is_locked"); err != nil {
return err
}
if err := UpdateIssueCols(ctx, opts.Issue, "is_locked"); 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
}
opt := &CreateCommentOptions{
Doer: opts.Doer,
Issue: opts.Issue,
Repo: opts.Issue.Repo,
Type: commentType,
Content: opts.Reason,
}
if _, err := CreateComment(ctx, opt); err != nil {
return err
}
return committer.Commit()
})
}

View File

@ -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()
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)
}
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)
}
if err = issue.LoadRepo(ctx); err != nil {
return fmt.Errorf("loadRepo: %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
}
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()
return db.WithTx(ctx, func(ctx context.Context) error {
if err = UpdateIssueCols(ctx, issue, "ref"); err != nil {
return fmt.Errorf("updateIssueCols: %w", err)
}
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)
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)
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 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()
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)
}
}
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 {
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
}
if err = SaveIssueContentHistory(ctx, doer.ID, issue.ID, 0,
timeutil.TimeStampNow(), issue.Content, false); 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
}
if err = SaveIssueContentHistory(ctx, doer.ID, issue.ID, 0,
timeutil.TimeStampNow(), issue.Content, false); err != nil {
return fmt.Errorf("SaveIssueContentHistory: %w", err)
}
if err = issue.AddCrossReferences(ctx, doer, true); err != nil {
return fmt.Errorf("addCrossReferences: %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
}
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
}
// Make the comment
if _, err = createDeadlineComment(ctx, doer, issue, deadlineUnix); err != nil {
return fmt.Errorf("createRemovedDueDateComment: %w", 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.

View File

@ -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()
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
for _, l := range labels {
color, err := label.NormalizeColor(l.Color)
if err != nil {
return err
if err := db.Insert(ctx, l); err != nil {
return err
}
}
l.Color = color
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 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 = db.GetEngine(ctx).
Where("label_id = ?", labelID).
Delete(new(IssueLabel)); err != nil {
return err
}
// delete comments about now deleted label_id
_, err = db.GetEngine(ctx).Where("label_id = ?", labelID).Cols("label_id").Delete(&Comment{})
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
}
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
}
// delete comments about now deleted label_id
if _, err = sess.Where("label_id = ?", labelID).Cols("label_id").Delete(&Comment{}); err != nil {
return err
}
return committer.Commit()
})
}
// GetLabelByID returns a label by given ID.

View File

@ -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 {
_, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID)
return err
}
if _, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID); err != nil {
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()
return db.WithTx(ctx, func(ctx context.Context) error {
if m.IsClosed && !oldIsClosed {
m.ClosedDateUnix = timeutil.TimeStampNow()
}
if m.IsClosed && !oldIsClosed {
m.ClosedDateUnix = timeutil.TimeStampNow()
}
if err := updateMilestone(ctx, m); err != nil {
return err
}
// 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()
return db.WithTx(ctx, func(ctx context.Context) error {
m := &Milestone{
ID: milestoneID,
RepoID: repoID,
}
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}
}
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}
}
if err := changeMilestoneStatus(ctx, m, isClosed); err != nil {
return err
}
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()
return db.WithTx(ctx, func(ctx context.Context) error {
if _, err = db.DeleteByID[Milestone](ctx, m.ID); err != nil {
return err
}
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 = db.GetEngine(ctx).ID(repo.ID).Cols("num_milestones, num_closed_milestones").Update(repo); err != nil {
return err
}
_, err = db.Exec(ctx, "UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID)
return err
})
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.Exec(ctx, "UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID); err != nil {
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()
})
}

View File

@ -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)
}
issue.Index = idx
issue.Title = util.EllipsisDisplayString(issue.Title, 255)
if err = committer.Commit(); err != nil {
return fmt.Errorf("Commit: %w", err)
}
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
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
}
}
pr.IssueID = pr.Issue.ID
if _, err := sess.NoAutoTime().Insert(pr); err != nil {
return err
}
}
return committer.Commit()
return nil
})
}
// GetPullRequestByMergedCommit returns a merged pull request by the given commit

View File

@ -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.

View File

@ -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)
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
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
}
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,
}
// 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")
}
if _, err := sess.Insert(review); err != nil {
return nil, err
}
return review, committer.Commit()
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
}
}
// 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")
}
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 {
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{}
}
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 {
review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID)
if err != nil && !IsErrReviewNotExist(err) {
return nil, err
}
}
review, err = CreateReview(ctx, CreateReviewOptions{
Type: ReviewTypeRequest,
Issue: issue,
Reviewer: reviewer,
Official: official,
Stale: false,
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.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 {
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
}
// func caller use the created comment to retrieve created review too.
comment.Review = review
return comment, nil
})
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
return comment, committer.Commit()
}
// 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
}
if review == nil || review.Type != ReviewTypeRequest {
return nil, nil
}
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 {
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
}
}
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
if review == nil || review.Type != ReviewTypeRequest {
return nil, nil
}
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 {
return nil, err
}
}
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()
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
}
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 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)
}
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
}
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)
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
}
// This team already has been requested to review - therefore skip this.
if review != nil {
if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE id=?", true, review.ID); err != 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)
}
}
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 doer == nil {
return nil, committer.Commit()
}
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: true, // Use RemovedAssignee as !isRequest
AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team 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) {
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
}
if review == nil {
return nil, nil
}
if _, err = db.DeleteByBean(ctx, review); err != nil {
return nil, err
}
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, 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)
}
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()
return db.WithTx(ctx, func(ctx context.Context) error {
if r.ID == 0 {
return errors.New("review is not allowed to be 0")
}
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")
}
if r.Type == ReviewTypeRequest {
return errors.New("review request can not be deleted using this method")
}
opts := FindCommentsOptions{
Type: CommentTypeCode,
IssueID: r.IssueID,
ReviewID: r.ID,
}
opts := FindCommentsOptions{
Type: CommentTypeCode,
IssueID: r.IssueID,
ReviewID: r.ID,
}
if _, err := db.Delete[Comment](ctx, opts); err != nil {
return err
}
opts = FindCommentsOptions{
Type: CommentTypeReview,
IssueID: r.IssueID,
ReviewID: r.ID,
}
if _, err := db.Delete[Comment](ctx, opts); err != nil {
return err
}
opts = FindCommentsOptions{
Type: CommentTypeDismissReview,
IssueID: r.IssueID,
ReviewID: r.ID,
}
if _, err := db.Delete[Comment](ctx, opts); err != nil {
return err
}
if _, err := db.DeleteByID[Review](ctx, r.ID); err != nil {
return err
}
if r.Official {
if err := restoreLatestOfficialReview(ctx, r.IssueID, r.ReviewerID); err != nil {
if _, err := db.Delete[Comment](ctx, opts); err != nil {
return err
}
}
return committer.Commit()
opts = FindCommentsOptions{
Type: CommentTypeReview,
IssueID: r.IssueID,
ReviewID: r.ID,
}
if _, err := db.Delete[Comment](ctx, opts); err != nil {
return err
}
opts = FindCommentsOptions{
Type: CommentTypeDismissReview,
IssueID: r.IssueID,
ReviewID: r.ID,
}
if _, err := db.Delete[Comment](ctx, opts); err != nil {
return err
}
if _, err := db.DeleteByID[Review](ctx, r.ID); err != nil {
return err
}
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

View File

@ -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()
return db.WithTx2(ctx, func(ctx context.Context) (*TrackedTime, error) {
t, err := addTime(ctx, user, issue, amount, created)
if err != nil {
return nil, err
}
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()
return db.WithTx(ctx, func(ctx context.Context) error {
opts := FindTrackedTimesOptions{
IssueID: issue.ID,
UserID: user.ID,
}
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 {
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
}
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 {
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()
return db.WithTx(ctx, func(ctx context.Context) error {
if err := t.LoadAttributes(ctx); err != nil {
return err
}
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 {
_, 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
}
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 {
return err
}
return committer.Commit()
})
}
func deleteTimes(ctx context.Context, opts FindTrackedTimesOptions) (removedTime int64, err error) {

View File

@ -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)
}
// 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)
}
// 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)
}
// 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
return db.WithTx(ctx, func(ctx context.Context) error {
if err = user_model.DeleteUserRedirect(ctx, org.Name); err != nil {
return 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
}
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)
}
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)
}
// 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)
}
return committer.Commit()
// 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)
}
// 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, &units); err != nil {
return err
}
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()
return db.WithTx(ctx, func(ctx context.Context) error {
// check in transaction
isAlreadyMember, err = IsOrganizationMember(ctx, orgID, uid)
if err != nil || isAlreadyMember {
return err
}
// 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,
}
ou := &OrgUser{
UID: uid,
OrgID: orgID,
IsPublic: setting.Service.DefaultOrgMemberVisible,
}
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 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.

View File

@ -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
})
}

View File

@ -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()
return db.WithTx(ctx, func(ctx context.Context) error {
p := new(Project)
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}
}
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}
}
if err := changeProjectStatus(ctx, p, isClosed); err != nil {
return err
}
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 {

View File

@ -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

View File

@ -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
}
}
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
var topLang string
var s int64
for lang, size := range stats {
if size > s {
s = size
topLang = lang
}
}
// 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 {
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
}
}
}
// 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
}
}
}
// 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
}
}
// 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
})
}

View File

@ -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
}
if _, err := sess.NoAutoTime().Insert(rel.Attachments); err != nil {
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
}
}
}
return committer.Commit()
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 nil
})
}
func FindTagsByCommitIDs(ctx context.Context, repoID int64, commitIDs ...string) (map[string][]*Release, error) {

View File

@ -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)
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 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 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
}
}
return committer.Commit()
return nil
})
}
// IsStaring checks if user has starred given repository.

View File

@ -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)
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
}
topic, err := GetRepoTopicByName(ctx, repoID, topicName)
if err != nil {
return nil, err
}
if topic != nil {
// Repo already have topic
topic, err = addTopicByNameToRepo(ctx, repoID, topicName)
if err != nil {
return nil, err
}
if err = syncTopicsInRepository(ctx, repoID); err != nil {
return nil, err
}
return topic, nil
}
topic, err = addTopicByNameToRepo(ctx, repoID, topicName)
if err != nil {
return nil, err
}
if err = syncTopicsInRepository(sess, repoID); err != nil {
return nil, err
}
return topic, committer.Commit()
})
}
// 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)
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 removeTopics []*Topic
for _, t := range topics {
var found bool
return db.WithTx(ctx, func(ctx context.Context) error {
var addedTopicNames []string
for _, topicName := range topicNames {
if strings.EqualFold(topicName, t.Name) {
found = true
break
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)
}
}
if !found {
removeTopics = append(removeTopics, t)
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)
}
}
}
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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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()
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.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_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()
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
})
}

View File

@ -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.

View File

@ -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"`
}

View File

@ -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)"`

View File

@ -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.
@ -169,7 +169,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
@ -179,23 +179,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]
@ -231,8 +231,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 = AZ
string.desc = ZA
[error]
occurred = An error occurred
@ -253,7 +253,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).
@ -270,16 +270,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
@ -295,7 +295,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
@ -431,8 +431,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.
@ -453,7 +453,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
@ -472,17 +472,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
@ -546,7 +546,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:
@ -598,7 +598,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.
@ -613,17 +613,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:
@ -648,14 +648,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
@ -686,7 +686,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
@ -703,7 +703,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:
@ -754,7 +754,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
@ -802,15 +802,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?
@ -856,7 +856,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:
@ -866,7 +866,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:
@ -887,7 +887,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.
@ -949,7 +949,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
@ -964,10 +964,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").
@ -977,7 +977,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.
@ -993,7 +993,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.
@ -1110,7 +1110,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)
@ -1123,7 +1123,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
@ -1165,8 +1165,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.
@ -1183,7 +1183,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
@ -1195,10 +1195,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
@ -1206,7 +1206,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.
@ -1255,7 +1255,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.
@ -1274,7 +1274,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
@ -1371,17 +1371,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?
@ -1407,7 +1407,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
@ -1509,7 +1509,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
@ -1581,7 +1581,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
@ -1781,9 +1781,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
@ -1794,13 +1794,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 %ss 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).
@ -1808,7 +1808,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
@ -1830,7 +1830,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
@ -1847,7 +1847,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
@ -1868,7 +1868,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.
@ -1897,7 +1897,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.
@ -1921,16 +1921,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
@ -1938,17 +1938,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
@ -1974,7 +1974,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
@ -2040,7 +2040,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.
@ -2154,13 +2154,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
@ -2178,7 +2178,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:
@ -2253,7 +2253,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
@ -2275,7 +2275,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).
@ -2290,13 +2290,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.
@ -2305,7 +2305,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.
@ -2327,8 +2327,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
@ -2593,7 +2593,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.
@ -2611,11 +2611,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
@ -2773,7 +2773,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
@ -2798,7 +2798,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.
@ -2837,7 +2837,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
@ -2859,10 +2859,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.
@ -2872,11 +2872,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.
@ -2933,7 +2933,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.
@ -2974,7 +2974,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
@ -3006,11 +3006,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
@ -3018,12 +3018,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
@ -3059,10 +3059,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
@ -3086,7 +3086,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
@ -3113,7 +3113,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
@ -3133,11 +3133,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
@ -3145,7 +3145,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
@ -3259,29 +3259,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
@ -3391,7 +3391,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
@ -3480,7 +3480,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
@ -3506,10 +3506,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]
@ -3593,8 +3593,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
@ -3633,7 +3633,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:
@ -3646,18 +3646,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
@ -3669,9 +3669,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
@ -3680,16 +3680,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
@ -3701,7 +3701,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:
@ -3714,7 +3714,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:

View File

@ -130,6 +130,7 @@ unpin=取消置顶
artifacts=产物
expired=已过期
confirm_delete_artifact=您确定要删除产物「%s」吗
archived=已归档
@ -459,6 +460,7 @@ oauth_signin_submit=绑定账号
oauth.signin.error.general=处理授权请求时出错:%s。如果此错误仍然存在请与站点管理员联系。
oauth.signin.error.access_denied=授权请求被拒绝。
oauth.signin.error.temporarily_unavailable=授权失败,因为认证服务器暂时不可用。请稍后再试。
oauth_callback_unable_auto_reg=自动注册已启用,但 OAuth2 提供商 %[1]s 返回缺失的字段:%[2]s无法自动创建帐户请创建或链接到一个帐户或联系站点管理员。
openid_connect_submit=连接
openid_connect_title=连接到现有的帐户
openid_connect_desc=所选的 OpenID URI 未知。在这里关联一个新帐户。
@ -499,6 +501,7 @@ activate_email.text=请在 <b>%s</b> 时间内,点击以下链接,以验证
register_notify=欢迎来到 %s
register_notify.title=%[1]s欢迎来到 %[2]s
register_notify.text_1=这是您的 %s 注册确认邮件
register_notify.text_3=如果此账户已为您创建,请先 <a href="%s">设置您的密码</a>。
reset_password=恢复您的账户
@ -723,6 +726,7 @@ webauthn=两步验证(安全密钥)
public_profile=公开信息
biography_placeholder=告诉我们一点您自己! (您可以使用 Markdown)
location_placeholder=与他人分享您的大概位置
profile_desc=控制您的个人资料对其他用户的显示方式。您的主邮箱地址将用于通知、密码恢复和基于网页的 Git 操作。
password_username_disabled=您不被允许更改您的用户名。更多详情请联系您的系统管理员。
password_full_name_disabled=您不被允许更改您的全名。请联系您的站点管理员了解更多详情。
full_name=自定义名称
@ -801,6 +805,7 @@ activations_pending=等待激活
can_not_add_email_activations_pending=有一个待处理的激活请求,请稍等几分钟后再尝试添加新的邮箱地址。
delete_email=移除
email_deletion=移除邮箱地址
email_deletion_desc=邮箱地址和相关信息将会被删除。使用此邮箱地址发送的Git提交将会保留继续
email_deletion_success=您的邮箱地址已移除。
theme_update_success=您的主题已更新。
theme_update_error=所选主题不存在。
@ -986,6 +991,7 @@ webauthn_alternative_tip=您可能想要配置额外的身份验证方法。
manage_account_links=管理绑定过的账号
manage_account_links_desc=这些外部帐户已经绑定到您的 Gitea 帐户。
account_links_not_available=当前没有外部帐户链接到您的 Gitea 帐户。
link_account=链接账户
remove_account_link=删除已绑定的账号
remove_account_link_desc=删除已绑定帐户将吊销其对您的 Gitea 帐户的访问权限。继续?
@ -1008,6 +1014,8 @@ email_notifications.onmention=仅被提及时通知
email_notifications.disable=停用邮件通知
email_notifications.submit=设置邮件通知
email_notifications.andyourown=仅与您相关的通知
email_notifications.actions.desc=设置了 <a target="_blank" href="%s">Gitea 工作流</a> 的仓库中工作流运行的通知。
email_notifications.actions.failure_only=仅在工作流运行失败时通知
visibility=用户可见性
visibility.public=公开
@ -1022,6 +1030,8 @@ new_repo_helper=代码仓库包含了所有的项目文件,包括版本历史
owner=拥有者
owner_helper=由于最大仓库数量限制,一些组织可能不会显示在下拉列表中。
repo_name=仓库名称
repo_name_profile_public_hint=.profile 是一个特殊的仓库,您可以使用它将 README.md 添加到您的公共组织资料中,任何人都可以看到。请确保它是公开的,并使用个人资料目录中的 README 对其进行初始化以开始使用。
repo_name_profile_private_hint=.profile-private 是一个特殊的仓库,您可以使用它向您的组织成员个人资料添加 README.md仅对组织成员可见。请确保它是私有的并使用个人资料目录中的 README 对其进行初始化以开始使用。
repo_name_helper=理想的仓库名称应由简短、有意义和独特的关键词组成。「.profile」和「.profile-private」可用于为用户/组织添加 README.md。
repo_size=仓库大小
template=模板
@ -1380,6 +1390,8 @@ editor.failed_to_commit=提交更改失败。
editor.failed_to_commit_summary=错误信息:
editor.fork_create=派生仓库以请求变更
editor.fork_create_description=您不能直接编辑此仓库。您可以派生此仓库,进行编辑并创建一个合并请求。
editor.fork_edit_description=您不能直接编辑此仓库。 更改将写入您的派生仓库 <b>%s</b>,以便您可以创建一个合并请求。
editor.fork_not_editable=您已经派生了此仓库,但您的派生是不可编辑的。
editor.fork_failed_to_push_branch=推送分支 %s 到仓库失败。
editor.fork_branch_exists=分支「%s」已存在于您的派生仓库中请选择一个新的分支名称。
@ -2795,6 +2807,7 @@ team_permission_desc=权限
team_unit_desc=允许访问仓库单元
team_unit_disabled=(已禁用)
form.name_been_taken=组织名称「%s」已经被占用。
form.name_reserved=组织名称「%s」是保留的。
form.name_pattern_not_allowed=组织名中不允许使用「%s」格式。
form.create_org_not_allowed=此账号禁止创建组织
@ -2836,6 +2849,7 @@ settings.delete_notices_2=此操作将永久删除 <strong>%s</strong> 的所有
settings.delete_notices_3=此操作将永久删除 <strong>%s</strong> 的所有 <strong>软件包</strong>。
settings.delete_notices_4=此操作将永久删除 <strong>%s</strong> 的所有 <strong>项目</strong>。
settings.confirm_delete_account=确认删除组织
settings.delete_failed=由于内部错误,删除组织失败
settings.delete_successful=组织 <b>%s</b> 已成功删除。
settings.hooks_desc=在此处添加的 Web 钩子将会应用到该组织下的 <strong>所有仓库</strong>。
@ -2985,6 +2999,8 @@ dashboard.resync_all_sshprincipals=使用 Gitea 的 SSH 规则更新「.ssh/auth
dashboard.resync_all_hooks=重新同步所有仓库的 pre-receive、update 和 post-receive 钩子
dashboard.reinit_missing_repos=重新初始化所有丢失的 Git 仓库存在的记录
dashboard.sync_external_users=同步外部用户数据
dashboard.cleanup_hook_task_table=清理 hook_task 表
dashboard.cleanup_actions=清理过期的工作流资源
dashboard.server_uptime=服务运行时间
dashboard.current_goroutine=当前 Goroutines 数量
dashboard.current_memory_usage=当前内存使用量
@ -3215,6 +3231,8 @@ auths.oauth2_required_claim_name_helper=设置此名称,只有具有此名称
auths.oauth2_required_claim_value=必须填写 Claim 声明的值
auths.oauth2_required_claim_value_helper=设置此值只有拥有对应的声明Claim的名称和值的用户才被允许从此源登录
auths.oauth2_group_claim_name=用于提供用户组名称的 Claim 声明名称。(可选)
auths.oauth2_full_name_claim_name=全名声明名称。(可选,如果设置,用户的全名将始终与此声明同步)
auths.oauth2_ssh_public_key_claim_name=SSH 公钥声明名称
auths.oauth2_admin_group=管理员用户组的 Claim 声明值。(可选 - 需要上面的声明名称)
auths.oauth2_restricted_group=受限用户组的 Claim 声明值。(可选 - 需要上面的声明名称)
auths.oauth2_map_group_to_team=映射声明的组到组织团队。(可选 - 要求在上面填写声明的名字)
@ -3690,6 +3708,7 @@ owner.settings.cargo.initialize.success=Cargo 索引已经成功创建。
owner.settings.cargo.rebuild=重建索引
owner.settings.cargo.rebuild.description=如果索引与存储的 Cargo 包不同步,重建可能会有用。
owner.settings.cargo.rebuild.error=无法重建 Cargo 索引: %v
owner.settings.cargo.rebuild.success=Cargo 索引已成功重建。
owner.settings.cleanuprules.title=管理清理规则
owner.settings.cleanuprules.add=添加清理规则
owner.settings.cleanuprules.edit=编辑清理规则

View File

@ -1,3 +0,0 @@
[virtualenvs]
in-project = true
options.no-pip = true

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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 {
if _, err = db.DeleteByID[asymkey_model.PublicKey](ctx, id); 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 {
return err
}
committer.Close()
if key.Type == asymkey_model.KeyTypePrincipal {
return RewriteAllPrincipalKeys(ctx)

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -15,31 +15,25 @@ 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
}); err != nil {
return err
}
if err := committer.Commit(); err != nil {
return err
}
committer.Close()
notify_service.IssueChangeStatus(ctx, doer, commitID, issue, comment, true)
return nil

View File

@ -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
}
}
}
// Update organization number of teams.
if _, err = db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
// 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.
_, 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 err
}
if _, err = sess.Cols("org_id", "team_id", "type", "access_mode").Insert(&t.Units); err != nil {
return err
}
}
// Update access for team members if needed.
if authChanged {
repos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{
TeamID: t.ID,
})
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 fmt.Errorf("GetTeamRepositories: %w", err)
return err
} else if has {
return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName}
}
for _, repo := range repos {
if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
return fmt.Errorf("recalculateTeamAccesses: %w", 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 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
}
}
}
// 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)
// 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)
}
}
}
}
return committer.Commit()
// 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 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 {
// 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
}
for _, tm := range t.Members {
if err := removeInvalidOrgUser(ctx, t.OrgID, tm); err != nil {
return err
}
}
}
if err := repo_service.RemoveAllRepositoriesFromTeam(ctx, t); 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
}
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
}
for _, tm := range t.Members {
if err := removeInvalidOrgUser(ctx, t.OrgID, tm); 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
}
return committer.Commit()
})
}
// 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)
})
}

View File

@ -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
}
}
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
}
}
pbs, err := packages_model.FindExpiredUnreferencedBlobs(ctx, olderThan)
if 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
}

View File

@ -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)
if err != nil {
return err
}
defer committer.Close()
pd, err := packages_model.GetPackageDescriptor(dbCtx, pv)
pd, err := packages_model.GetPackageDescriptor(ctx, 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
}

View File

@ -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()
return db.WithTx2(ctx, func(ctx context.Context) (bool, error) {
pr.Issue = nil
if err := pr.LoadIssue(ctx); err != nil {
return false, err
}
pr.Issue = nil
if err := pr.LoadIssue(ctx); err != nil {
return false, err
}
if err := pr.Issue.LoadRepo(ctx); err != nil {
return false, err
}
if err := pr.Issue.LoadRepo(ctx); err != nil {
return false, err
}
if err := pr.Issue.Repo.LoadOwner(ctx); err != nil {
return false, err
}
if err := pr.Issue.Repo.LoadOwner(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)
}
// 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)
}
// Set issue as closed
if _, err := issues_model.SetIssueAsClosed(ctx, pr.Issue, pr.Merger, true); err != nil {
return false, fmt.Errorf("ChangeIssueStatus: %w", 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)
}
// 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
}
// 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
}
if err := committer.Commit(); err != nil {
return false, err
}
return true, nil
return true, nil
})
}

View File

@ -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()
return db.WithTx(ctx, func(ctx context.Context) error {
oldAvatarPath := repo.CustomAvatarRelativePath()
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)
}
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)
// 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)
}
}
return committer.Commit()
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)
}
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()
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)
}
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 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

View File

@ -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()
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 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
}
if err := repo.LoadOwner(ctx); err != nil {
return err
}
if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
return err
}
if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
return err
}
if err = repo_model.WatchRepo(ctx, collaborator, repo, false); err != nil {
return err
}
if err = repo_model.WatchRepo(ctx, collaborator, repo, false); err != nil {
return err
}
if err = ReconsiderWatches(ctx, repo, collaborator); err != nil {
return err
}
if err = ReconsiderWatches(ctx, repo, collaborator); 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
}
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 {

View File

@ -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

View File

@ -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
})
}

View File

@ -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()
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)
}
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.

View File

@ -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>

View File

@ -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"

View File

@ -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")