mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-28 14:59:31 +00:00
Merge branch 'main' into lunny/refactor_org_setting
This commit is contained in:
commit
a3d88b14f6
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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)"`
|
||||
|
@ -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 = A–Z
|
||||
string.desc = Z–A
|
||||
|
||||
[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 %s’s review %s"
|
||||
issues.review.dismissed = "dismissed %s's review %s"
|
||||
issues.review.dismissed_label = Dismissed
|
||||
issues.review.left_comment = left a comment
|
||||
issues.review.content.empty = You need to leave a comment indicating the requested change(s).
|
||||
@ -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:
|
||||
|
@ -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=编辑清理规则
|
||||
|
@ -1,3 +0,0 @@
|
||||
[virtualenvs]
|
||||
in-project = true
|
||||
options.no-pip = true
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
50
templates/swagger/v1_json.tmpl
generated
50
templates/swagger/v1_json.tmpl
generated
@ -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"
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user