mirror of https://github.com/go-gitea/gitea.git
Merge branch 'main' into label_delete_event_fix
commit
6aa8ba38cd
|
@ -80,6 +80,11 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDumpRepository(ctx *cli.Context) error {
|
func runDumpRepository(ctx *cli.Context) error {
|
||||||
|
setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr)
|
||||||
|
|
||||||
|
setting.DisableLoggerInit()
|
||||||
|
setting.LoadSettings() // cannot access skip_tls_verify settings otherwise
|
||||||
|
|
||||||
stdCtx, cancel := installSignals()
|
stdCtx, cancel := installSignals()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1739214665,
|
"lastModified": 1747179050,
|
||||||
"narHash": "sha256-26L8VAu3/1YRxS8MHgBOyOM8xALdo6N0I04PgorE7UM=",
|
"narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "64e75cd44acf21c7933d61d7721e812eac1b5a0a",
|
"rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -317,7 +317,7 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
|
||||||
|
|
||||||
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
|
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
|
||||||
|
|
||||||
replace github.com/nektos/act => gitea.com/gitea/act v0.261.4
|
replace github.com/nektos/act => gitea.com/gitea/act v0.261.6
|
||||||
|
|
||||||
// TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why
|
// TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why
|
||||||
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0
|
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -14,8 +14,8 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
gitea.com/gitea/act v0.261.4 h1:Tf9eLlvsYFtKcpuxlMvf9yT3g4Hshb2Beqw6C1STuH8=
|
gitea.com/gitea/act v0.261.6 h1:CjZwKOyejonNFDmsXOw3wGm5Vet573hHM6VMLsxtvPY=
|
||||||
gitea.com/gitea/act v0.261.4/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
gitea.com/gitea/act v0.261.6/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
||||||
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
|
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
|
||||||
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
|
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
|
||||||
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
|
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
@ -171,6 +172,7 @@ func (run *ActionRun) IsSchedule() bool {
|
||||||
|
|
||||||
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
|
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
|
||||||
_, err := db.GetEngine(ctx).ID(repo.ID).
|
_, err := db.GetEngine(ctx).ID(repo.ID).
|
||||||
|
NoAutoTime().
|
||||||
SetExpr("num_action_runs",
|
SetExpr("num_action_runs",
|
||||||
builder.Select("count(*)").From("action_run").
|
builder.Select("count(*)").From("action_run").
|
||||||
Where(builder.Eq{"repo_id": repo.ID}),
|
Where(builder.Eq{"repo_id": repo.ID}),
|
||||||
|
@ -342,13 +344,13 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
|
||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) {
|
func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, error) {
|
||||||
var run ActionRun
|
var run ActionRun
|
||||||
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run)
|
has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", runID, repoID).Get(&run)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
return nil, fmt.Errorf("run with id %d: %w", id, util.ErrNotExist)
|
return nil, fmt.Errorf("run with id %d: %w", runID, util.ErrNotExist)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &run, nil
|
return &run, nil
|
||||||
|
@ -419,18 +421,11 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
|
||||||
|
|
||||||
if run.Status != 0 || slices.Contains(cols, "status") {
|
if run.Status != 0 || slices.Contains(cols, "status") {
|
||||||
if run.RepoID == 0 {
|
if run.RepoID == 0 {
|
||||||
run, err = GetRunByID(ctx, run.ID)
|
setting.PanicInDevOrTesting("RepoID should not be 0")
|
||||||
if err != nil {
|
}
|
||||||
|
if err = run.LoadRepo(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
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 {
|
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ func (job *ActionRunJob) Duration() time.Duration {
|
||||||
|
|
||||||
func (job *ActionRunJob) LoadRun(ctx context.Context) error {
|
func (job *ActionRunJob) LoadRun(ctx context.Context) error {
|
||||||
if job.Run == nil {
|
if job.Run == nil {
|
||||||
run, err := GetRunByID(ctx, job.RunID)
|
run, err := GetRunByRepoAndID(ctx, job.RepoID, job.RunID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -142,7 +142,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
|
||||||
{
|
{
|
||||||
// Other goroutines may aggregate the status of the run and update it too.
|
// Other goroutines may aggregate the status of the run and update it too.
|
||||||
// So we need load the run and its jobs before updating the run.
|
// So we need load the run and its jobs before updating the run.
|
||||||
run, err := GetRunByID(ctx, job.RunID)
|
run, err := GetRunByRepoAndID(ctx, job.RepoID, job.RunID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -298,6 +299,23 @@ func DeleteRunner(ctx context.Context, id int64) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteEphemeralRunner deletes a ephemeral runner by given ID.
|
||||||
|
func DeleteEphemeralRunner(ctx context.Context, id int64) error {
|
||||||
|
runner, err := GetRunnerByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !runner.Ephemeral {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.DeleteByID[ActionRunner](ctx, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// CreateRunner creates new runner.
|
// CreateRunner creates new runner.
|
||||||
func CreateRunner(ctx context.Context, t *ActionRunner) error {
|
func CreateRunner(ctx context.Context, t *ActionRunner) error {
|
||||||
if t.OwnerID != 0 && t.RepoID != 0 {
|
if t.OwnerID != 0 && t.RepoID != 0 {
|
||||||
|
|
|
@ -336,6 +336,11 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
|
||||||
sess.Cols(cols...)
|
sess.Cols(cols...)
|
||||||
}
|
}
|
||||||
_, err := sess.Update(task)
|
_, err := sess.Update(task)
|
||||||
|
|
||||||
|
// Automatically delete the ephemeral runner if the task is done
|
||||||
|
if err == nil && task.Status.IsDone() && util.SliceContainsString(cols, "status") {
|
||||||
|
return DeleteEphemeralRunner(ctx, task.RunnerID)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ func (tasks TaskList) LoadAttributes(ctx context.Context) error {
|
||||||
type FindTaskOptions struct {
|
type FindTaskOptions struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
RepoID int64
|
RepoID int64
|
||||||
|
JobID int64
|
||||||
OwnerID int64
|
OwnerID int64
|
||||||
CommitSHA string
|
CommitSHA string
|
||||||
Status Status
|
Status Status
|
||||||
|
@ -61,6 +62,9 @@ func (opts FindTaskOptions) ToConds() builder.Cond {
|
||||||
if opts.RepoID > 0 {
|
if opts.RepoID > 0 {
|
||||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
}
|
}
|
||||||
|
if opts.JobID > 0 {
|
||||||
|
cond = cond.And(builder.Eq{"job_id": opts.JobID})
|
||||||
|
}
|
||||||
if opts.OwnerID > 0 {
|
if opts.OwnerID > 0 {
|
||||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,3 +82,22 @@ func calculateDuration(started, stopped timeutil.TimeStamp, status Status) time.
|
||||||
}
|
}
|
||||||
return timeSince(s).Truncate(time.Second)
|
return timeSince(s).Truncate(time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// best effort function to convert an action schedule to action run, to be used in GenerateGiteaContext
|
||||||
|
func (s *ActionSchedule) ToActionRun() *ActionRun {
|
||||||
|
return &ActionRun{
|
||||||
|
Title: s.Title,
|
||||||
|
RepoID: s.RepoID,
|
||||||
|
Repo: s.Repo,
|
||||||
|
OwnerID: s.OwnerID,
|
||||||
|
WorkflowID: s.WorkflowID,
|
||||||
|
TriggerUserID: s.TriggerUserID,
|
||||||
|
TriggerUser: s.TriggerUser,
|
||||||
|
Ref: s.Ref,
|
||||||
|
CommitSHA: s.CommitSHA,
|
||||||
|
Event: s.Event,
|
||||||
|
EventPayload: s.EventPayload,
|
||||||
|
Created: s.Created,
|
||||||
|
Updated: s.Updated,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -530,7 +530,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
|
||||||
|
|
||||||
if opts.RequestedTeam != nil {
|
if opts.RequestedTeam != nil {
|
||||||
env := repo_model.AccessibleTeamReposEnv(organization.OrgFromUser(opts.RequestedUser), opts.RequestedTeam)
|
env := repo_model.AccessibleTeamReposEnv(organization.OrgFromUser(opts.RequestedUser), opts.RequestedTeam)
|
||||||
teamRepoIDs, err := env.RepoIDs(ctx, 1, opts.RequestedUser.NumRepos)
|
teamRepoIDs, err := env.RepoIDs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetTeamRepositories: %w", err)
|
return nil, fmt.Errorf("GetTeamRepositories: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,3 +105,39 @@
|
||||||
created_unix: 1730330775
|
created_unix: 1730330775
|
||||||
updated_unix: 1730330775
|
updated_unix: 1730330775
|
||||||
expired_unix: 1738106775
|
expired_unix: 1738106775
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 24
|
||||||
|
run_id: 795
|
||||||
|
runner_id: 1
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
storage_path: "27/5/1730330775594233150.chunk"
|
||||||
|
file_size: 1024
|
||||||
|
file_compressed_size: 1024
|
||||||
|
content_encoding: "application/zip"
|
||||||
|
artifact_path: "artifact-795-1.zip"
|
||||||
|
artifact_name: "artifact-795-1"
|
||||||
|
status: 2
|
||||||
|
created_unix: 1730330775
|
||||||
|
updated_unix: 1730330775
|
||||||
|
expired_unix: 1738106775
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 25
|
||||||
|
run_id: 795
|
||||||
|
runner_id: 1
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
storage_path: "27/5/1730330775594233150.chunk"
|
||||||
|
file_size: 1024
|
||||||
|
file_compressed_size: 1024
|
||||||
|
content_encoding: "application/zip"
|
||||||
|
artifact_path: "artifact-795-2.zip"
|
||||||
|
artifact_name: "artifact-795-2"
|
||||||
|
status: 2
|
||||||
|
created_unix: 1730330775
|
||||||
|
updated_unix: 1730330775
|
||||||
|
expired_unix: 1738106775
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||||
event: "push"
|
event: "push"
|
||||||
is_fork_pull_request: 0
|
is_fork_pull_request: 0
|
||||||
status: 1
|
status: 6 # running
|
||||||
started: 1683636528
|
started: 1683636528
|
||||||
stopped: 1683636626
|
stopped: 1683636626
|
||||||
created: 1683636108
|
created: 1683636108
|
||||||
|
@ -74,3 +74,23 @@
|
||||||
updated: 1683636626
|
updated: 1683636626
|
||||||
need_approval: 0
|
need_approval: 0
|
||||||
approved_by: 0
|
approved_by: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 795
|
||||||
|
title: "to be deleted (test)"
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
workflow_id: "test.yaml"
|
||||||
|
index: 191
|
||||||
|
trigger_user_id: 1
|
||||||
|
ref: "refs/heads/test"
|
||||||
|
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||||
|
event: "push"
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
status: 2
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
created: 1683636108
|
||||||
|
updated: 1683636626
|
||||||
|
need_approval: 0
|
||||||
|
approved_by: 0
|
||||||
|
|
|
@ -69,3 +69,33 @@
|
||||||
status: 5
|
status: 5
|
||||||
started: 1683636528
|
started: 1683636528
|
||||||
stopped: 1683636626
|
stopped: 1683636626
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 198
|
||||||
|
run_id: 795
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
name: job_1
|
||||||
|
attempt: 1
|
||||||
|
job_id: job_1
|
||||||
|
task_id: 53
|
||||||
|
status: 1
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 199
|
||||||
|
run_id: 795
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
name: job_2
|
||||||
|
attempt: 1
|
||||||
|
job_id: job_2
|
||||||
|
task_id: 54
|
||||||
|
status: 2
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
|
|
@ -38,3 +38,14 @@
|
||||||
repo_id: 0
|
repo_id: 0
|
||||||
description: "This runner is going to be deleted"
|
description: "This runner is going to be deleted"
|
||||||
agent_labels: '["runner_to_be_deleted","linux"]'
|
agent_labels: '["runner_to_be_deleted","linux"]'
|
||||||
|
-
|
||||||
|
id: 34350
|
||||||
|
name: runner_to_be_deleted-org-ephemeral
|
||||||
|
uuid: 3FF231BD-FBB7-4E4B-9602-E6F28363EF20
|
||||||
|
token_hash: 3FF231BD-FBB7-4E4B-9602-E6F28363EF20
|
||||||
|
ephemeral: true
|
||||||
|
version: "1.0.0"
|
||||||
|
owner_id: 3
|
||||||
|
repo_id: 0
|
||||||
|
description: "This runner is going to be deleted"
|
||||||
|
agent_labels: '["runner_to_be_deleted","linux"]'
|
||||||
|
|
|
@ -117,3 +117,63 @@
|
||||||
log_length: 707
|
log_length: 707
|
||||||
log_size: 90179
|
log_size: 90179
|
||||||
log_expired: 0
|
log_expired: 0
|
||||||
|
-
|
||||||
|
id: 52
|
||||||
|
job_id: 196
|
||||||
|
attempt: 1
|
||||||
|
runner_id: 34350
|
||||||
|
status: 6 # running
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
repo_id: 4
|
||||||
|
owner_id: 1
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
token_hash: f8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784222
|
||||||
|
token_salt: ffffffffff
|
||||||
|
token_last_eight: ffffffff
|
||||||
|
log_filename: artifact-test2/2f/47.log
|
||||||
|
log_in_storage: 1
|
||||||
|
log_length: 707
|
||||||
|
log_size: 90179
|
||||||
|
log_expired: 0
|
||||||
|
-
|
||||||
|
id: 53
|
||||||
|
job_id: 198
|
||||||
|
attempt: 1
|
||||||
|
runner_id: 1
|
||||||
|
status: 1
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784223
|
||||||
|
token_salt: ffffffffff
|
||||||
|
token_last_eight: ffffffff
|
||||||
|
log_filename: artifact-test2/2f/47.log
|
||||||
|
log_in_storage: 1
|
||||||
|
log_length: 0
|
||||||
|
log_size: 0
|
||||||
|
log_expired: 0
|
||||||
|
-
|
||||||
|
id: 54
|
||||||
|
job_id: 199
|
||||||
|
attempt: 1
|
||||||
|
runner_id: 1
|
||||||
|
status: 2
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784224
|
||||||
|
token_salt: ffffffffff
|
||||||
|
token_last_eight: ffffffff
|
||||||
|
log_filename: artifact-test2/2f/47.log
|
||||||
|
log_in_storage: 1
|
||||||
|
log_length: 0
|
||||||
|
log_size: 0
|
||||||
|
log_expired: 0
|
||||||
|
|
|
@ -14,5 +14,8 @@ func AddIndexToActionTaskStoppedLogExpired(x *xorm.Engine) error {
|
||||||
Stopped timeutil.TimeStamp `xorm:"index(stopped_log_expired)"`
|
Stopped timeutil.TimeStamp `xorm:"index(stopped_log_expired)"`
|
||||||
LogExpired bool `xorm:"index(stopped_log_expired)"`
|
LogExpired bool `xorm:"index(stopped_log_expired)"`
|
||||||
}
|
}
|
||||||
return x.Sync(new(ActionTask))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreDropIndices: true,
|
||||||
|
}, new(ActionTask))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_23 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/migrations/base"
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_AddIndexToActionTaskStoppedLogExpired(t *testing.T) {
|
||||||
|
type ActionTask struct {
|
||||||
|
ID int64
|
||||||
|
JobID int64
|
||||||
|
Attempt int64
|
||||||
|
RunnerID int64 `xorm:"index"`
|
||||||
|
Status int `xorm:"index"`
|
||||||
|
Started timeutil.TimeStamp `xorm:"index"`
|
||||||
|
Stopped timeutil.TimeStamp `xorm:"index(stopped_log_expired)"`
|
||||||
|
|
||||||
|
RepoID int64 `xorm:"index"`
|
||||||
|
OwnerID int64 `xorm:"index"`
|
||||||
|
CommitSHA string `xorm:"index"`
|
||||||
|
IsForkPullRequest bool
|
||||||
|
|
||||||
|
Token string `xorm:"-"`
|
||||||
|
TokenHash string `xorm:"UNIQUE"` // sha256 of token
|
||||||
|
TokenSalt string
|
||||||
|
TokenLastEight string `xorm:"index token_last_eight"`
|
||||||
|
|
||||||
|
LogFilename string // file name of log
|
||||||
|
LogInStorage bool // read log from database or from storage
|
||||||
|
LogLength int64 // lines count
|
||||||
|
LogSize int64 // blob size
|
||||||
|
LogIndexes []int64 `xorm:"LONGBLOB"` // line number to offset
|
||||||
|
LogExpired bool `xorm:"index(stopped_log_expired)"` // files that are too old will be deleted
|
||||||
|
|
||||||
|
Created timeutil.TimeStamp `xorm:"created"`
|
||||||
|
Updated timeutil.TimeStamp `xorm:"updated index"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare and load the testing database
|
||||||
|
x, deferable := base.PrepareTestEnv(t, 0, new(ActionTask))
|
||||||
|
defer deferable()
|
||||||
|
|
||||||
|
assert.NoError(t, AddIndexToActionTaskStoppedLogExpired(x))
|
||||||
|
}
|
|
@ -9,5 +9,8 @@ func AddIndexForReleaseSha1(x *xorm.Engine) error {
|
||||||
type Release struct {
|
type Release struct {
|
||||||
Sha1 string `xorm:"INDEX VARCHAR(64)"`
|
Sha1 string `xorm:"INDEX VARCHAR(64)"`
|
||||||
}
|
}
|
||||||
return x.Sync(new(Release))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreDropIndices: true,
|
||||||
|
}, new(Release))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_23 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/migrations/base"
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_AddIndexForReleaseSha1(t *testing.T) {
|
||||||
|
type Release struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
RepoID int64 `xorm:"INDEX UNIQUE(n)"`
|
||||||
|
PublisherID int64 `xorm:"INDEX"`
|
||||||
|
TagName string `xorm:"INDEX UNIQUE(n)"`
|
||||||
|
OriginalAuthor string
|
||||||
|
OriginalAuthorID int64 `xorm:"index"`
|
||||||
|
LowerTagName string
|
||||||
|
Target string
|
||||||
|
Title string
|
||||||
|
Sha1 string `xorm:"VARCHAR(64)"`
|
||||||
|
NumCommits int64
|
||||||
|
Note string `xorm:"TEXT"`
|
||||||
|
IsDraft bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
IsTag bool `xorm:"NOT NULL DEFAULT false"` // will be true only if the record is a tag and has no related releases
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare and load the testing database
|
||||||
|
x, deferable := base.PrepareTestEnv(t, 0, new(Release))
|
||||||
|
defer deferable()
|
||||||
|
|
||||||
|
assert.NoError(t, AddIndexForReleaseSha1(x))
|
||||||
|
}
|
|
@ -334,7 +334,7 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
|
||||||
testSuccess := func(userID int64, expectedRepoIDs []int64) {
|
testSuccess := func(userID int64, expectedRepoIDs []int64) {
|
||||||
env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID)
|
env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
repoIDs, err := env.RepoIDs(db.DefaultContext, 1, 100)
|
repoIDs, err := env.RepoIDs(db.DefaultContext)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expectedRepoIDs, repoIDs)
|
assert.Equal(t, expectedRepoIDs, repoIDs)
|
||||||
}
|
}
|
||||||
|
@ -342,25 +342,6 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
|
||||||
testSuccess(4, []int64{3, 32})
|
testSuccess(4, []int64{3, 32})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccessibleReposEnv_Repos(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
|
||||||
testSuccess := func(userID int64, expectedRepoIDs []int64) {
|
|
||||||
env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
repos, err := env.Repos(db.DefaultContext, 1, 100)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
expectedRepos := make(repo_model.RepositoryList, len(expectedRepoIDs))
|
|
||||||
for i, repoID := range expectedRepoIDs {
|
|
||||||
expectedRepos[i] = unittest.AssertExistsAndLoadBean(t,
|
|
||||||
&repo_model.Repository{ID: repoID})
|
|
||||||
}
|
|
||||||
assert.Equal(t, expectedRepos, repos)
|
|
||||||
}
|
|
||||||
testSuccess(2, []int64{3, 5, 32})
|
|
||||||
testSuccess(4, []int64{3, 32})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
|
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||||
|
|
|
@ -48,8 +48,7 @@ func GetTeamRepositories(ctx context.Context, opts *SearchTeamRepoOptions) (Repo
|
||||||
// accessible to a particular user
|
// accessible to a particular user
|
||||||
type AccessibleReposEnvironment interface {
|
type AccessibleReposEnvironment interface {
|
||||||
CountRepos(ctx context.Context) (int64, error)
|
CountRepos(ctx context.Context) (int64, error)
|
||||||
RepoIDs(ctx context.Context, page, pageSize int) ([]int64, error)
|
RepoIDs(ctx context.Context) ([]int64, error)
|
||||||
Repos(ctx context.Context, page, pageSize int) (RepositoryList, error)
|
|
||||||
MirrorRepos(ctx context.Context) (RepositoryList, error)
|
MirrorRepos(ctx context.Context) (RepositoryList, error)
|
||||||
AddKeyword(keyword string)
|
AddKeyword(keyword string)
|
||||||
SetSort(db.SearchOrderBy)
|
SetSort(db.SearchOrderBy)
|
||||||
|
@ -132,40 +131,18 @@ func (env *accessibleReposEnv) CountRepos(ctx context.Context) (int64, error) {
|
||||||
return repoCount, nil
|
return repoCount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (env *accessibleReposEnv) RepoIDs(ctx context.Context, page, pageSize int) ([]int64, error) {
|
func (env *accessibleReposEnv) RepoIDs(ctx context.Context) ([]int64, error) {
|
||||||
if page <= 0 {
|
var repoIDs []int64
|
||||||
page = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
repoIDs := make([]int64, 0, pageSize)
|
|
||||||
return repoIDs, db.GetEngine(ctx).
|
return repoIDs, db.GetEngine(ctx).
|
||||||
Table("repository").
|
Table("repository").
|
||||||
Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id").
|
Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id").
|
||||||
Where(env.cond()).
|
Where(env.cond()).
|
||||||
GroupBy("`repository`.id,`repository`." + strings.Fields(string(env.orderBy))[0]).
|
GroupBy("`repository`.id,`repository`." + strings.Fields(string(env.orderBy))[0]).
|
||||||
OrderBy(string(env.orderBy)).
|
OrderBy(string(env.orderBy)).
|
||||||
Limit(pageSize, (page-1)*pageSize).
|
|
||||||
Cols("`repository`.id").
|
Cols("`repository`.id").
|
||||||
Find(&repoIDs)
|
Find(&repoIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (env *accessibleReposEnv) Repos(ctx context.Context, page, pageSize int) (RepositoryList, error) {
|
|
||||||
repoIDs, err := env.RepoIDs(ctx, page, pageSize)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetUserRepositoryIDs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
repos := make([]*Repository, 0, len(repoIDs))
|
|
||||||
if len(repoIDs) == 0 {
|
|
||||||
return repos, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return repos, db.GetEngine(ctx).
|
|
||||||
In("`repository`.id", repoIDs).
|
|
||||||
OrderBy(string(env.orderBy)).
|
|
||||||
Find(&repos)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (env *accessibleReposEnv) MirrorRepoIDs(ctx context.Context) ([]int64, error) {
|
func (env *accessibleReposEnv) MirrorRepoIDs(ctx context.Context) ([]int64, error) {
|
||||||
repoIDs := make([]int64, 0, 10)
|
repoIDs := make([]int64, 0, 10)
|
||||||
return repoIDs, db.GetEngine(ctx).
|
return repoIDs, db.GetEngine(ctx).
|
||||||
|
|
|
@ -249,7 +249,7 @@ func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_m
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.Status = RepositoryPendingTransfer
|
repo.Status = RepositoryPendingTransfer
|
||||||
if err := UpdateRepositoryCols(ctx, repo, "status"); err != nil {
|
if err := UpdateRepositoryColsNoAutoTime(ctx, repo, "status"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ func UpdateRepositoryOwnerNames(ctx context.Context, ownerID int64, ownerName st
|
||||||
}
|
}
|
||||||
defer committer.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
if _, err := db.GetEngine(ctx).Where("owner_id = ?", ownerID).Cols("owner_name").Update(&Repository{
|
if _, err := db.GetEngine(ctx).Where("owner_id = ?", ownerID).Cols("owner_name").NoAutoTime().Update(&Repository{
|
||||||
OwnerName: ownerName,
|
OwnerName: ownerName,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -40,8 +40,8 @@ func UpdateRepositoryUpdatedTime(ctx context.Context, repoID int64, updateTime t
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRepositoryCols updates repository's columns
|
// UpdateRepositoryColsWithAutoTime updates repository's columns
|
||||||
func UpdateRepositoryCols(ctx context.Context, repo *Repository, cols ...string) error {
|
func UpdateRepositoryColsWithAutoTime(ctx context.Context, repo *Repository, cols ...string) error {
|
||||||
_, err := db.GetEngine(ctx).ID(repo.ID).Cols(cols...).Update(repo)
|
_, err := db.GetEngine(ctx).ID(repo.ID).Cols(cols...).Update(repo)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,6 +166,8 @@ type CommitsCountOptions struct {
|
||||||
Not string
|
Not string
|
||||||
Revision []string
|
Revision []string
|
||||||
RelPath []string
|
RelPath []string
|
||||||
|
Since string
|
||||||
|
Until string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitsCount returns number of total commits of until given revision.
|
// CommitsCount returns number of total commits of until given revision.
|
||||||
|
@ -199,8 +201,8 @@ func (c *Commit) CommitsCount() (int64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
|
// CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
|
||||||
func (c *Commit) CommitsByRange(page, pageSize int, not string) ([]*Commit, error) {
|
func (c *Commit) CommitsByRange(page, pageSize int, not, since, until string) ([]*Commit, error) {
|
||||||
return c.repo.commitsByRange(c.ID, page, pageSize, not)
|
return c.repo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitsBefore returns all the commits before current revision
|
// CommitsBefore returns all the commits before current revision
|
||||||
|
|
|
@ -89,7 +89,8 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
|
||||||
return commits[0], nil
|
return commits[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not string) ([]*Commit, error) {
|
// commitsByRangeWithTime returns the specific page commits before current revision, with not, since, until support
|
||||||
|
func (repo *Repository) commitsByRangeWithTime(id ObjectID, page, pageSize int, not, since, until string) ([]*Commit, error) {
|
||||||
cmd := NewCommand("log").
|
cmd := NewCommand("log").
|
||||||
AddOptionFormat("--skip=%d", (page-1)*pageSize).
|
AddOptionFormat("--skip=%d", (page-1)*pageSize).
|
||||||
AddOptionFormat("--max-count=%d", pageSize).
|
AddOptionFormat("--max-count=%d", pageSize).
|
||||||
|
@ -99,6 +100,12 @@ func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not stri
|
||||||
if not != "" {
|
if not != "" {
|
||||||
cmd.AddOptionValues("--not", not)
|
cmd.AddOptionValues("--not", not)
|
||||||
}
|
}
|
||||||
|
if since != "" {
|
||||||
|
cmd.AddOptionFormat("--since=%s", since)
|
||||||
|
}
|
||||||
|
if until != "" {
|
||||||
|
cmd.AddOptionFormat("--until=%s", until)
|
||||||
|
}
|
||||||
|
|
||||||
stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
|
stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -212,6 +219,8 @@ type CommitsByFileAndRangeOptions struct {
|
||||||
File string
|
File string
|
||||||
Not string
|
Not string
|
||||||
Page int
|
Page int
|
||||||
|
Since string
|
||||||
|
Until string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitsByFileAndRange return the commits according revision file and the page
|
// CommitsByFileAndRange return the commits according revision file and the page
|
||||||
|
@ -231,6 +240,12 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
|
||||||
if opts.Not != "" {
|
if opts.Not != "" {
|
||||||
gitCmd.AddOptionValues("--not", opts.Not)
|
gitCmd.AddOptionValues("--not", opts.Not)
|
||||||
}
|
}
|
||||||
|
if opts.Since != "" {
|
||||||
|
gitCmd.AddOptionFormat("--since=%s", opts.Since)
|
||||||
|
}
|
||||||
|
if opts.Until != "" {
|
||||||
|
gitCmd.AddOptionFormat("--until=%s", opts.Until)
|
||||||
|
}
|
||||||
|
|
||||||
gitCmd.AddDashesAndList(opts.File)
|
gitCmd.AddDashesAndList(opts.File)
|
||||||
err := gitCmd.Run(repo.Ctx, &RunOpts{
|
err := gitCmd.Run(repo.Ctx, &RunOpts{
|
||||||
|
|
|
@ -40,7 +40,9 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
|
||||||
|
|
||||||
since := fromTime.Format(time.RFC3339)
|
since := fromTime.Format(time.RFC3339)
|
||||||
|
|
||||||
stdout, _, runErr := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").AddOptionFormat("--since='%s'", since).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
|
stdout, _, runErr := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").
|
||||||
|
AddOptionFormat("--since=%s", since).
|
||||||
|
RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
|
||||||
if runErr != nil {
|
if runErr != nil {
|
||||||
return nil, runErr
|
return nil, runErr
|
||||||
}
|
}
|
||||||
|
@ -60,7 +62,8 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
|
||||||
_ = stdoutWriter.Close()
|
_ = stdoutWriter.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
gitCmd := NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").AddOptionFormat("--since='%s'", since)
|
gitCmd := NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").
|
||||||
|
AddOptionFormat("--since=%s", since)
|
||||||
if len(branch) == 0 {
|
if len(branch) == 0 {
|
||||||
gitCmd.AddArguments("--branches=*")
|
gitCmd.AddArguments("--branches=*")
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -200,5 +200,3 @@ func TestListToPushCommits(t *testing.T) {
|
||||||
assert.Equal(t, now, pushCommits.Commits[1].Timestamp)
|
assert.Equal(t, now, pushCommits.Commits[1].Timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO TestPushUpdate
|
|
||||||
|
|
|
@ -101,6 +101,8 @@ type Repository struct {
|
||||||
AllowSquash bool `json:"allow_squash_merge"`
|
AllowSquash bool `json:"allow_squash_merge"`
|
||||||
AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge"`
|
AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge"`
|
||||||
AllowRebaseUpdate bool `json:"allow_rebase_update"`
|
AllowRebaseUpdate bool `json:"allow_rebase_update"`
|
||||||
|
AllowManualMerge bool `json:"allow_manual_merge"`
|
||||||
|
AutodetectManualMerge bool `json:"autodetect_manual_merge"`
|
||||||
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"`
|
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"`
|
||||||
DefaultMergeStyle string `json:"default_merge_style"`
|
DefaultMergeStyle string `json:"default_merge_style"`
|
||||||
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"`
|
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"`
|
||||||
|
|
|
@ -66,6 +66,7 @@ Piotr Orzechowski <piotr AT orzechowski DOT tech>
|
||||||
Richard Bukovansky <richard DOT bukovansky AT gmail DOT com>
|
Richard Bukovansky <richard DOT bukovansky AT gmail DOT com>
|
||||||
Robert Nuske <robert DOT nuske AT web DOT de>
|
Robert Nuske <robert DOT nuske AT web DOT de>
|
||||||
Robin Hübner <profan AT prfn DOT se>
|
Robin Hübner <profan AT prfn DOT se>
|
||||||
|
Ryo Hanafusa <ryo7gumi AT gmail DOT com>
|
||||||
SeongJae Park <sj38 DOT park AT gmail DOT com>
|
SeongJae Park <sj38 DOT park AT gmail DOT com>
|
||||||
Thiago Avelino <thiago AT avelino DOT xxx>
|
Thiago Avelino <thiago AT avelino DOT xxx>
|
||||||
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
|
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
|
||||||
|
|
|
@ -1228,6 +1228,7 @@ migrate.migrating_issues = Migrating Issues
|
||||||
migrate.migrating_pulls = Migrating Pull Requests
|
migrate.migrating_pulls = Migrating Pull Requests
|
||||||
migrate.cancel_migrating_title = Cancel Migration
|
migrate.cancel_migrating_title = Cancel Migration
|
||||||
migrate.cancel_migrating_confirm = Do you want to cancel this migration?
|
migrate.cancel_migrating_confirm = Do you want to cancel this migration?
|
||||||
|
migrating_status = Migrating status
|
||||||
|
|
||||||
mirror_from = mirror of
|
mirror_from = mirror of
|
||||||
forked_from = forked from
|
forked_from = forked from
|
||||||
|
@ -3811,6 +3812,9 @@ runs.no_workflows.documentation = For more information on Gitea Actions, see <a
|
||||||
runs.no_runs = The workflow has no runs yet.
|
runs.no_runs = The workflow has no runs yet.
|
||||||
runs.empty_commit_message = (empty commit message)
|
runs.empty_commit_message = (empty commit message)
|
||||||
runs.expire_log_message = Logs have been purged because they were too old.
|
runs.expire_log_message = Logs have been purged because they were too old.
|
||||||
|
runs.delete = Delete workflow run
|
||||||
|
runs.delete.description = Are you sure you want to permanently delete this workflow run? This action cannot be undone.
|
||||||
|
runs.not_done = This workflow run is not done.
|
||||||
|
|
||||||
workflow.disable = Disable Workflow
|
workflow.disable = Disable Workflow
|
||||||
workflow.disable_success = Workflow '%s' disabled successfully.
|
workflow.disable_success = Workflow '%s' disabled successfully.
|
||||||
|
|
|
@ -3728,7 +3728,11 @@ creation.name_placeholder=Caractères alphanumériques ou tirets bas uniquement,
|
||||||
creation.value_placeholder=Entrez n’importe quoi. Les blancs cernant seront taillés.
|
creation.value_placeholder=Entrez n’importe quoi. Les blancs cernant seront taillés.
|
||||||
creation.description_placeholder=Décrire brièvement votre dépôt (optionnel).
|
creation.description_placeholder=Décrire brièvement votre dépôt (optionnel).
|
||||||
|
|
||||||
|
save_success=Le secret « %s » a été enregistré.
|
||||||
|
save_failed=Impossible d’enregistrer le secret.
|
||||||
|
|
||||||
|
add_secret=Ajouter un secret
|
||||||
|
edit_secret=Modifier le secret
|
||||||
deletion=Supprimer le secret
|
deletion=Supprimer le secret
|
||||||
deletion.description=La suppression d'un secret est permanente et irréversible. Continuer ?
|
deletion.description=La suppression d'un secret est permanente et irréversible. Continuer ?
|
||||||
deletion.success=Le secret a été supprimé.
|
deletion.success=Le secret a été supprimé.
|
||||||
|
@ -3806,6 +3810,9 @@ runs.no_workflows.documentation=Pour plus d’informations sur les actions Gitea
|
||||||
runs.no_runs=Le flux de travail n'a pas encore d'exécution.
|
runs.no_runs=Le flux de travail n'a pas encore d'exécution.
|
||||||
runs.empty_commit_message=(message de révision vide)
|
runs.empty_commit_message=(message de révision vide)
|
||||||
runs.expire_log_message=Les journaux ont été supprimés car ils étaient trop anciens.
|
runs.expire_log_message=Les journaux ont été supprimés car ils étaient trop anciens.
|
||||||
|
runs.delete=Supprimer cette exécution
|
||||||
|
runs.delete.description=Êtes-vous sûr de vouloir supprimer définitivement cette exécution ? Cette action ne peut pas être annulée.
|
||||||
|
runs.not_done=Cette exécution du flux de travail n’est pas terminée.
|
||||||
|
|
||||||
workflow.disable=Désactiver le flux de travail
|
workflow.disable=Désactiver le flux de travail
|
||||||
workflow.disable_success=Le flux de travail « %s » a bien été désactivé.
|
workflow.disable_success=Le flux de travail « %s » a bien été désactivé.
|
||||||
|
|
|
@ -3728,7 +3728,11 @@ creation.name_placeholder=carachtair alfanumair nó íoslaghda amháin nach féi
|
||||||
creation.value_placeholder=Ionchur ábhar ar bith. Fágfar spás bán ag tús agus ag deireadh ar lár.
|
creation.value_placeholder=Ionchur ábhar ar bith. Fágfar spás bán ag tús agus ag deireadh ar lár.
|
||||||
creation.description_placeholder=Cuir isteach cur síos gairid (roghnach).
|
creation.description_placeholder=Cuir isteach cur síos gairid (roghnach).
|
||||||
|
|
||||||
|
save_success=Tá an rún "%s" sábháilte.
|
||||||
|
save_failed=Theip ar an rún a shábháil.
|
||||||
|
|
||||||
|
add_secret=Cuir rún leis
|
||||||
|
edit_secret=Cuir rún in eagar
|
||||||
deletion=Bain rún
|
deletion=Bain rún
|
||||||
deletion.description=Is buan rún a bhaint agus ní féidir é a chealú. Lean ort?
|
deletion.description=Is buan rún a bhaint agus ní féidir é a chealú. Lean ort?
|
||||||
deletion.success=Tá an rún bainte.
|
deletion.success=Tá an rún bainte.
|
||||||
|
@ -3806,6 +3810,9 @@ runs.no_workflows.documentation=Le haghaidh tuilleadh eolais ar Gitea Actions, f
|
||||||
runs.no_runs=Níl aon rith ag an sreabhadh oibre fós.
|
runs.no_runs=Níl aon rith ag an sreabhadh oibre fós.
|
||||||
runs.empty_commit_message=(teachtaireacht tiomantas folamh)
|
runs.empty_commit_message=(teachtaireacht tiomantas folamh)
|
||||||
runs.expire_log_message=Glanadh logaí toisc go raibh siad ró-sean.
|
runs.expire_log_message=Glanadh logaí toisc go raibh siad ró-sean.
|
||||||
|
runs.delete=Scrios rith sreabha oibre
|
||||||
|
runs.delete.description=An bhfuil tú cinnte gur mian leat an rith sreabha oibre seo a scriosadh go buan? Ní féidir an gníomh seo a chealú.
|
||||||
|
runs.not_done=Níl an rith sreabha oibre seo críochnaithe.
|
||||||
|
|
||||||
workflow.disable=Díchumasaigh sreabhadh oibre
|
workflow.disable=Díchumasaigh sreabhadh oibre
|
||||||
workflow.disable_success=D'éirigh le sreabhadh oibre '%s' a dhíchumasú.
|
workflow.disable_success=D'éirigh le sreabhadh oibre '%s' a dhíchumasú.
|
||||||
|
|
|
@ -450,6 +450,7 @@ use_scratch_code=スクラッチコードを使う
|
||||||
twofa_scratch_used=あなたはスクラッチコードを使用しました。 2要素認証の設定ページにリダイレクトしましたので、デバイスの登録を解除するか、新しいスクラッチコードを生成しましょう。
|
twofa_scratch_used=あなたはスクラッチコードを使用しました。 2要素認証の設定ページにリダイレクトしましたので、デバイスの登録を解除するか、新しいスクラッチコードを生成しましょう。
|
||||||
twofa_passcode_incorrect=パスコードが正しくありません。デバイスを紛失した場合は、スクラッチコードを使ってサインインしてください。
|
twofa_passcode_incorrect=パスコードが正しくありません。デバイスを紛失した場合は、スクラッチコードを使ってサインインしてください。
|
||||||
twofa_scratch_token_incorrect=スクラッチコードが正しくありません。
|
twofa_scratch_token_incorrect=スクラッチコードが正しくありません。
|
||||||
|
twofa_required=リポジトリにアクセスするには2段階認証を設定するか、再度ログインしてください。
|
||||||
login_userpass=サインイン
|
login_userpass=サインイン
|
||||||
login_openid=OpenID
|
login_openid=OpenID
|
||||||
oauth_signup_tab=新規アカウント登録
|
oauth_signup_tab=新規アカウント登録
|
||||||
|
@ -1878,6 +1879,7 @@ pulls.add_prefix=先頭に <strong>%s</strong> を追加
|
||||||
pulls.remove_prefix=先頭の <strong>%s</strong> を除去
|
pulls.remove_prefix=先頭の <strong>%s</strong> を除去
|
||||||
pulls.data_broken=このプルリクエストは、フォークの情報が見つからないため壊れています。
|
pulls.data_broken=このプルリクエストは、フォークの情報が見つからないため壊れています。
|
||||||
pulls.files_conflicted=このプルリクエストは、ターゲットブランチと競合する変更を含んでいます。
|
pulls.files_conflicted=このプルリクエストは、ターゲットブランチと競合する変更を含んでいます。
|
||||||
|
pulls.is_checking=マージの競合を確認しています...
|
||||||
pulls.is_ancestor=このブランチは既にターゲットブランチに含まれています。マージするものはありません。
|
pulls.is_ancestor=このブランチは既にターゲットブランチに含まれています。マージするものはありません。
|
||||||
pulls.is_empty=このブランチの変更は既にターゲットブランチにあります。これは空のコミットになります。
|
pulls.is_empty=このブランチの変更は既にターゲットブランチにあります。これは空のコミットになります。
|
||||||
pulls.required_status_check_failed=いくつかの必要なステータスチェックが成功していません。
|
pulls.required_status_check_failed=いくつかの必要なステータスチェックが成功していません。
|
||||||
|
@ -3725,7 +3727,11 @@ creation.name_placeholder=大文字小文字の区別なし、英数字とアン
|
||||||
creation.value_placeholder=内容を入力してください。前後の空白は除去されます。
|
creation.value_placeholder=内容を入力してください。前後の空白は除去されます。
|
||||||
creation.description_placeholder=簡単な説明を入力してください。 (オプション)
|
creation.description_placeholder=簡単な説明を入力してください。 (オプション)
|
||||||
|
|
||||||
|
save_success=シークレット "%s" を保存しました。
|
||||||
|
save_failed=シークレットの保存に失敗しました。
|
||||||
|
|
||||||
|
add_secret=シークレットを追加
|
||||||
|
edit_secret=シークレットを編集
|
||||||
deletion=シークレットの削除
|
deletion=シークレットの削除
|
||||||
deletion.description=シークレットの削除は恒久的で元に戻すことはできません。 続行しますか?
|
deletion.description=シークレットの削除は恒久的で元に戻すことはできません。 続行しますか?
|
||||||
deletion.success=シークレットを削除しました。
|
deletion.success=シークレットを削除しました。
|
||||||
|
@ -3842,6 +3848,8 @@ deleted.display_name=削除されたプロジェクト
|
||||||
type-1.display_name=個人プロジェクト
|
type-1.display_name=個人プロジェクト
|
||||||
type-2.display_name=リポジトリ プロジェクト
|
type-2.display_name=リポジトリ プロジェクト
|
||||||
type-3.display_name=組織プロジェクト
|
type-3.display_name=組織プロジェクト
|
||||||
|
enter_fullscreen=フルスクリーン
|
||||||
|
exit_fullscreen=フルスクリーンを終了
|
||||||
|
|
||||||
[git.filemode]
|
[git.filemode]
|
||||||
changed_filemode=%[1]s → %[2]s
|
changed_filemode=%[1]s → %[2]s
|
||||||
|
|
|
@ -3810,6 +3810,9 @@ runs.no_workflows.documentation=Para mais informação sobre o Gitea Actions vej
|
||||||
runs.no_runs=A sequência de trabalho ainda não foi executada.
|
runs.no_runs=A sequência de trabalho ainda não foi executada.
|
||||||
runs.empty_commit_message=(mensagem de cometimento vazia)
|
runs.empty_commit_message=(mensagem de cometimento vazia)
|
||||||
runs.expire_log_message=Os registros foram removidos porque eram muito antigos.
|
runs.expire_log_message=Os registros foram removidos porque eram muito antigos.
|
||||||
|
runs.delete=Eliminar execução da sequência de trabalho
|
||||||
|
runs.delete.description=Tem a certeza que pretende eliminar permanentemente a execução desta sequência de trabalho? Esta operação não poderá ser desfeita.
|
||||||
|
runs.not_done=A execução desta sequência de trabalho ainda não terminou.
|
||||||
|
|
||||||
workflow.disable=Desabilitar sequência de trabalho
|
workflow.disable=Desabilitar sequência de trabalho
|
||||||
workflow.disable_success=A sequência de trabalho '%s' foi desabilitada com sucesso.
|
workflow.disable_success=A sequência de trabalho '%s' foi desabilitada com sucesso.
|
||||||
|
|
|
@ -113,9 +113,11 @@ copy_type_unsupported=Bu dosya türü kopyalanamaz
|
||||||
write=Yaz
|
write=Yaz
|
||||||
preview=Önizleme
|
preview=Önizleme
|
||||||
loading=Yükleniyor…
|
loading=Yükleniyor…
|
||||||
|
files=Dosyalar
|
||||||
|
|
||||||
error=Hata
|
error=Hata
|
||||||
error404=Ulaşmaya çalıştığınız sayfa <strong>mevcut değil</strong> veya <strong>görüntüleme yetkiniz yok</strong>.
|
error404=Ulaşmaya çalıştığınız sayfa <strong>mevcut değil</strong> veya <strong>görüntüleme yetkiniz yok</strong>.
|
||||||
|
error503=Sunucu isteğinizi gerçekleştiremedi. Lütfen daha sonra tekrar deneyin.
|
||||||
go_back=Geri Git
|
go_back=Geri Git
|
||||||
invalid_data=Geçersiz veri: %v
|
invalid_data=Geçersiz veri: %v
|
||||||
|
|
||||||
|
@ -128,6 +130,7 @@ pin=Sabitle
|
||||||
unpin=Sabitlemeyi kaldır
|
unpin=Sabitlemeyi kaldır
|
||||||
|
|
||||||
artifacts=Yapılar
|
artifacts=Yapılar
|
||||||
|
expired=Süresi doldu
|
||||||
confirm_delete_artifact=%s yapısını silmek istediğinizden emin misiniz?
|
confirm_delete_artifact=%s yapısını silmek istediğinizden emin misiniz?
|
||||||
|
|
||||||
archived=Arşivlenmiş
|
archived=Arşivlenmiş
|
||||||
|
@ -169,6 +172,10 @@ search=Ara...
|
||||||
type_tooltip=Arama türü
|
type_tooltip=Arama türü
|
||||||
fuzzy=Bulanık
|
fuzzy=Bulanık
|
||||||
fuzzy_tooltip=Arama terimine benzeyen sonuçları da içer
|
fuzzy_tooltip=Arama terimine benzeyen sonuçları da içer
|
||||||
|
words=Kelimeler
|
||||||
|
words_tooltip=Sadece arama terimi kelimeleriyle eşleşen sonuçları içer
|
||||||
|
regexp=Regexp
|
||||||
|
regexp_tooltip=Sadece regexp arama terimiyle tamamen eşleşen sonuçları içer
|
||||||
exact=Tam
|
exact=Tam
|
||||||
exact_tooltip=Sadece arama terimiyle tamamen eşleşen sonuçları içer
|
exact_tooltip=Sadece arama terimiyle tamamen eşleşen sonuçları içer
|
||||||
repo_kind=Depoları ara...
|
repo_kind=Depoları ara...
|
||||||
|
@ -235,13 +242,17 @@ network_error=Ağ hatası
|
||||||
[startpage]
|
[startpage]
|
||||||
app_desc=Zahmetsiz, kendi sunucunuzda barındırabileceğiniz Git servisi
|
app_desc=Zahmetsiz, kendi sunucunuzda barındırabileceğiniz Git servisi
|
||||||
install=Kurulumu kolay
|
install=Kurulumu kolay
|
||||||
|
install_desc=Platformunuz için <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-binary">ikili dosyayı çalıştırın</a>, <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea/tree/master/docker">Docker</a> ile yükleyin veya <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-package">paket</a> olarak edinin.
|
||||||
platform=Farklı platformlarda çalışablir
|
platform=Farklı platformlarda çalışablir
|
||||||
|
platform_desc=Gitea <a target="_blank" rel="noopener noreferrer" href="%s">Go</a> ile derleme yapılabilecek her yerde çalışmaktadır: Windows, macOS, Linux, ARM, vb. Hangisini seviyorsanız onu seçin!
|
||||||
lightweight=Hafif
|
lightweight=Hafif
|
||||||
lightweight_desc=Gitea'nın minimal gereksinimleri çok düşüktür ve ucuz bir Raspberry Pi üzerinde çalışabilmektedir. Makine enerjinizden tasarruf edin!
|
lightweight_desc=Gitea'nın minimal gereksinimleri çok düşüktür ve ucuz bir Raspberry Pi üzerinde çalışabilmektedir. Makine enerjinizden tasarruf edin!
|
||||||
license=Açık Kaynak
|
license=Açık Kaynak
|
||||||
|
license_desc=Gidin ve <a target="_blank" rel="noopener noreferrer" href="https://code.gitea.io/gitea">code.gitea.io/gitea</a>'yı edinin! Bu projeyi daha da iyi yapmak için <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea">katkıda bulunarak</a> bize katılın. Katkıda bulunmaktan çekinmeyin!
|
||||||
|
|
||||||
[install]
|
[install]
|
||||||
install=Kurulum
|
install=Kurulum
|
||||||
|
installing_desc=Şimdi kuruluyor, lütfen bekleyin...
|
||||||
title=Başlangıç Yapılandırması
|
title=Başlangıç Yapılandırması
|
||||||
docker_helper=Eğer Gitea'yı Docker içerisinde çalıştırıyorsanız, lütfen herhangi bir değişiklik yapmadan önce <a target="_blank" rel="noopener noreferrer" href="%s">belgeleri</a> okuyun.
|
docker_helper=Eğer Gitea'yı Docker içerisinde çalıştırıyorsanız, lütfen herhangi bir değişiklik yapmadan önce <a target="_blank" rel="noopener noreferrer" href="%s">belgeleri</a> okuyun.
|
||||||
require_db_desc=Gitea MySQL, PostgreSQL, MSSQL, SQLite3 veya TiDB (MySQL protokolü) gerektirir.
|
require_db_desc=Gitea MySQL, PostgreSQL, MSSQL, SQLite3 veya TiDB (MySQL protokolü) gerektirir.
|
||||||
|
@ -352,6 +363,7 @@ enable_update_checker=Güncelleme Denetleyicisini Etkinleştir
|
||||||
enable_update_checker_helper=Düzenli olarak gitea.io'ya bağlanarak yeni yayınlanan sürümleri denetler.
|
enable_update_checker_helper=Düzenli olarak gitea.io'ya bağlanarak yeni yayınlanan sürümleri denetler.
|
||||||
env_config_keys=Ortam Yapılandırma
|
env_config_keys=Ortam Yapılandırma
|
||||||
env_config_keys_prompt=Aşağıdaki ortam değişkenleri de yapılandırma dosyanıza eklenecektir:
|
env_config_keys_prompt=Aşağıdaki ortam değişkenleri de yapılandırma dosyanıza eklenecektir:
|
||||||
|
config_write_file_prompt=Bu yapılandırma seçenekleri şuraya yazılacak: %s
|
||||||
|
|
||||||
[home]
|
[home]
|
||||||
nav_menu=Gezinti Menüsü
|
nav_menu=Gezinti Menüsü
|
||||||
|
@ -380,6 +392,12 @@ show_only_public=Yalnızca açık olanlar gösteriliyor
|
||||||
|
|
||||||
issues.in_your_repos=Depolarınızda
|
issues.in_your_repos=Depolarınızda
|
||||||
|
|
||||||
|
guide_title=Etkinlik yok
|
||||||
|
guide_desc=Herhangi bir depo veya kullanıcı takip etmiyorsunuz, bu yüzden görüntülenecek bir içerik yok. Aşağıdaki bağlantıları kullanarak ilgi çekici depo ve kullanıcıları keşfedebilirsiniz.
|
||||||
|
explore_repos=Depoları keşfet
|
||||||
|
explore_users=Kullanıcıları keşfet
|
||||||
|
empty_org=Henüz bir organizasyon yok.
|
||||||
|
empty_repo=Henüz bir depo yok.
|
||||||
|
|
||||||
[explore]
|
[explore]
|
||||||
repos=Depolar
|
repos=Depolar
|
||||||
|
@ -433,6 +451,7 @@ use_scratch_code=Bir çizgi kodu kullanınız
|
||||||
twofa_scratch_used=Geçici kodunuzu kullandınız. İki aşamalı ayarlar sayfasına yönlendirildiniz, burada aygıt kaydınızı kaldırabilir veya yeni bir geçici kod oluşturabilirsiniz.
|
twofa_scratch_used=Geçici kodunuzu kullandınız. İki aşamalı ayarlar sayfasına yönlendirildiniz, burada aygıt kaydınızı kaldırabilir veya yeni bir geçici kod oluşturabilirsiniz.
|
||||||
twofa_passcode_incorrect=Şifreniz yanlış. Aygıtınızı yanlış yerleştirdiyseniz, oturum açmak için çizgi kodunuzu kullanın.
|
twofa_passcode_incorrect=Şifreniz yanlış. Aygıtınızı yanlış yerleştirdiyseniz, oturum açmak için çizgi kodunuzu kullanın.
|
||||||
twofa_scratch_token_incorrect=Çizgi kodunuz doğru değildir.
|
twofa_scratch_token_incorrect=Çizgi kodunuz doğru değildir.
|
||||||
|
twofa_required=Depolara erişmek için iki aşama doğrulama kullanmanız veya tekrar oturum açmayı denemeniz gereklidir.
|
||||||
login_userpass=Oturum Aç
|
login_userpass=Oturum Aç
|
||||||
login_openid=Açık Kimlik
|
login_openid=Açık Kimlik
|
||||||
oauth_signup_tab=Yeni Hesap Oluştur
|
oauth_signup_tab=Yeni Hesap Oluştur
|
||||||
|
@ -441,6 +460,7 @@ oauth_signup_submit=Hesabı Tamamla
|
||||||
oauth_signin_tab=Mevcut Hesaba Bağla
|
oauth_signin_tab=Mevcut Hesaba Bağla
|
||||||
oauth_signin_title=Bağlantılı Hesabı Yetkilendirmek için Giriş Yapın
|
oauth_signin_title=Bağlantılı Hesabı Yetkilendirmek için Giriş Yapın
|
||||||
oauth_signin_submit=Hesabı Bağla
|
oauth_signin_submit=Hesabı Bağla
|
||||||
|
oauth.signin.error.general=Yetkilendirme isteğini işlerken bir hata oluştu: %s. Eğer hata devam ederse lütfen site yöneticisiyle bağlantıya geçin.
|
||||||
oauth.signin.error.access_denied=Yetkilendirme isteği reddedildi.
|
oauth.signin.error.access_denied=Yetkilendirme isteği reddedildi.
|
||||||
oauth.signin.error.temporarily_unavailable=Yetkilendirme sunucusu geçici olarak erişilemez olduğu için yetkilendirme başarısız oldu. Lütfen daha sonra tekrar deneyin.
|
oauth.signin.error.temporarily_unavailable=Yetkilendirme sunucusu geçici olarak erişilemez olduğu için yetkilendirme başarısız oldu. Lütfen daha sonra tekrar deneyin.
|
||||||
oauth_callback_unable_auto_reg=Otomatik kayıt etkin ancak OAuth2 Sağlayıcı %[1] eksik sahalar döndürdü: %[2]s, otomatik olarak hesap oluşturulamıyor, lütfen bir hesap oluşturun veya bağlantı verin, veya site yöneticisiyle iletişim kurun.
|
oauth_callback_unable_auto_reg=Otomatik kayıt etkin ancak OAuth2 Sağlayıcı %[1] eksik sahalar döndürdü: %[2]s, otomatik olarak hesap oluşturulamıyor, lütfen bir hesap oluşturun veya bağlantı verin, veya site yöneticisiyle iletişim kurun.
|
||||||
|
@ -457,10 +477,12 @@ authorize_application=Uygulamayı Yetkilendir
|
||||||
authorize_redirect_notice=Bu uygulamayı yetkilendirirseniz %s adresine yönlendirileceksiniz.
|
authorize_redirect_notice=Bu uygulamayı yetkilendirirseniz %s adresine yönlendirileceksiniz.
|
||||||
authorize_application_created_by=Bu uygulama %s tarafından oluşturuldu.
|
authorize_application_created_by=Bu uygulama %s tarafından oluşturuldu.
|
||||||
authorize_application_description=Erişime izin verirseniz, özel depolar ve organizasyonlar da dahil olmak üzere tüm hesap bilgilerinize erişebilir ve yazabilir.
|
authorize_application_description=Erişime izin verirseniz, özel depolar ve organizasyonlar da dahil olmak üzere tüm hesap bilgilerinize erişebilir ve yazabilir.
|
||||||
|
authorize_application_with_scopes=Kapsamlar: %s
|
||||||
authorize_title=Hesabınıza erişmesi için "%s" yetkilendirilsin mi?
|
authorize_title=Hesabınıza erişmesi için "%s" yetkilendirilsin mi?
|
||||||
authorization_failed=Yetkilendirme başarısız oldu
|
authorization_failed=Yetkilendirme başarısız oldu
|
||||||
authorization_failed_desc=Geçersiz bir istek tespit ettiğimiz için yetkilendirme başarısız oldu. Lütfen izin vermeye çalıştığınız uygulamanın sağlayıcısı ile iletişim kurun.
|
authorization_failed_desc=Geçersiz bir istek tespit ettiğimiz için yetkilendirme başarısız oldu. Lütfen izin vermeye çalıştığınız uygulamanın sağlayıcısı ile iletişim kurun.
|
||||||
sspi_auth_failed=SSPI kimlik doğrulaması başarısız oldu
|
sspi_auth_failed=SSPI kimlik doğrulaması başarısız oldu
|
||||||
|
password_pwned=Seçtiğiniz parola, daha önce herkese açık veri ihlallerinde açığa çıkan bir <a target="_blank" rel="noopener noreferrer" href="%s">çalınan parola listesindedir</a>. Lütfen farklı bir parola ile tekrar deneyin ve başka yerlerde de bu parolayı değiştirmeyi düşünün.
|
||||||
password_pwned_err=HaveIBeenPwned'e yapılan istek tamamlanamadı
|
password_pwned_err=HaveIBeenPwned'e yapılan istek tamamlanamadı
|
||||||
last_admin=Son yöneticiyi silemezsiniz. En azından bir yönetici olmalıdır.
|
last_admin=Son yöneticiyi silemezsiniz. En azından bir yönetici olmalıdır.
|
||||||
signin_passkey=Bir parola anahtarı ile oturum aç
|
signin_passkey=Bir parola anahtarı ile oturum aç
|
||||||
|
@ -583,6 +605,8 @@ lang_select_error=Listeden bir dil seçin.
|
||||||
|
|
||||||
username_been_taken=Bu kullanıcı adı daha önce alınmış.
|
username_been_taken=Bu kullanıcı adı daha önce alınmış.
|
||||||
username_change_not_local_user=Yerel olmayan kullanıcılar kendi kullanıcı adlarını değiştiremezler.
|
username_change_not_local_user=Yerel olmayan kullanıcılar kendi kullanıcı adlarını değiştiremezler.
|
||||||
|
change_username_disabled=Kullanıcı adı değişikliği devre dışıdır.
|
||||||
|
change_full_name_disabled=Tam ad değişikliği devre dışıdır.
|
||||||
username_has_not_been_changed=Kullanıcı adı değişmedi
|
username_has_not_been_changed=Kullanıcı adı değişmedi
|
||||||
repo_name_been_taken=Depo adı zaten kullanılıyor.
|
repo_name_been_taken=Depo adı zaten kullanılıyor.
|
||||||
repository_force_private=Gizliyi Zorla devrede: gizli depolar herkese açık yapılamaz.
|
repository_force_private=Gizliyi Zorla devrede: gizli depolar herkese açık yapılamaz.
|
||||||
|
@ -632,6 +656,7 @@ org_still_own_repo=Bu organizasyon hala bir veya daha fazla depoya sahip, önce
|
||||||
org_still_own_packages=Bu organizasyon hala bir veya daha fazla pakete sahip, önce onları silin.
|
org_still_own_packages=Bu organizasyon hala bir veya daha fazla pakete sahip, önce onları silin.
|
||||||
|
|
||||||
target_branch_not_exist=Hedef dal mevcut değil.
|
target_branch_not_exist=Hedef dal mevcut değil.
|
||||||
|
target_ref_not_exist=Hedef referans mevcut değil %s
|
||||||
|
|
||||||
admin_cannot_delete_self=Yöneticiyken kendinizi silemezsiniz. Lütfen önce yönetici haklarınızı kaldırın.
|
admin_cannot_delete_self=Yöneticiyken kendinizi silemezsiniz. Lütfen önce yönetici haklarınızı kaldırın.
|
||||||
|
|
||||||
|
@ -698,14 +723,18 @@ applications=Uygulamalar
|
||||||
orgs=Organizasyonları Yönet
|
orgs=Organizasyonları Yönet
|
||||||
repos=Depolar
|
repos=Depolar
|
||||||
delete=Hesabı Sil
|
delete=Hesabı Sil
|
||||||
|
twofa=İki Aşamalı Kimlik Doğrulama (TOTP)
|
||||||
account_link=Bağlı Hesaplar
|
account_link=Bağlı Hesaplar
|
||||||
organization=Organizasyonlar
|
organization=Organizasyonlar
|
||||||
uid=UID
|
uid=UID
|
||||||
|
webauthn=İki-Aşamalı Kimlik Doğrulama (Güvenlik Anahtarları)
|
||||||
|
|
||||||
public_profile=Herkese Açık Profil
|
public_profile=Herkese Açık Profil
|
||||||
biography_placeholder=Bize kendiniz hakkında birşeyler söyleyin! (Markdown kullanabilirsiniz)
|
biography_placeholder=Bize kendiniz hakkında birşeyler söyleyin! (Markdown kullanabilirsiniz)
|
||||||
location_placeholder=Yaklaşık konumunuzu başkalarıyla paylaşın
|
location_placeholder=Yaklaşık konumunuzu başkalarıyla paylaşın
|
||||||
profile_desc=Profilinizin başkalarına nasıl gösterildiğini yönetin. Ana e-posta adresiniz bildirimler, parola kurtarma ve web tabanlı Git işlemleri için kullanılacaktır.
|
profile_desc=Profilinizin başkalarına nasıl gösterildiğini yönetin. Ana e-posta adresiniz bildirimler, parola kurtarma ve web tabanlı Git işlemleri için kullanılacaktır.
|
||||||
|
password_username_disabled=Yerel olmayan kullanıcılara kullanıcı adlarını değiştirme izni verilmemiştir. Daha fazla bilgi edinmek için lütfen site yöneticisi ile iletişime geçiniz.
|
||||||
|
password_full_name_disabled=Tam adınızı değiştirme izniniz yoktur. Daha fazla bilgi edinmek için lütfen site yöneticisi ile iletişime geçiniz.
|
||||||
full_name=Ad Soyad
|
full_name=Ad Soyad
|
||||||
website=Web Sitesi
|
website=Web Sitesi
|
||||||
location=Konum
|
location=Konum
|
||||||
|
@ -755,6 +784,7 @@ uploaded_avatar_not_a_image=Yüklenen dosya bir resim dosyası değil.
|
||||||
uploaded_avatar_is_too_big=Yüklenen dosyanın boyutu (%d KiB), azami boyutu (%d KiB) aşıyor.
|
uploaded_avatar_is_too_big=Yüklenen dosyanın boyutu (%d KiB), azami boyutu (%d KiB) aşıyor.
|
||||||
update_avatar_success=Profil resminiz değiştirildi.
|
update_avatar_success=Profil resminiz değiştirildi.
|
||||||
update_user_avatar_success=Kullanıcının avatarı güncellendi.
|
update_user_avatar_success=Kullanıcının avatarı güncellendi.
|
||||||
|
cropper_prompt=Kaydetmeden önce resmi düzenleyebilirsiniz. Düzenlenen resim PNG biçiminde kaydedilecektir.
|
||||||
|
|
||||||
change_password=Parolayı Güncelle
|
change_password=Parolayı Güncelle
|
||||||
old_password=Mevcut Parola
|
old_password=Mevcut Parola
|
||||||
|
@ -797,6 +827,7 @@ add_email_success=Yeni e-posta adresi eklendi.
|
||||||
email_preference_set_success=E-posta tercihi başarıyla ayarlandı.
|
email_preference_set_success=E-posta tercihi başarıyla ayarlandı.
|
||||||
add_openid_success=Yeni OpenID adresi eklendi.
|
add_openid_success=Yeni OpenID adresi eklendi.
|
||||||
keep_email_private=E-posta Adresini Gizle
|
keep_email_private=E-posta Adresini Gizle
|
||||||
|
keep_email_private_popup=Bu, e-posta adresinizi profilde, değişiklik isteği yaptığınızda veya web arayüzünde dosya düzenlediğinizde gizleyecektir. İtilen işlemeler değişmeyecektir.
|
||||||
openid_desc=OpenID, kimlik doğrulama işlemini harici bir sağlayıcıya devretmenize olanak sağlar.
|
openid_desc=OpenID, kimlik doğrulama işlemini harici bir sağlayıcıya devretmenize olanak sağlar.
|
||||||
|
|
||||||
manage_ssh_keys=SSH Anahtarlarını Yönet
|
manage_ssh_keys=SSH Anahtarlarını Yönet
|
||||||
|
@ -898,6 +929,9 @@ permission_not_set=Ayarlanmadı
|
||||||
permission_no_access=Erişim Yok
|
permission_no_access=Erişim Yok
|
||||||
permission_read=Okunmuş
|
permission_read=Okunmuş
|
||||||
permission_write=Okuma ve Yazma
|
permission_write=Okuma ve Yazma
|
||||||
|
permission_anonymous_read=Anonim Okuma
|
||||||
|
permission_everyone_read=Herkes Okuyabilir
|
||||||
|
permission_everyone_write=Herkes Yazabilir
|
||||||
access_token_desc=Seçili token izinleri, yetkilendirmeyi ilgili <a %s>API</a> yollarıyla sınırlandıracaktır. Daha fazla bilgi için <a %s>belgeleri</a> okuyun.
|
access_token_desc=Seçili token izinleri, yetkilendirmeyi ilgili <a %s>API</a> yollarıyla sınırlandıracaktır. Daha fazla bilgi için <a %s>belgeleri</a> okuyun.
|
||||||
at_least_one_permission=Bir token oluşturmak için en azından bir izin seçmelisiniz
|
at_least_one_permission=Bir token oluşturmak için en azından bir izin seçmelisiniz
|
||||||
permissions_list=İzinler:
|
permissions_list=İzinler:
|
||||||
|
@ -925,6 +959,7 @@ oauth2_client_secret_hint=Bu sayfadan ayrıldıktan veya yeniledikten sonra gizl
|
||||||
oauth2_application_edit=Düzenle
|
oauth2_application_edit=Düzenle
|
||||||
oauth2_application_create_description=OAuth2 uygulamaları, üçüncü taraf uygulamanıza bu durumda kullanıcı hesaplarına erişim sağlar.
|
oauth2_application_create_description=OAuth2 uygulamaları, üçüncü taraf uygulamanıza bu durumda kullanıcı hesaplarına erişim sağlar.
|
||||||
oauth2_application_remove_description=Bir OAuth2 uygulamasının kaldırılması, bu sunucudaki yetkili kullanıcı hesaplarına erişmesini önler. Devam edilsin mi?
|
oauth2_application_remove_description=Bir OAuth2 uygulamasının kaldırılması, bu sunucudaki yetkili kullanıcı hesaplarına erişmesini önler. Devam edilsin mi?
|
||||||
|
oauth2_application_locked=Gitea kimi OAuth2 uygulamalarının başlangıçta ön kaydını, yapılandırmada etkinleştirilmişse yapabilir. Beklenmeyen davranışı önlemek için bunlar ne düzenlenmeli ne de kaldırılmalı. Daha fazla bilgi için OAuth2 belgesine bakın.
|
||||||
|
|
||||||
authorized_oauth2_applications=Yetkili OAuth2 Uygulamaları
|
authorized_oauth2_applications=Yetkili OAuth2 Uygulamaları
|
||||||
authorized_oauth2_applications_description=Kişisel Gitea hesabınıza bu üçüncü parti uygulamalara erişim izni verdiniz. Lütfen artık ihtiyaç duyulmayan uygulamalara erişimi iptal edin.
|
authorized_oauth2_applications_description=Kişisel Gitea hesabınıza bu üçüncü parti uygulamalara erişim izni verdiniz. Lütfen artık ihtiyaç duyulmayan uygulamalara erişimi iptal edin.
|
||||||
|
@ -933,13 +968,17 @@ revoke_oauth2_grant=Erişimi İptal Et
|
||||||
revoke_oauth2_grant_description=Bu üçüncü taraf uygulamasına erişimin iptal edilmesi bu uygulamanın verilerinize erişmesini önleyecektir. Emin misiniz?
|
revoke_oauth2_grant_description=Bu üçüncü taraf uygulamasına erişimin iptal edilmesi bu uygulamanın verilerinize erişmesini önleyecektir. Emin misiniz?
|
||||||
revoke_oauth2_grant_success=Erişim başarıyla kaldırıldı.
|
revoke_oauth2_grant_success=Erişim başarıyla kaldırıldı.
|
||||||
|
|
||||||
|
twofa_desc=İki aşamalı kimlik doğrulama, hesabınızın güvenliğini artırır.
|
||||||
twofa_recovery_tip=Aygıtınızı kaybetmeniz durumunda, hesabınıza tekrar erişmek için tek kullanımlık kurtarma anahtarını kullanabileceksiniz.
|
twofa_recovery_tip=Aygıtınızı kaybetmeniz durumunda, hesabınıza tekrar erişmek için tek kullanımlık kurtarma anahtarını kullanabileceksiniz.
|
||||||
twofa_is_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde <strong>kaydedilmiş</strong>.
|
twofa_is_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde <strong>kaydedilmiş</strong>.
|
||||||
twofa_not_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde kaydedilmemiş.
|
twofa_not_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde kaydedilmemiş.
|
||||||
twofa_disable=İki Aşamalı Doğrulamayı Devre Dışı Bırak
|
twofa_disable=İki Aşamalı Doğrulamayı Devre Dışı Bırak
|
||||||
|
twofa_scratch_token_regenerate=Geçici Kodu Yeniden Üret
|
||||||
|
twofa_scratch_token_regenerated=Geçici kodunuz şimdi %s. Güvenli bir yerde saklayın, tekrar gösterilmeyecektir.
|
||||||
twofa_enroll=İki Faktörlü Kimlik Doğrulamaya Kaydolun
|
twofa_enroll=İki Faktörlü Kimlik Doğrulamaya Kaydolun
|
||||||
twofa_disable_note=Gerekirse iki faktörlü kimlik doğrulamayı devre dışı bırakabilirsiniz.
|
twofa_disable_note=Gerekirse iki faktörlü kimlik doğrulamayı devre dışı bırakabilirsiniz.
|
||||||
twofa_disable_desc=İki faktörlü kimlik doğrulamayı devre dışı bırakmak hesabınızı daha az güvenli hale getirir. Devam edilsin mi?
|
twofa_disable_desc=İki faktörlü kimlik doğrulamayı devre dışı bırakmak hesabınızı daha az güvenli hale getirir. Devam edilsin mi?
|
||||||
|
regenerate_scratch_token_desc=Geçici kodunuzu kaybettiyseniz veya oturum açmak için kullandıysanız, buradan sıfırlayabilirsiniz.
|
||||||
twofa_disabled=İki faktörlü kimlik doğrulama devre dışı bırakıldı.
|
twofa_disabled=İki faktörlü kimlik doğrulama devre dışı bırakıldı.
|
||||||
scan_this_image=Kim doğrulama uygulamanızla bu görüntüyü tarayın:
|
scan_this_image=Kim doğrulama uygulamanızla bu görüntüyü tarayın:
|
||||||
or_enter_secret=Veya gizli şeyi girin: %s
|
or_enter_secret=Veya gizli şeyi girin: %s
|
||||||
|
@ -993,6 +1032,8 @@ new_repo_helper=Bir depo, sürüm geçmişi dahil tüm proje dosyalarını içer
|
||||||
owner=Sahibi
|
owner=Sahibi
|
||||||
owner_helper=Bazı organizasyonlar, en çok depo sayısı sınırı nedeniyle açılır menüde görünmeyebilir.
|
owner_helper=Bazı organizasyonlar, en çok depo sayısı sınırı nedeniyle açılır menüde görünmeyebilir.
|
||||||
repo_name=Depo İsmi
|
repo_name=Depo İsmi
|
||||||
|
repo_name_profile_public_hint=.profile herkese açık organizasyonunuzun profiline herkesin görüntüleyebileceği bir README.md dosyası eklemek için kullanabileceğiniz özel bir depodur. Başlamak için herkese açık olduğundan ve profile dizininde README ile başladığınızdan emin olun.
|
||||||
|
repo_name_profile_private_hint=.profile-private organizasyonunuzun üye profiline sadece organizasyon üyelerinin görüntüleyebileceği bir README.md eklemek için kullanabileceğiniz özel bir depodur. Başlamak için özel olduğundan ve profil dizininde README ile başladığınızdan emin olun.
|
||||||
repo_size=Depo Boyutu
|
repo_size=Depo Boyutu
|
||||||
template=Şablon
|
template=Şablon
|
||||||
template_select=Bir şablon seçin.
|
template_select=Bir şablon seçin.
|
||||||
|
@ -1011,6 +1052,8 @@ fork_to_different_account=Başka bir hesaba çatalla
|
||||||
fork_visibility_helper=Çatallanmış bir deponun görünürlüğü değiştirilemez.
|
fork_visibility_helper=Çatallanmış bir deponun görünürlüğü değiştirilemez.
|
||||||
fork_branch=Çatala klonlanacak dal
|
fork_branch=Çatala klonlanacak dal
|
||||||
all_branches=Tüm dallar
|
all_branches=Tüm dallar
|
||||||
|
view_all_branches=Tüm dalları görüntüle
|
||||||
|
view_all_tags=Tüm etiketleri görüntüle
|
||||||
fork_no_valid_owners=Geçerli bir sahibi olmadığı için bu depo çatallanamaz.
|
fork_no_valid_owners=Geçerli bir sahibi olmadığı için bu depo çatallanamaz.
|
||||||
fork.blocked_user=Depo çatallanamıyor, depo sahibi tarafından engellenmişsiniz.
|
fork.blocked_user=Depo çatallanamıyor, depo sahibi tarafından engellenmişsiniz.
|
||||||
use_template=Bu şablonu kullan
|
use_template=Bu şablonu kullan
|
||||||
|
@ -1022,6 +1065,8 @@ generate_repo=Depo Oluştur
|
||||||
generate_from=Şuradan Oluştur
|
generate_from=Şuradan Oluştur
|
||||||
repo_desc=Açıklama
|
repo_desc=Açıklama
|
||||||
repo_desc_helper=Kısa açıklama girin (isteğe bağlı)
|
repo_desc_helper=Kısa açıklama girin (isteğe bağlı)
|
||||||
|
repo_no_desc=Hiçbir açıklama sağlanmadı
|
||||||
|
repo_lang=Dil
|
||||||
repo_gitignore_helper=.gitignore şablonlarını seç.
|
repo_gitignore_helper=.gitignore şablonlarını seç.
|
||||||
repo_gitignore_helper_desc=Sık kullanılan diller için bir şablon listesinden hangi dosyaların izlenmeyeceğini seçin. Her dilin oluşturma araçları tarafından oluşturulan tipik yapılar, varsayılan olarak .gitignore dosyasına dahil edilmiştir.
|
repo_gitignore_helper_desc=Sık kullanılan diller için bir şablon listesinden hangi dosyaların izlenmeyeceğini seçin. Her dilin oluşturma araçları tarafından oluşturulan tipik yapılar, varsayılan olarak .gitignore dosyasına dahil edilmiştir.
|
||||||
issue_labels=Konu Etiketleri
|
issue_labels=Konu Etiketleri
|
||||||
|
@ -1029,6 +1074,7 @@ issue_labels_helper=Bir konu etiket seti seçin.
|
||||||
license=Lisans
|
license=Lisans
|
||||||
license_helper=Bir lisans dosyası seçin.
|
license_helper=Bir lisans dosyası seçin.
|
||||||
license_helper_desc=Bir lisans, başkalarının kodunuzla neler yapıp yapamayacağını yönetir. Projeniz için hangisinin doğru olduğundan emin değil misiniz? <a target="_blank" rel="noopener noreferrer" href="%s">Lisans seçme</a> konusuna bakın
|
license_helper_desc=Bir lisans, başkalarının kodunuzla neler yapıp yapamayacağını yönetir. Projeniz için hangisinin doğru olduğundan emin değil misiniz? <a target="_blank" rel="noopener noreferrer" href="%s">Lisans seçme</a> konusuna bakın
|
||||||
|
multiple_licenses=Çoklu Lisans
|
||||||
object_format=Nesne Biçimi
|
object_format=Nesne Biçimi
|
||||||
object_format_helper=Deponun nesne biçimi. Daha sonra değiştirilemez. SHA1 en uyumlu olandır.
|
object_format_helper=Deponun nesne biçimi. Daha sonra değiştirilemez. SHA1 en uyumlu olandır.
|
||||||
readme=README
|
readme=README
|
||||||
|
@ -1082,15 +1128,20 @@ delete_preexisting_success=%s içindeki kabul edilmeyen dosyalar silindi
|
||||||
blame_prior=Bu değişiklikten önceki suçu görüntüle
|
blame_prior=Bu değişiklikten önceki suçu görüntüle
|
||||||
blame.ignore_revs=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılıyor. Bunun yerine normal sorumlu görüntüsü için <a href="%s">buraya tıklayın</a>.
|
blame.ignore_revs=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılıyor. Bunun yerine normal sorumlu görüntüsü için <a href="%s">buraya tıklayın</a>.
|
||||||
blame.ignore_revs.failed=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılamadı.
|
blame.ignore_revs.failed=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılamadı.
|
||||||
|
user_search_tooltip=En fazla 30 kullanıcı görüntüler
|
||||||
|
|
||||||
|
tree_path_not_found=%[1] yolu, %[2]s deposunda mevcut değil
|
||||||
|
|
||||||
transfer.accept=Aktarımı Kabul Et
|
transfer.accept=Aktarımı Kabul Et
|
||||||
|
transfer.accept_desc=`"%s" tarafına aktar`
|
||||||
transfer.reject=Aktarımı Reddet
|
transfer.reject=Aktarımı Reddet
|
||||||
|
transfer.reject_desc=`"%s" tarafına aktarımı iptal et`
|
||||||
transfer.no_permission_to_accept=Bu aktarımı kabul etme izniniz yok.
|
transfer.no_permission_to_accept=Bu aktarımı kabul etme izniniz yok.
|
||||||
transfer.no_permission_to_reject=Bu aktarımı reddetme izniniz yok.
|
transfer.no_permission_to_reject=Bu aktarımı reddetme izniniz yok.
|
||||||
|
|
||||||
desc.private=Özel
|
desc.private=Özel
|
||||||
desc.public=Genel
|
desc.public=Genel
|
||||||
|
desc.public_access=Herkese Açık Erişim
|
||||||
desc.template=Şablon
|
desc.template=Şablon
|
||||||
desc.internal=Dahili
|
desc.internal=Dahili
|
||||||
desc.archived=Arşivlenmiş
|
desc.archived=Arşivlenmiş
|
||||||
|
@ -1160,6 +1211,10 @@ migrate.gogs.description=Notabug.org veya diğer Gogs sunucularından veri aktar
|
||||||
migrate.onedev.description=Code.onedev.io ve diğer OneDev sunucularından veri aktar.
|
migrate.onedev.description=Code.onedev.io ve diğer OneDev sunucularından veri aktar.
|
||||||
migrate.codebase.description=Codebasehq.com sitesinden veri aktar.
|
migrate.codebase.description=Codebasehq.com sitesinden veri aktar.
|
||||||
migrate.gitbucket.description=GitBucket sunucularından veri aktar.
|
migrate.gitbucket.description=GitBucket sunucularından veri aktar.
|
||||||
|
migrate.codecommit.aws_access_key_id=AWS Erişim Anahtarı Kimliği
|
||||||
|
migrate.codecommit.aws_secret_access_key=AWS Gizli Erişim Anahtarı
|
||||||
|
migrate.codecommit.https_git_credentials_username=HTTPS Git Kimliği Kullanıcı Adı
|
||||||
|
migrate.codecommit.https_git_credentials_password=HTTPS Git Kimliği Parolası
|
||||||
migrate.migrating_git=Git Verilerini Taşıma
|
migrate.migrating_git=Git Verilerini Taşıma
|
||||||
migrate.migrating_topics=Konuları Taşıma
|
migrate.migrating_topics=Konuları Taşıma
|
||||||
migrate.migrating_milestones=Kilometre Taşlarını Taşıma
|
migrate.migrating_milestones=Kilometre Taşlarını Taşıma
|
||||||
|
@ -1193,6 +1248,7 @@ create_new_repo_command=Komut satırında yeni bir depo oluşturuluyor
|
||||||
push_exist_repo=Komut satırından mevcut bir depo itiliyor
|
push_exist_repo=Komut satırından mevcut bir depo itiliyor
|
||||||
empty_message=Bu depoda herhangi bir içerik yok.
|
empty_message=Bu depoda herhangi bir içerik yok.
|
||||||
broken_message=Bu deponun altındaki Git verisi okunamıyor. Bu sunucunun yöneticisiyle bağlantıya geçin veya bu depoyu silin.
|
broken_message=Bu deponun altındaki Git verisi okunamıyor. Bu sunucunun yöneticisiyle bağlantıya geçin veya bu depoyu silin.
|
||||||
|
no_branch=Bu deponun hiç bir dalı yok.
|
||||||
|
|
||||||
code=Kod
|
code=Kod
|
||||||
code.desc=Kaynak koda, dosyalara, işlemelere ve dallara eriş.
|
code.desc=Kaynak koda, dosyalara, işlemelere ve dallara eriş.
|
||||||
|
@ -1302,6 +1358,8 @@ editor.new_branch_name_desc=Yeni dal ismi…
|
||||||
editor.cancel=İptal
|
editor.cancel=İptal
|
||||||
editor.filename_cannot_be_empty=Dosya adı boş olamaz.
|
editor.filename_cannot_be_empty=Dosya adı boş olamaz.
|
||||||
editor.filename_is_invalid=Dosya adı geçersiz: "%s".
|
editor.filename_is_invalid=Dosya adı geçersiz: "%s".
|
||||||
|
editor.commit_email=İşleme e-postası
|
||||||
|
editor.invalid_commit_email=İşleme e-postası hatalı.
|
||||||
editor.branch_does_not_exist=Bu depoda "%s" dalı yok.
|
editor.branch_does_not_exist=Bu depoda "%s" dalı yok.
|
||||||
editor.branch_already_exists=Bu depoda "%s" dalı zaten var.
|
editor.branch_already_exists=Bu depoda "%s" dalı zaten var.
|
||||||
editor.directory_is_a_file=Dizin adı "%s" zaten bu depoda bir dosya adı olarak kullanılmaktadır.
|
editor.directory_is_a_file=Dizin adı "%s" zaten bu depoda bir dosya adı olarak kullanılmaktadır.
|
||||||
|
@ -1350,6 +1408,7 @@ commits.signed_by_untrusted_user_unmatched=İşleyici ile eşleşmeyen güvenilm
|
||||||
commits.gpg_key_id=GPG Anahtar Kimliği
|
commits.gpg_key_id=GPG Anahtar Kimliği
|
||||||
commits.ssh_key_fingerprint=SSH Anahtar Parmak İzi
|
commits.ssh_key_fingerprint=SSH Anahtar Parmak İzi
|
||||||
commits.view_path=Geçmişte bu noktayı görüntüle
|
commits.view_path=Geçmişte bu noktayı görüntüle
|
||||||
|
commits.view_file_diff=Bu dosyanın bu işlemedeki değişikliklerini görüntüle
|
||||||
|
|
||||||
commit.operations=İşlemler
|
commit.operations=İşlemler
|
||||||
commit.revert=Geri Al
|
commit.revert=Geri Al
|
||||||
|
@ -1410,6 +1469,8 @@ issues.filter_milestones=Kilometre Taşı Süzgeci
|
||||||
issues.filter_projects=Projeyi Süz
|
issues.filter_projects=Projeyi Süz
|
||||||
issues.filter_labels=Etiket Süzgeci
|
issues.filter_labels=Etiket Süzgeci
|
||||||
issues.filter_reviewers=Gözden Geçiren Süzgeci
|
issues.filter_reviewers=Gözden Geçiren Süzgeci
|
||||||
|
issues.filter_no_results=Sonuç yok
|
||||||
|
issues.filter_no_results_placeholder=Arama filtrelerinizi ayarlamayı deneyin.
|
||||||
issues.new=Yeni Konu
|
issues.new=Yeni Konu
|
||||||
issues.new.title_empty=Başlık boş olamaz
|
issues.new.title_empty=Başlık boş olamaz
|
||||||
issues.new.labels=Etiketler
|
issues.new.labels=Etiketler
|
||||||
|
@ -1427,6 +1488,7 @@ issues.new.clear_milestone=Kilometre Taşlarını Temizle
|
||||||
issues.new.assignees=Atananlar
|
issues.new.assignees=Atananlar
|
||||||
issues.new.clear_assignees=Atamaları Temizle
|
issues.new.clear_assignees=Atamaları Temizle
|
||||||
issues.new.no_assignees=Atanan Kişi Yok
|
issues.new.no_assignees=Atanan Kişi Yok
|
||||||
|
issues.new.no_reviewers=Gözden geçiren yok
|
||||||
issues.new.blocked_user=Konu oluşturulamıyor, depo sahibi tarafından engellenmişsiniz.
|
issues.new.blocked_user=Konu oluşturulamıyor, depo sahibi tarafından engellenmişsiniz.
|
||||||
issues.edit.already_changed=Konuya yapılan değişiklikler kaydedilemiyor. İçerik başka kullanıcı tarafından değiştirilmiş gözüküyor. Diğerlerinin değişikliklerinin üzerine yazmamak için lütfen sayfayı yenileyin ve tekrar düzenlemeye çalışın
|
issues.edit.already_changed=Konuya yapılan değişiklikler kaydedilemiyor. İçerik başka kullanıcı tarafından değiştirilmiş gözüküyor. Diğerlerinin değişikliklerinin üzerine yazmamak için lütfen sayfayı yenileyin ve tekrar düzenlemeye çalışın
|
||||||
issues.edit.blocked_user=İçerik düzenlenemiyor, gönderen veya depo sahibi tarafından engellenmişsiniz.
|
issues.edit.blocked_user=İçerik düzenlenemiyor, gönderen veya depo sahibi tarafından engellenmişsiniz.
|
||||||
|
@ -1483,6 +1545,7 @@ issues.filter_project=Proje
|
||||||
issues.filter_project_all=Tüm projeler
|
issues.filter_project_all=Tüm projeler
|
||||||
issues.filter_project_none=Proje yok
|
issues.filter_project_none=Proje yok
|
||||||
issues.filter_assignee=Atanan
|
issues.filter_assignee=Atanan
|
||||||
|
issues.filter_assignee_no_assignee=Hiç kimseye atanmamış
|
||||||
issues.filter_poster=Yazar
|
issues.filter_poster=Yazar
|
||||||
issues.filter_type=Tür
|
issues.filter_type=Tür
|
||||||
issues.filter_type.all_issues=Tüm konular
|
issues.filter_type.all_issues=Tüm konular
|
||||||
|
@ -2029,6 +2092,7 @@ contributors.contribution_type.deletions=Silmeler
|
||||||
settings=Ayarlar
|
settings=Ayarlar
|
||||||
settings.desc=Ayarlar, deponun ayarlarını yönetebileceğiniz yerdir
|
settings.desc=Ayarlar, deponun ayarlarını yönetebileceğiniz yerdir
|
||||||
settings.options=Depo
|
settings.options=Depo
|
||||||
|
settings.public_access=Herkese Açık Erişim
|
||||||
settings.collaboration=Katkıcılar
|
settings.collaboration=Katkıcılar
|
||||||
settings.collaboration.admin=Yönetici
|
settings.collaboration.admin=Yönetici
|
||||||
settings.collaboration.write=Yazma
|
settings.collaboration.write=Yazma
|
||||||
|
|
|
@ -117,6 +117,7 @@ files=文件
|
||||||
|
|
||||||
error=错误
|
error=错误
|
||||||
error404=您正尝试访问的页面 <strong>不存在</strong> 或 <strong>您尚未被授权</strong> 查看该页面。
|
error404=您正尝试访问的页面 <strong>不存在</strong> 或 <strong>您尚未被授权</strong> 查看该页面。
|
||||||
|
error503=服务器无法完成您的请求,请稍后重试。
|
||||||
go_back=返回
|
go_back=返回
|
||||||
invalid_data=无效数据: %v
|
invalid_data=无效数据: %v
|
||||||
|
|
||||||
|
@ -129,6 +130,7 @@ pin=固定
|
||||||
unpin=取消置顶
|
unpin=取消置顶
|
||||||
|
|
||||||
artifacts=制品
|
artifacts=制品
|
||||||
|
expired=已过期
|
||||||
confirm_delete_artifact=您确定要删除制品'%s'吗?
|
confirm_delete_artifact=您确定要删除制品'%s'吗?
|
||||||
|
|
||||||
archived=已归档
|
archived=已归档
|
||||||
|
@ -449,6 +451,7 @@ use_scratch_code=使用验证口令
|
||||||
twofa_scratch_used=你已经使用了你的验证口令。你将会转到两步验证设置页面以便移除你的注册设备或者重新生成新的验证口令。
|
twofa_scratch_used=你已经使用了你的验证口令。你将会转到两步验证设置页面以便移除你的注册设备或者重新生成新的验证口令。
|
||||||
twofa_passcode_incorrect=你的验证码不正确。如果你丢失了你的设备,请使用你的验证口令。
|
twofa_passcode_incorrect=你的验证码不正确。如果你丢失了你的设备,请使用你的验证口令。
|
||||||
twofa_scratch_token_incorrect=你的验证口令不正确。
|
twofa_scratch_token_incorrect=你的验证口令不正确。
|
||||||
|
twofa_required=您必须设置两步验证来访问仓库,或者尝试重新登录。
|
||||||
login_userpass=登录
|
login_userpass=登录
|
||||||
login_openid=OpenID
|
login_openid=OpenID
|
||||||
oauth_signup_tab=注册帐号
|
oauth_signup_tab=注册帐号
|
||||||
|
@ -1524,7 +1527,7 @@ issues.move_to_column_of_project=`将此对象移至 %s 的 %s 中在 %s 上`
|
||||||
issues.change_milestone_at=`%[3]s 修改了里程碑从 <b>%[1]s</b> 到 <b>%[2]s</b>`
|
issues.change_milestone_at=`%[3]s 修改了里程碑从 <b>%[1]s</b> 到 <b>%[2]s</b>`
|
||||||
issues.change_project_at=于 %[3]s 将此从项目 <b>%[1]s</b> 移到 <b>%[2]s</b>
|
issues.change_project_at=于 %[3]s 将此从项目 <b>%[1]s</b> 移到 <b>%[2]s</b>
|
||||||
issues.remove_milestone_at=`%[2]s 删除了里程碑 <b>%[1]s</b>`
|
issues.remove_milestone_at=`%[2]s 删除了里程碑 <b>%[1]s</b>`
|
||||||
issues.remove_project_at=`从 <b>%s</b> 项目 %s 中删除`
|
issues.remove_project_at=`于 %[2]s 将此工单从项目 <b>%[1]s</b> 中删除`
|
||||||
issues.deleted_milestone=(已删除)
|
issues.deleted_milestone=(已删除)
|
||||||
issues.deleted_project=`(已删除)`
|
issues.deleted_project=`(已删除)`
|
||||||
issues.self_assign_at=`于 %s 指派给自己`
|
issues.self_assign_at=`于 %s 指派给自己`
|
||||||
|
@ -1877,6 +1880,7 @@ pulls.add_prefix=添加 <strong>%s</strong> 前缀
|
||||||
pulls.remove_prefix=删除 <strong>%s</strong> 前缀
|
pulls.remove_prefix=删除 <strong>%s</strong> 前缀
|
||||||
pulls.data_broken=此合并请求因为派生仓库信息缺失而中断。
|
pulls.data_broken=此合并请求因为派生仓库信息缺失而中断。
|
||||||
pulls.files_conflicted=此合并请求有变更与目标分支冲突。
|
pulls.files_conflicted=此合并请求有变更与目标分支冲突。
|
||||||
|
pulls.is_checking=正在进行合并冲突检测 ...
|
||||||
pulls.is_ancestor=此分支已经包含在目标分支中,没有什么可以合并。
|
pulls.is_ancestor=此分支已经包含在目标分支中,没有什么可以合并。
|
||||||
pulls.is_empty=此分支上的更改已经在目标分支上。这将是一个空提交。
|
pulls.is_empty=此分支上的更改已经在目标分支上。这将是一个空提交。
|
||||||
pulls.required_status_check_failed=一些必要的检查没有成功
|
pulls.required_status_check_failed=一些必要的检查没有成功
|
||||||
|
@ -3724,7 +3728,11 @@ creation.name_placeholder=不区分大小写,字母数字或下划线不能以
|
||||||
creation.value_placeholder=输入任何内容,开头和结尾的空白都会被省略
|
creation.value_placeholder=输入任何内容,开头和结尾的空白都会被省略
|
||||||
creation.description_placeholder=输入简短描述(可选)。
|
creation.description_placeholder=输入简短描述(可选)。
|
||||||
|
|
||||||
|
save_success=密钥 '%s' 保存成功。
|
||||||
|
save_failed=密钥保存失败。
|
||||||
|
|
||||||
|
add_secret=添加密钥
|
||||||
|
edit_secret=编辑密钥
|
||||||
deletion=删除密钥
|
deletion=删除密钥
|
||||||
deletion.description=删除密钥是永久性的,无法撤消。继续吗?
|
deletion.description=删除密钥是永久性的,无法撤消。继续吗?
|
||||||
deletion.success=此Secret已被删除。
|
deletion.success=此Secret已被删除。
|
||||||
|
@ -3841,6 +3849,8 @@ deleted.display_name=已删除项目
|
||||||
type-1.display_name=个人项目
|
type-1.display_name=个人项目
|
||||||
type-2.display_name=仓库项目
|
type-2.display_name=仓库项目
|
||||||
type-3.display_name=组织项目
|
type-3.display_name=组织项目
|
||||||
|
enter_fullscreen=全屏
|
||||||
|
exit_fullscreen=退出全屏
|
||||||
|
|
||||||
[git.filemode]
|
[git.filemode]
|
||||||
changed_filemode=%[1]s -> %[2]s
|
changed_filemode=%[1]s -> %[2]s
|
||||||
|
|
|
@ -1279,7 +1279,10 @@ func Routes() *web.Router {
|
||||||
}, reqToken(), reqAdmin())
|
}, reqToken(), reqAdmin())
|
||||||
m.Group("/actions", func() {
|
m.Group("/actions", func() {
|
||||||
m.Get("/tasks", repo.ListActionTasks)
|
m.Get("/tasks", repo.ListActionTasks)
|
||||||
m.Get("/runs/{run}/artifacts", repo.GetArtifactsOfRun)
|
m.Group("/runs/{run}", func() {
|
||||||
|
m.Get("/artifacts", repo.GetArtifactsOfRun)
|
||||||
|
m.Delete("", reqToken(), reqRepoWriter(unit.TypeActions), repo.DeleteActionRun)
|
||||||
|
})
|
||||||
m.Get("/artifacts", repo.GetArtifacts)
|
m.Get("/artifacts", repo.GetArtifacts)
|
||||||
m.Group("/artifacts/{artifact_id}", func() {
|
m.Group("/artifacts/{artifact_id}", func() {
|
||||||
m.Get("", repo.GetArtifact)
|
m.Get("", repo.GetArtifact)
|
||||||
|
|
|
@ -1061,6 +1061,58 @@ func GetArtifactsOfRun(ctx *context.APIContext) {
|
||||||
ctx.JSON(http.StatusOK, &res)
|
ctx.JSON(http.StatusOK, &res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteActionRun Delete a workflow run
|
||||||
|
func DeleteActionRun(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /repos/{owner}/{repo}/actions/runs/{run} repository deleteActionRun
|
||||||
|
// ---
|
||||||
|
// summary: Delete a workflow run
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: name of the owner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: run
|
||||||
|
// in: path
|
||||||
|
// description: runid of the workflow run
|
||||||
|
// type: integer
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// description: "No Content"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
runID := ctx.PathParamInt64("run")
|
||||||
|
run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.APIError(http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !run.Status.IsDone() {
|
||||||
|
ctx.APIError(http.StatusBadRequest, "this workflow run is not done")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := actions_service.DeleteRun(ctx, run); err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
// GetArtifacts Lists all artifacts for a repository.
|
// GetArtifacts Lists all artifacts for a repository.
|
||||||
func GetArtifacts(ctx *context.APIContext) {
|
func GetArtifacts(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/actions/artifacts repository getArtifacts
|
// swagger:operation GET /repos/{owner}/{repo}/actions/artifacts repository getArtifacts
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -116,6 +117,16 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||||
// in: query
|
// in: query
|
||||||
// description: filepath of a file/dir
|
// description: filepath of a file/dir
|
||||||
// type: string
|
// type: string
|
||||||
|
// - name: since
|
||||||
|
// in: query
|
||||||
|
// description: Only commits after this date will be returned (ISO 8601 format)
|
||||||
|
// type: string
|
||||||
|
// format: date-time
|
||||||
|
// - name: until
|
||||||
|
// in: query
|
||||||
|
// description: Only commits before this date will be returned (ISO 8601 format)
|
||||||
|
// type: string
|
||||||
|
// format: date-time
|
||||||
// - name: stat
|
// - name: stat
|
||||||
// in: query
|
// in: query
|
||||||
// description: include diff stats for every commit (disable for speedup, default 'true')
|
// description: include diff stats for every commit (disable for speedup, default 'true')
|
||||||
|
@ -148,6 +159,23 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||||
// "409":
|
// "409":
|
||||||
// "$ref": "#/responses/EmptyRepository"
|
// "$ref": "#/responses/EmptyRepository"
|
||||||
|
|
||||||
|
since := ctx.FormString("since")
|
||||||
|
until := ctx.FormString("until")
|
||||||
|
|
||||||
|
// Validate since/until as ISO 8601 (RFC3339)
|
||||||
|
if since != "" {
|
||||||
|
if _, err := time.Parse(time.RFC3339, since); err != nil {
|
||||||
|
ctx.APIError(http.StatusUnprocessableEntity, "invalid 'since' format, expected ISO 8601 (RFC3339)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if until != "" {
|
||||||
|
if _, err := time.Parse(time.RFC3339, until); err != nil {
|
||||||
|
ctx.APIError(http.StatusUnprocessableEntity, "invalid 'until' format, expected ISO 8601 (RFC3339)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.Repo.Repository.IsEmpty {
|
if ctx.Repo.Repository.IsEmpty {
|
||||||
ctx.JSON(http.StatusConflict, api.APIError{
|
ctx.JSON(http.StatusConflict, api.APIError{
|
||||||
Message: "Git Repository is empty.",
|
Message: "Git Repository is empty.",
|
||||||
|
@ -198,6 +226,8 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||||
RepoPath: ctx.Repo.GitRepo.Path,
|
RepoPath: ctx.Repo.GitRepo.Path,
|
||||||
Not: not,
|
Not: not,
|
||||||
Revision: []string{baseCommit.ID.String()},
|
Revision: []string{baseCommit.ID.String()},
|
||||||
|
Since: since,
|
||||||
|
Until: until,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
|
@ -205,7 +235,7 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query commits
|
// Query commits
|
||||||
commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not)
|
commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not, since, until)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
|
@ -221,6 +251,8 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||||
Not: not,
|
Not: not,
|
||||||
Revision: []string{sha},
|
Revision: []string{sha},
|
||||||
RelPath: []string{path},
|
RelPath: []string{path},
|
||||||
|
Since: since,
|
||||||
|
Until: until,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -237,6 +269,8 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||||
File: path,
|
File: path,
|
||||||
Not: not,
|
Not: not,
|
||||||
Page: listOptions.Page,
|
Page: listOptions.Page,
|
||||||
|
Since: since,
|
||||||
|
Until: until,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
|
|
|
@ -895,6 +895,15 @@ func EditIssue(ctx *context.APIContext) {
|
||||||
issue.MilestoneID != *form.Milestone {
|
issue.MilestoneID != *form.Milestone {
|
||||||
oldMilestoneID := issue.MilestoneID
|
oldMilestoneID := issue.MilestoneID
|
||||||
issue.MilestoneID = *form.Milestone
|
issue.MilestoneID = *form.Milestone
|
||||||
|
if issue.MilestoneID > 0 {
|
||||||
|
issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, *form.Milestone)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
issue.Milestone = nil
|
||||||
|
}
|
||||||
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -609,6 +609,7 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if form.Body != comment.Content {
|
||||||
oldContent := comment.Content
|
oldContent := comment.Content
|
||||||
comment.Content = form.Body
|
comment.Content = form.Body
|
||||||
if err := issue_service.UpdateComment(ctx, comment, comment.ContentVersion, ctx.Doer, oldContent); err != nil {
|
if err := issue_service.UpdateComment(ctx, comment, comment.ContentVersion, ctx.Doer, oldContent); err != nil {
|
||||||
|
@ -619,6 +620,7 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
|
ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
|
||||||
}
|
}
|
||||||
|
|
|
@ -706,6 +706,11 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||||
issue.MilestoneID != form.Milestone {
|
issue.MilestoneID != form.Milestone {
|
||||||
oldMilestoneID := issue.MilestoneID
|
oldMilestoneID := issue.MilestoneID
|
||||||
issue.MilestoneID = form.Milestone
|
issue.MilestoneID = form.Milestone
|
||||||
|
issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, form.Milestone)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -67,6 +67,28 @@ func ListRunners(ctx *context.APIContext, ownerID, repoID int64) {
|
||||||
ctx.JSON(http.StatusOK, &res)
|
ctx.JSON(http.StatusOK, &res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRunnerByID(ctx *context.APIContext, ownerID, repoID, runnerID int64) (*actions_model.ActionRunner, bool) {
|
||||||
|
if ownerID != 0 && repoID != 0 {
|
||||||
|
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||||
|
}
|
||||||
|
|
||||||
|
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.APIErrorNotFound("Runner not found")
|
||||||
|
} else {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !runner.EditableInContext(ownerID, repoID) {
|
||||||
|
ctx.APIErrorNotFound("No permission to access this runner")
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return runner, true
|
||||||
|
}
|
||||||
|
|
||||||
// GetRunner get the runner for api route validated ownerID and repoID
|
// GetRunner get the runner for api route validated ownerID and repoID
|
||||||
// ownerID == 0 and repoID == 0 means any runner including global runners
|
// ownerID == 0 and repoID == 0 means any runner including global runners
|
||||||
// ownerID == 0 and repoID != 0 means any runner for the given repo
|
// ownerID == 0 and repoID != 0 means any runner for the given repo
|
||||||
|
@ -77,13 +99,8 @@ func GetRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
||||||
if ownerID != 0 && repoID != 0 {
|
if ownerID != 0 && repoID != 0 {
|
||||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||||
}
|
}
|
||||||
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
|
runner, ok := getRunnerByID(ctx, ownerID, repoID, runnerID)
|
||||||
if err != nil {
|
if !ok {
|
||||||
ctx.APIErrorNotFound(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !runner.EditableInContext(ownerID, repoID) {
|
|
||||||
ctx.APIErrorNotFound("No permission to get this runner")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusOK, convert.ToActionRunner(ctx, runner))
|
ctx.JSON(http.StatusOK, convert.ToActionRunner(ctx, runner))
|
||||||
|
@ -96,20 +113,12 @@ func GetRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
||||||
// ownerID != 0 and repoID != 0 undefined behavior
|
// ownerID != 0 and repoID != 0 undefined behavior
|
||||||
// Access rights are checked at the API route level
|
// Access rights are checked at the API route level
|
||||||
func DeleteRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
func DeleteRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
||||||
if ownerID != 0 && repoID != 0 {
|
runner, ok := getRunnerByID(ctx, ownerID, repoID, runnerID)
|
||||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
if !ok {
|
||||||
}
|
|
||||||
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.APIErrorInternal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !runner.EditableInContext(ownerID, repoID) {
|
|
||||||
ctx.APIErrorNotFound("No permission to delete this runner")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = actions_model.DeleteRunner(ctx, runner.ID)
|
err := actions_model.DeleteRunner(ctx, runner.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"code.gitea.io/gitea/modules/validation"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
webhook_service "code.gitea.io/gitea/services/webhook"
|
webhook_service "code.gitea.io/gitea/services/webhook"
|
||||||
|
@ -92,6 +93,10 @@ func checkCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption)
|
||||||
ctx.APIError(http.StatusUnprocessableEntity, "Invalid content type")
|
ctx.APIError(http.StatusUnprocessableEntity, "Invalid content type")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if !validation.IsValidURL(form.Config["url"]) {
|
||||||
|
ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,6 +159,41 @@ func pullHook(events []string, event string) bool {
|
||||||
return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), true)
|
return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateHookEvents(events []string) webhook_module.HookEvents {
|
||||||
|
if len(events) == 0 {
|
||||||
|
events = []string{"push"}
|
||||||
|
}
|
||||||
|
hookEvents := make(webhook_module.HookEvents)
|
||||||
|
hookEvents[webhook_module.HookEventCreate] = util.SliceContainsString(events, string(webhook_module.HookEventCreate), true)
|
||||||
|
hookEvents[webhook_module.HookEventPush] = util.SliceContainsString(events, string(webhook_module.HookEventPush), true)
|
||||||
|
hookEvents[webhook_module.HookEventDelete] = util.SliceContainsString(events, string(webhook_module.HookEventDelete), true)
|
||||||
|
hookEvents[webhook_module.HookEventFork] = util.SliceContainsString(events, string(webhook_module.HookEventFork), true)
|
||||||
|
hookEvents[webhook_module.HookEventRepository] = util.SliceContainsString(events, string(webhook_module.HookEventRepository), true)
|
||||||
|
hookEvents[webhook_module.HookEventWiki] = util.SliceContainsString(events, string(webhook_module.HookEventWiki), true)
|
||||||
|
hookEvents[webhook_module.HookEventRelease] = util.SliceContainsString(events, string(webhook_module.HookEventRelease), true)
|
||||||
|
hookEvents[webhook_module.HookEventPackage] = util.SliceContainsString(events, string(webhook_module.HookEventPackage), true)
|
||||||
|
hookEvents[webhook_module.HookEventStatus] = util.SliceContainsString(events, string(webhook_module.HookEventStatus), true)
|
||||||
|
hookEvents[webhook_module.HookEventWorkflowJob] = util.SliceContainsString(events, string(webhook_module.HookEventWorkflowJob), true)
|
||||||
|
|
||||||
|
// Issues
|
||||||
|
hookEvents[webhook_module.HookEventIssues] = issuesHook(events, "issues_only")
|
||||||
|
hookEvents[webhook_module.HookEventIssueAssign] = issuesHook(events, string(webhook_module.HookEventIssueAssign))
|
||||||
|
hookEvents[webhook_module.HookEventIssueLabel] = issuesHook(events, string(webhook_module.HookEventIssueLabel))
|
||||||
|
hookEvents[webhook_module.HookEventIssueMilestone] = issuesHook(events, string(webhook_module.HookEventIssueMilestone))
|
||||||
|
hookEvents[webhook_module.HookEventIssueComment] = issuesHook(events, string(webhook_module.HookEventIssueComment))
|
||||||
|
|
||||||
|
// Pull requests
|
||||||
|
hookEvents[webhook_module.HookEventPullRequest] = pullHook(events, "pull_request_only")
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestAssign] = pullHook(events, string(webhook_module.HookEventPullRequestAssign))
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestLabel] = pullHook(events, string(webhook_module.HookEventPullRequestLabel))
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestMilestone] = pullHook(events, string(webhook_module.HookEventPullRequestMilestone))
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestComment] = pullHook(events, string(webhook_module.HookEventPullRequestComment))
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestReview] = pullHook(events, "pull_request_review")
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestReviewRequest] = pullHook(events, string(webhook_module.HookEventPullRequestReviewRequest))
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestSync] = pullHook(events, string(webhook_module.HookEventPullRequestSync))
|
||||||
|
return hookEvents
|
||||||
|
}
|
||||||
|
|
||||||
// addHook add the hook specified by `form`, `ownerID` and `repoID`. If there is
|
// addHook add the hook specified by `form`, `ownerID` and `repoID`. If there is
|
||||||
// an error, write to `ctx` accordingly. Return (webhook, ok)
|
// an error, write to `ctx` accordingly. Return (webhook, ok)
|
||||||
func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoID int64) (*webhook.Webhook, bool) {
|
func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoID int64) (*webhook.Webhook, bool) {
|
||||||
|
@ -162,9 +202,6 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(form.Events) == 0 {
|
|
||||||
form.Events = []string{"push"}
|
|
||||||
}
|
|
||||||
if form.Config["is_system_webhook"] != "" {
|
if form.Config["is_system_webhook"] != "" {
|
||||||
sw, err := strconv.ParseBool(form.Config["is_system_webhook"])
|
sw, err := strconv.ParseBool(form.Config["is_system_webhook"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -183,31 +220,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
|
||||||
IsSystemWebhook: isSystemWebhook,
|
IsSystemWebhook: isSystemWebhook,
|
||||||
HookEvent: &webhook_module.HookEvent{
|
HookEvent: &webhook_module.HookEvent{
|
||||||
ChooseEvents: true,
|
ChooseEvents: true,
|
||||||
HookEvents: webhook_module.HookEvents{
|
HookEvents: updateHookEvents(form.Events),
|
||||||
webhook_module.HookEventCreate: util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true),
|
|
||||||
webhook_module.HookEventDelete: util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true),
|
|
||||||
webhook_module.HookEventFork: util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true),
|
|
||||||
webhook_module.HookEventIssues: issuesHook(form.Events, "issues_only"),
|
|
||||||
webhook_module.HookEventIssueAssign: issuesHook(form.Events, string(webhook_module.HookEventIssueAssign)),
|
|
||||||
webhook_module.HookEventIssueLabel: issuesHook(form.Events, string(webhook_module.HookEventIssueLabel)),
|
|
||||||
webhook_module.HookEventIssueMilestone: issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone)),
|
|
||||||
webhook_module.HookEventIssueComment: issuesHook(form.Events, string(webhook_module.HookEventIssueComment)),
|
|
||||||
webhook_module.HookEventPush: util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true),
|
|
||||||
webhook_module.HookEventPullRequest: pullHook(form.Events, "pull_request_only"),
|
|
||||||
webhook_module.HookEventPullRequestAssign: pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign)),
|
|
||||||
webhook_module.HookEventPullRequestLabel: pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel)),
|
|
||||||
webhook_module.HookEventPullRequestMilestone: pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)),
|
|
||||||
webhook_module.HookEventPullRequestComment: pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)),
|
|
||||||
webhook_module.HookEventPullRequestReview: pullHook(form.Events, "pull_request_review"),
|
|
||||||
webhook_module.HookEventPullRequestReviewRequest: pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest)),
|
|
||||||
webhook_module.HookEventPullRequestSync: pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)),
|
|
||||||
webhook_module.HookEventWiki: util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true),
|
|
||||||
webhook_module.HookEventRepository: util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true),
|
|
||||||
webhook_module.HookEventRelease: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true),
|
|
||||||
webhook_module.HookEventPackage: util.SliceContainsString(form.Events, string(webhook_module.HookEventPackage), true),
|
|
||||||
webhook_module.HookEventStatus: util.SliceContainsString(form.Events, string(webhook_module.HookEventStatus), true),
|
|
||||||
webhook_module.HookEventWorkflowJob: util.SliceContainsString(form.Events, string(webhook_module.HookEventWorkflowJob), true),
|
|
||||||
},
|
|
||||||
BranchFilter: form.BranchFilter,
|
BranchFilter: form.BranchFilter,
|
||||||
},
|
},
|
||||||
IsActive: form.Active,
|
IsActive: form.Active,
|
||||||
|
@ -324,6 +337,10 @@ func EditRepoHook(ctx *context.APIContext, form *api.EditHookOption, hookID int6
|
||||||
func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webhook) bool {
|
func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webhook) bool {
|
||||||
if form.Config != nil {
|
if form.Config != nil {
|
||||||
if url, ok := form.Config["url"]; ok {
|
if url, ok := form.Config["url"]; ok {
|
||||||
|
if !validation.IsValidURL(url) {
|
||||||
|
ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
|
||||||
|
return false
|
||||||
|
}
|
||||||
w.URL = url
|
w.URL = url
|
||||||
}
|
}
|
||||||
if ct, ok := form.Config["content_type"]; ok {
|
if ct, ok := form.Config["content_type"]; ok {
|
||||||
|
@ -352,19 +369,10 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update events
|
// Update events
|
||||||
if len(form.Events) == 0 {
|
w.HookEvents = updateHookEvents(form.Events)
|
||||||
form.Events = []string{"push"}
|
|
||||||
}
|
|
||||||
w.PushOnly = false
|
w.PushOnly = false
|
||||||
w.SendEverything = false
|
w.SendEverything = false
|
||||||
w.ChooseEvents = true
|
w.ChooseEvents = true
|
||||||
w.HookEvents[webhook_module.HookEventCreate] = util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true)
|
|
||||||
w.HookEvents[webhook_module.HookEventPush] = util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true)
|
|
||||||
w.HookEvents[webhook_module.HookEventDelete] = util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true)
|
|
||||||
w.HookEvents[webhook_module.HookEventFork] = util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true)
|
|
||||||
w.HookEvents[webhook_module.HookEventRepository] = util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true)
|
|
||||||
w.HookEvents[webhook_module.HookEventWiki] = util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true)
|
|
||||||
w.HookEvents[webhook_module.HookEventRelease] = util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true)
|
|
||||||
w.BranchFilter = form.BranchFilter
|
w.BranchFilter = form.BranchFilter
|
||||||
|
|
||||||
err := w.SetHeaderAuthorization(form.AuthorizationHeader)
|
err := w.SetHeaderAuthorization(form.AuthorizationHeader)
|
||||||
|
@ -373,23 +381,6 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issues
|
|
||||||
w.HookEvents[webhook_module.HookEventIssues] = issuesHook(form.Events, "issues_only")
|
|
||||||
w.HookEvents[webhook_module.HookEventIssueAssign] = issuesHook(form.Events, string(webhook_module.HookEventIssueAssign))
|
|
||||||
w.HookEvents[webhook_module.HookEventIssueLabel] = issuesHook(form.Events, string(webhook_module.HookEventIssueLabel))
|
|
||||||
w.HookEvents[webhook_module.HookEventIssueMilestone] = issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone))
|
|
||||||
w.HookEvents[webhook_module.HookEventIssueComment] = issuesHook(form.Events, string(webhook_module.HookEventIssueComment))
|
|
||||||
|
|
||||||
// Pull requests
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequest] = pullHook(form.Events, "pull_request_only")
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestAssign] = pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign))
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestLabel] = pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel))
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestMilestone] = pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone))
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestComment] = pullHook(form.Events, string(webhook_module.HookEventPullRequestComment))
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestReview] = pullHook(form.Events, "pull_request_review")
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestReviewRequest] = pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest))
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestSync] = pullHook(form.Events, string(webhook_module.HookEventPullRequestSync))
|
|
||||||
|
|
||||||
if err := w.UpdateEvent(); err != nil {
|
if err := w.UpdateEvent(); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/services/contexttest"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTestHookValidation(t *testing.T) {
|
||||||
|
unittest.PrepareTestEnv(t)
|
||||||
|
|
||||||
|
t.Run("Test Validation", func(t *testing.T) {
|
||||||
|
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
|
||||||
|
contexttest.LoadRepo(t, ctx, 1)
|
||||||
|
contexttest.LoadRepoCommit(t, ctx)
|
||||||
|
contexttest.LoadUser(t, ctx, 2)
|
||||||
|
|
||||||
|
checkCreateHookOption(ctx, &structs.CreateHookOption{
|
||||||
|
Type: "gitea",
|
||||||
|
Config: map[string]string{
|
||||||
|
"content_type": "json",
|
||||||
|
"url": "https://example.com/webhook",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Equal(t, 0, ctx.Resp.WrittenStatus()) // not written yet
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Validation with invalid URL", func(t *testing.T) {
|
||||||
|
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
|
||||||
|
contexttest.LoadRepo(t, ctx, 1)
|
||||||
|
contexttest.LoadRepoCommit(t, ctx)
|
||||||
|
contexttest.LoadUser(t, ctx, 2)
|
||||||
|
|
||||||
|
checkCreateHookOption(ctx, &structs.CreateHookOption{
|
||||||
|
Type: "gitea",
|
||||||
|
Config: map[string]string{
|
||||||
|
"content_type": "json",
|
||||||
|
"url": "example.com/webhook",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Validation with invalid webhook type", func(t *testing.T) {
|
||||||
|
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
|
||||||
|
contexttest.LoadRepo(t, ctx, 1)
|
||||||
|
contexttest.LoadRepoCommit(t, ctx)
|
||||||
|
contexttest.LoadUser(t, ctx, 2)
|
||||||
|
|
||||||
|
checkCreateHookOption(ctx, &structs.CreateHookOption{
|
||||||
|
Type: "unknown",
|
||||||
|
Config: map[string]string{
|
||||||
|
"content_type": "json",
|
||||||
|
"url": "example.com/webhook",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Validation with empty content type", func(t *testing.T) {
|
||||||
|
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
|
||||||
|
contexttest.LoadRepo(t, ctx, 1)
|
||||||
|
contexttest.LoadRepoCommit(t, ctx)
|
||||||
|
contexttest.LoadUser(t, ctx, 2)
|
||||||
|
|
||||||
|
checkCreateHookOption(ctx, &structs.CreateHookOption{
|
||||||
|
Type: "unknown",
|
||||||
|
Config: map[string]string{
|
||||||
|
"url": "https://example.com/webhook",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
webhook_service "code.gitea.io/gitea/services/webhook"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
unittest.MainTest(m, &unittest.TestOptions{
|
||||||
|
SetUp: func() error {
|
||||||
|
setting.LoadQueueSettings()
|
||||||
|
return webhook_service.Init()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -220,7 +220,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cols) > 0 {
|
if len(cols) > 0 {
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, repo, cols...); err != nil {
|
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, cols...); err != nil {
|
||||||
log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
|
log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
|
||||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
|
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
|
||||||
Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
|
Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
|
|
||||||
// ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed
|
// ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed
|
||||||
func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) {
|
func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) {
|
||||||
commits, err := ctx.Repo.Commit.CommitsByRange(0, 10, "")
|
commits, err := ctx.Repo.Commit.CommitsByRange(0, 10, "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("ShowBranchFeed", err)
|
ctx.ServerError("ShowBranchFeed", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -317,6 +317,8 @@ func prepareWorkflowList(ctx *context.Context, workflows []Workflow) {
|
||||||
pager.AddParamFromRequest(ctx.Req)
|
pager.AddParamFromRequest(ctx.Req)
|
||||||
ctx.Data["Page"] = pager
|
ctx.Data["Page"] = pager
|
||||||
ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0
|
ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0
|
||||||
|
|
||||||
|
ctx.Data["AllowDeleteWorkflowRuns"] = ctx.Repo.CanWrite(unit.TypeActions)
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadIsRefDeleted loads the IsRefDeleted field for each run in the list.
|
// loadIsRefDeleted loads the IsRefDeleted field for each run in the list.
|
||||||
|
|
|
@ -577,6 +577,33 @@ func Approve(ctx *context_module.Context) {
|
||||||
ctx.JSON(http.StatusOK, struct{}{})
|
ctx.JSON(http.StatusOK, struct{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Delete(ctx *context_module.Context) {
|
||||||
|
runIndex := getRunIndex(ctx)
|
||||||
|
repoID := ctx.Repo.Repository.ID
|
||||||
|
|
||||||
|
run, err := actions_model.GetRunByIndex(ctx, repoID, runIndex)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.JSONErrorNotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.ServerError("GetRunByIndex", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !run.Status.IsDone() {
|
||||||
|
ctx.JSONError(ctx.Tr("actions.runs.not_done"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := actions_service.DeleteRun(ctx, run); err != nil {
|
||||||
|
ctx.ServerError("DeleteRun", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSONOK()
|
||||||
|
}
|
||||||
|
|
||||||
// getRunJobs gets the jobs of runIndex, and returns jobs[jobIndex], jobs.
|
// getRunJobs gets the jobs of runIndex, and returns jobs[jobIndex], jobs.
|
||||||
// Any error will be written to the ctx.
|
// Any error will be written to the ctx.
|
||||||
// It never returns a nil job of an empty jobs, if the jobIndex is out of range, it will be treated as 0.
|
// It never returns a nil job of an empty jobs, if the jobIndex is out of range, it will be treated as 0.
|
||||||
|
@ -584,20 +611,20 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions
|
||||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, util.ErrNotExist) {
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
ctx.HTTPError(http.StatusNotFound, err.Error())
|
ctx.NotFound(nil)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
ctx.HTTPError(http.StatusInternalServerError, err.Error())
|
ctx.ServerError("GetRunByIndex", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
run.Repo = ctx.Repo.Repository
|
run.Repo = ctx.Repo.Repository
|
||||||
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.HTTPError(http.StatusInternalServerError, err.Error())
|
ctx.ServerError("GetRunJobsByRunID", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if len(jobs) == 0 {
|
if len(jobs) == 0 {
|
||||||
ctx.HTTPError(http.StatusNotFound)
|
ctx.NotFound(nil)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -264,7 +264,7 @@ func MergeUpstream(ctx *context.Context) {
|
||||||
_, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, branchName)
|
_, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, branchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, util.ErrNotExist) {
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
ctx.JSONError(ctx.Tr("error.not_found"))
|
ctx.JSONErrorNotFound()
|
||||||
return
|
return
|
||||||
} else if pull_service.IsErrMergeConflicts(err) {
|
} else if pull_service.IsErrMergeConflicts(err) {
|
||||||
ctx.JSONError(ctx.Tr("repo.pulls.merge_conflict"))
|
ctx.JSONError(ctx.Tr("repo.pulls.merge_conflict"))
|
||||||
|
|
|
@ -78,7 +78,7 @@ func Commits(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Both `git log branchName` and `git log commitId` work.
|
// Both `git log branchName` and `git log commitId` work.
|
||||||
commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "")
|
commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("CommitsByRange", err)
|
ctx.ServerError("CommitsByRange", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -376,12 +376,6 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Repo.Repository.IsEmpty {
|
|
||||||
if isEmpty, err := ctx.Repo.GitRepo.IsEmpty(); err == nil && !isEmpty {
|
|
||||||
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectForCommitChoice(ctx, form.CommitChoice, branchName, form.TreePath)
|
redirectForCommitChoice(ctx, form.CommitChoice, branchName, form.TreePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -790,7 +784,7 @@ func UploadFilePost(ctx *context.Context) {
|
||||||
|
|
||||||
if ctx.Repo.Repository.IsEmpty {
|
if ctx.Repo.Repository.IsEmpty {
|
||||||
if isEmpty, err := ctx.Repo.GitRepo.IsEmpty(); err == nil && !isEmpty {
|
if isEmpty, err := ctx.Repo.GitRepo.IsEmpty(); err == nil && !isEmpty {
|
||||||
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
|
_ = repo_model.UpdateRepositoryColsWithAutoTime(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -418,6 +418,16 @@ func UpdateIssueMilestone(ctx *context.Context) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
issue.MilestoneID = milestoneID
|
issue.MilestoneID = milestoneID
|
||||||
|
if milestoneID > 0 {
|
||||||
|
var err error
|
||||||
|
issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetMilestoneByRepoID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
issue.Milestone = nil
|
||||||
|
}
|
||||||
if err := issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
if err := issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
||||||
ctx.ServerError("ChangeMilestoneAssign", err)
|
ctx.ServerError("ChangeMilestoneAssign", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -239,12 +239,18 @@ func UpdateCommentContent(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oldContent := comment.Content
|
|
||||||
newContent := ctx.FormString("content")
|
newContent := ctx.FormString("content")
|
||||||
contentVersion := ctx.FormInt("content_version")
|
contentVersion := ctx.FormInt("content_version")
|
||||||
|
if contentVersion != comment.ContentVersion {
|
||||||
|
ctx.JSONError(ctx.Tr("repo.comments.edit.already_changed"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if newContent != comment.Content {
|
||||||
// allow to save empty content
|
// allow to save empty content
|
||||||
|
oldContent := comment.Content
|
||||||
comment.Content = newContent
|
comment.Content = newContent
|
||||||
|
|
||||||
if err = issue_service.UpdateComment(ctx, comment, contentVersion, ctx.Doer, oldContent); err != nil {
|
if err = issue_service.UpdateComment(ctx, comment, contentVersion, ctx.Doer, oldContent); err != nil {
|
||||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||||
ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user"))
|
ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user"))
|
||||||
|
@ -255,6 +261,7 @@ func UpdateCommentContent(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := comment.LoadAttachments(ctx); err != nil {
|
if err := comment.LoadAttachments(ctx); err != nil {
|
||||||
ctx.ServerError("LoadAttachments", err)
|
ctx.ServerError("LoadAttachments", err)
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
giturl "code.gitea.io/gitea/modules/git/url"
|
giturl "code.gitea.io/gitea/modules/git/url"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/httplib"
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
|
@ -261,6 +262,10 @@ func updateContextRepoEmptyAndStatus(ctx *context.Context, empty bool, status re
|
||||||
|
|
||||||
func handleRepoEmptyOrBroken(ctx *context.Context) {
|
func handleRepoEmptyOrBroken(ctx *context.Context) {
|
||||||
showEmpty := true
|
showEmpty := true
|
||||||
|
if ctx.Repo.GitRepo == nil {
|
||||||
|
// in case the repo really exists and works, but the status was incorrectly marked as "broken", we need to open and check it again
|
||||||
|
ctx.Repo.GitRepo, _ = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
|
||||||
|
}
|
||||||
if ctx.Repo.GitRepo != nil {
|
if ctx.Repo.GitRepo != nil {
|
||||||
reallyEmpty, err := ctx.Repo.GitRepo.IsEmpty()
|
reallyEmpty, err := ctx.Repo.GitRepo.IsEmpty()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -396,10 +401,8 @@ func Home(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareHomeTreeSideBarSwitch(ctx)
|
|
||||||
|
|
||||||
title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
|
title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
|
||||||
if len(ctx.Repo.Repository.Description) > 0 {
|
if ctx.Repo.Repository.Description != "" {
|
||||||
title += ": " + ctx.Repo.Repository.Description
|
title += ": " + ctx.Repo.Repository.Description
|
||||||
}
|
}
|
||||||
ctx.Data["Title"] = title
|
ctx.Data["Title"] = title
|
||||||
|
@ -412,6 +415,8 @@ func Home(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepareHomeTreeSideBarSwitch(ctx)
|
||||||
|
|
||||||
// get the current git entry which doer user is currently looking at.
|
// get the current git entry which doer user is currently looking at.
|
||||||
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
|
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -109,7 +109,7 @@ func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, err
|
||||||
return wikiGitRepo, nil, errBranch
|
return wikiGitRepo, nil, errBranch
|
||||||
}
|
}
|
||||||
// update the default branch in the database
|
// update the default branch in the database
|
||||||
errDb := repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, DefaultWikiBranch: gitRepoDefaultBranch}, "default_wiki_branch")
|
errDb := repo_model.UpdateRepositoryColsNoAutoTime(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, DefaultWikiBranch: gitRepoDefaultBranch}, "default_wiki_branch")
|
||||||
if errDb != nil {
|
if errDb != nil {
|
||||||
return wikiGitRepo, nil, errDb
|
return wikiGitRepo, nil, errDb
|
||||||
}
|
}
|
||||||
|
|
|
@ -245,7 +245,7 @@ func TestDefaultWikiBranch(t *testing.T) {
|
||||||
assert.NoError(t, wiki_service.ChangeDefaultWikiBranch(db.DefaultContext, repoWithNoWiki, "main"))
|
assert.NoError(t, wiki_service.ChangeDefaultWikiBranch(db.DefaultContext, repoWithNoWiki, "main"))
|
||||||
|
|
||||||
// repo with wiki
|
// repo with wiki
|
||||||
assert.NoError(t, repo_model.UpdateRepositoryCols(db.DefaultContext, &repo_model.Repository{ID: 1, DefaultWikiBranch: "wrong-branch"}))
|
assert.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(db.DefaultContext, &repo_model.Repository{ID: 1, DefaultWikiBranch: "wrong-branch"}))
|
||||||
|
|
||||||
ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki")
|
ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki")
|
||||||
ctx.SetPathParam("*", "Home")
|
ctx.SetPathParam("*", "Home")
|
||||||
|
|
|
@ -1447,6 +1447,7 @@ func registerWebRoutes(m *web.Router) {
|
||||||
})
|
})
|
||||||
m.Post("/cancel", reqRepoActionsWriter, actions.Cancel)
|
m.Post("/cancel", reqRepoActionsWriter, actions.Cancel)
|
||||||
m.Post("/approve", reqRepoActionsWriter, actions.Approve)
|
m.Post("/approve", reqRepoActionsWriter, actions.Approve)
|
||||||
|
m.Post("/delete", reqRepoActionsWriter, actions.Delete)
|
||||||
m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView)
|
m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView)
|
||||||
m.Delete("/artifacts/{artifact_name}", reqRepoActionsWriter, actions.ArtifactsDeleteView)
|
m.Delete("/artifacts/{artifact_name}", reqRepoActionsWriter, actions.ArtifactsDeleteView)
|
||||||
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
|
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
|
||||||
|
|
|
@ -5,12 +5,14 @@ package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
actions_module "code.gitea.io/gitea/modules/actions"
|
actions_module "code.gitea.io/gitea/modules/actions"
|
||||||
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
@ -27,7 +29,7 @@ func Cleanup(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up old logs
|
// clean up old logs
|
||||||
if err := CleanupLogs(ctx); err != nil {
|
if err := CleanupExpiredLogs(ctx); err != nil {
|
||||||
return fmt.Errorf("cleanup logs: %w", err)
|
return fmt.Errorf("cleanup logs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,8 +100,15 @@ func cleanNeedDeleteArtifacts(taskCtx context.Context) error {
|
||||||
|
|
||||||
const deleteLogBatchSize = 100
|
const deleteLogBatchSize = 100
|
||||||
|
|
||||||
// CleanupLogs removes logs which are older than the configured retention time
|
func removeTaskLog(ctx context.Context, task *actions_model.ActionTask) {
|
||||||
func CleanupLogs(ctx context.Context) error {
|
if err := actions_module.RemoveLogs(ctx, task.LogInStorage, task.LogFilename); err != nil {
|
||||||
|
log.Error("Failed to remove log %s (in storage %v) of task %v: %v", task.LogFilename, task.LogInStorage, task.ID, err)
|
||||||
|
// do not return error here, go on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanupExpiredLogs removes logs which are older than the configured retention time
|
||||||
|
func CleanupExpiredLogs(ctx context.Context) error {
|
||||||
olderThan := timeutil.TimeStampNow().AddDuration(-time.Duration(setting.Actions.LogRetentionDays) * 24 * time.Hour)
|
olderThan := timeutil.TimeStampNow().AddDuration(-time.Duration(setting.Actions.LogRetentionDays) * 24 * time.Hour)
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
|
@ -109,10 +118,7 @@ func CleanupLogs(ctx context.Context) error {
|
||||||
return fmt.Errorf("find old tasks: %w", err)
|
return fmt.Errorf("find old tasks: %w", err)
|
||||||
}
|
}
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
if err := actions_module.RemoveLogs(ctx, task.LogInStorage, task.LogFilename); err != nil {
|
removeTaskLog(ctx, task)
|
||||||
log.Error("Failed to remove log %s (in storage %v) of task %v: %v", task.LogFilename, task.LogInStorage, task.ID, err)
|
|
||||||
// do not return error here, go on
|
|
||||||
}
|
|
||||||
task.LogIndexes = nil // clear log indexes since it's a heavy field
|
task.LogIndexes = nil // clear log indexes since it's a heavy field
|
||||||
task.LogExpired = true
|
task.LogExpired = true
|
||||||
if err := actions_model.UpdateTask(ctx, task, "log_indexes", "log_expired"); err != nil {
|
if err := actions_model.UpdateTask(ctx, task, "log_indexes", "log_expired"); err != nil {
|
||||||
|
@ -148,3 +154,107 @@ func CleanupEphemeralRunners(ctx context.Context) error {
|
||||||
log.Info("Removed %d runners", affected)
|
log.Info("Removed %d runners", affected)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CleanupEphemeralRunnersByPickedTaskOfRepo removes all ephemeral runners that have active/finished tasks on the given repository
|
||||||
|
func CleanupEphemeralRunnersByPickedTaskOfRepo(ctx context.Context, repoID int64) error {
|
||||||
|
subQuery := builder.Select("`action_runner`.id").
|
||||||
|
From(builder.Select("*").From("`action_runner`"), "`action_runner`"). // mysql needs this redundant subquery
|
||||||
|
Join("INNER", "`action_task`", "`action_task`.`runner_id` = `action_runner`.`id`").
|
||||||
|
Where(builder.And(builder.Eq{"`action_runner`.`ephemeral`": true}, builder.Eq{"`action_task`.`repo_id`": repoID}))
|
||||||
|
b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`")
|
||||||
|
res, err := db.GetEngine(ctx).Exec(b)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("find runners: %w", err)
|
||||||
|
}
|
||||||
|
affected, _ := res.RowsAffected()
|
||||||
|
log.Info("Removed %d runners", affected)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRun deletes workflow run, including all logs and artifacts.
|
||||||
|
func DeleteRun(ctx context.Context, run *actions_model.ActionRun) error {
|
||||||
|
if !run.Status.IsDone() {
|
||||||
|
return errors.New("run is not done")
|
||||||
|
}
|
||||||
|
|
||||||
|
repoID := run.RepoID
|
||||||
|
|
||||||
|
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
jobIDs := container.FilterSlice(jobs, func(j *actions_model.ActionRunJob) (int64, bool) {
|
||||||
|
return j.ID, true
|
||||||
|
})
|
||||||
|
tasks := make(actions_model.TaskList, 0)
|
||||||
|
if len(jobIDs) > 0 {
|
||||||
|
if err := db.GetEngine(ctx).Where("repo_id = ?", repoID).In("job_id", jobIDs).Find(&tasks); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts, err := db.Find[actions_model.ActionArtifact](ctx, actions_model.FindArtifactsOptions{
|
||||||
|
RepoID: repoID,
|
||||||
|
RunID: run.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var recordsToDelete []any
|
||||||
|
|
||||||
|
recordsToDelete = append(recordsToDelete, &actions_model.ActionRun{
|
||||||
|
RepoID: repoID,
|
||||||
|
ID: run.ID,
|
||||||
|
})
|
||||||
|
recordsToDelete = append(recordsToDelete, &actions_model.ActionRunJob{
|
||||||
|
RepoID: repoID,
|
||||||
|
RunID: run.ID,
|
||||||
|
})
|
||||||
|
for _, tas := range tasks {
|
||||||
|
recordsToDelete = append(recordsToDelete, &actions_model.ActionTask{
|
||||||
|
RepoID: repoID,
|
||||||
|
ID: tas.ID,
|
||||||
|
})
|
||||||
|
recordsToDelete = append(recordsToDelete, &actions_model.ActionTaskStep{
|
||||||
|
RepoID: repoID,
|
||||||
|
TaskID: tas.ID,
|
||||||
|
})
|
||||||
|
recordsToDelete = append(recordsToDelete, &actions_model.ActionTaskOutput{
|
||||||
|
TaskID: tas.ID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
recordsToDelete = append(recordsToDelete, &actions_model.ActionArtifact{
|
||||||
|
RepoID: repoID,
|
||||||
|
RunID: run.ID,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
// TODO: Deleting task records could break current ephemeral runner implementation. This is a temporary workaround suggested by ChristopherHX.
|
||||||
|
// Since you delete potentially the only task an ephemeral act_runner has ever run, please delete the affected runners first.
|
||||||
|
// one of
|
||||||
|
// call cleanup ephemeral runners first
|
||||||
|
// delete affected ephemeral act_runners
|
||||||
|
// I would make ephemeral runners fully delete directly before formally finishing the task
|
||||||
|
//
|
||||||
|
// See also: https://github.com/go-gitea/gitea/pull/34337#issuecomment-2862222788
|
||||||
|
if err := CleanupEphemeralRunners(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return db.DeleteBeans(ctx, recordsToDelete...)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete files on storage
|
||||||
|
for _, tas := range tasks {
|
||||||
|
removeTaskLog(ctx, tas)
|
||||||
|
}
|
||||||
|
for _, art := range artifacts {
|
||||||
|
if err := storage.ActionsArtifacts.Delete(art.StoragePath); err != nil {
|
||||||
|
log.Error("remove artifact file %q: %v", art.StoragePath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -15,11 +15,15 @@ import (
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
|
"github.com/nektos/act/pkg/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type GiteaContext map[string]any
|
||||||
|
|
||||||
// GenerateGiteaContext generate the gitea context without token and gitea_runtime_token
|
// GenerateGiteaContext generate the gitea context without token and gitea_runtime_token
|
||||||
// job can be nil when generating a context for parsing workflow-level expressions
|
// job can be nil when generating a context for parsing workflow-level expressions
|
||||||
func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.ActionRunJob) map[string]any {
|
func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.ActionRunJob) GiteaContext {
|
||||||
event := map[string]any{}
|
event := map[string]any{}
|
||||||
_ = json.Unmarshal([]byte(run.EventPayload), &event)
|
_ = json.Unmarshal([]byte(run.EventPayload), &event)
|
||||||
|
|
||||||
|
@ -42,7 +46,7 @@ func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.Actio
|
||||||
|
|
||||||
refName := git.RefName(ref)
|
refName := git.RefName(ref)
|
||||||
|
|
||||||
gitContext := map[string]any{
|
gitContext := GiteaContext{
|
||||||
// standard contexts, see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
|
// standard contexts, see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
|
||||||
"action": "", // string, The name of the action currently running, or the id of a step. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2.
|
"action": "", // string, The name of the action currently running, or the id of a step. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2.
|
||||||
"action_path": "", // string, The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action.
|
"action_path": "", // string, The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action.
|
||||||
|
@ -160,3 +164,37 @@ func mergeTwoOutputs(o1, o2 map[string]string) map[string]string {
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *GiteaContext) ToGitHubContext() *model.GithubContext {
|
||||||
|
return &model.GithubContext{
|
||||||
|
Event: (*g)["event"].(map[string]any),
|
||||||
|
EventPath: (*g)["event_path"].(string),
|
||||||
|
Workflow: (*g)["workflow"].(string),
|
||||||
|
RunID: (*g)["run_id"].(string),
|
||||||
|
RunNumber: (*g)["run_number"].(string),
|
||||||
|
Actor: (*g)["actor"].(string),
|
||||||
|
Repository: (*g)["repository"].(string),
|
||||||
|
EventName: (*g)["event_name"].(string),
|
||||||
|
Sha: (*g)["sha"].(string),
|
||||||
|
Ref: (*g)["ref"].(string),
|
||||||
|
RefName: (*g)["ref_name"].(string),
|
||||||
|
RefType: (*g)["ref_type"].(string),
|
||||||
|
HeadRef: (*g)["head_ref"].(string),
|
||||||
|
BaseRef: (*g)["base_ref"].(string),
|
||||||
|
Token: "", // deliberately omitted for security
|
||||||
|
Workspace: (*g)["workspace"].(string),
|
||||||
|
Action: (*g)["action"].(string),
|
||||||
|
ActionPath: (*g)["action_path"].(string),
|
||||||
|
ActionRef: (*g)["action_ref"].(string),
|
||||||
|
ActionRepository: (*g)["action_repository"].(string),
|
||||||
|
Job: (*g)["job"].(string),
|
||||||
|
JobName: "", // not present in GiteaContext
|
||||||
|
RepositoryOwner: (*g)["repository_owner"].(string),
|
||||||
|
RetentionDays: (*g)["retention_days"].(string),
|
||||||
|
RunnerPerflog: "", // not present in GiteaContext
|
||||||
|
RunnerTrackingID: "", // not present in GiteaContext
|
||||||
|
ServerURL: (*g)["server_url"].(string),
|
||||||
|
APIURL: (*g)["api_url"].(string),
|
||||||
|
GraphQLURL: (*g)["graphql_url"].(string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -302,9 +302,11 @@ func handleWorkflows(
|
||||||
run := &actions_model.ActionRun{
|
run := &actions_model.ActionRun{
|
||||||
Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
|
Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
|
||||||
RepoID: input.Repo.ID,
|
RepoID: input.Repo.ID,
|
||||||
|
Repo: input.Repo,
|
||||||
OwnerID: input.Repo.OwnerID,
|
OwnerID: input.Repo.OwnerID,
|
||||||
WorkflowID: dwf.EntryName,
|
WorkflowID: dwf.EntryName,
|
||||||
TriggerUserID: input.Doer.ID,
|
TriggerUserID: input.Doer.ID,
|
||||||
|
TriggerUser: input.Doer,
|
||||||
Ref: ref,
|
Ref: ref,
|
||||||
CommitSHA: commit.ID.String(),
|
CommitSHA: commit.ID.String(),
|
||||||
IsForkPullRequest: isForkPullRequest,
|
IsForkPullRequest: isForkPullRequest,
|
||||||
|
@ -333,12 +335,18 @@ func handleWorkflows(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
jobs, err := jobparser.Parse(dwf.Content, jobparser.WithVars(vars))
|
giteaCtx := GenerateGiteaContext(run, nil)
|
||||||
|
|
||||||
|
jobs, err := jobparser.Parse(dwf.Content, jobparser.WithVars(vars), jobparser.WithGitContext(giteaCtx.ToGitHubContext()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("jobparser.Parse: %v", err)
|
log.Error("jobparser.Parse: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(jobs) > 0 && jobs[0].RunName != "" {
|
||||||
|
run.Title = jobs[0].RunName
|
||||||
|
}
|
||||||
|
|
||||||
// cancel running jobs if the event is push or pull_request_sync
|
// cancel running jobs if the event is push or pull_request_sync
|
||||||
if run.Event == webhook_module.HookEventPush ||
|
if run.Event == webhook_module.HookEventPush ||
|
||||||
run.Event == webhook_module.HookEventPullRequestSync {
|
run.Event == webhook_module.HookEventPullRequestSync {
|
||||||
|
@ -508,9 +516,11 @@ func handleSchedules(
|
||||||
run := &actions_model.ActionSchedule{
|
run := &actions_model.ActionSchedule{
|
||||||
Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
|
Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
|
||||||
RepoID: input.Repo.ID,
|
RepoID: input.Repo.ID,
|
||||||
|
Repo: input.Repo,
|
||||||
OwnerID: input.Repo.OwnerID,
|
OwnerID: input.Repo.OwnerID,
|
||||||
WorkflowID: dwf.EntryName,
|
WorkflowID: dwf.EntryName,
|
||||||
TriggerUserID: user_model.ActionsUserID,
|
TriggerUserID: user_model.ActionsUserID,
|
||||||
|
TriggerUser: user_model.NewActionsUser(),
|
||||||
Ref: ref,
|
Ref: ref,
|
||||||
CommitSHA: commit.ID.String(),
|
CommitSHA: commit.ID.String(),
|
||||||
Event: input.Event,
|
Event: input.Event,
|
||||||
|
@ -518,6 +528,25 @@ func handleSchedules(
|
||||||
Specs: schedules,
|
Specs: schedules,
|
||||||
Content: dwf.Content,
|
Content: dwf.Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vars, err := actions_model.GetVariablesOfRun(ctx, run.ToActionRun())
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetVariablesOfRun: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
giteaCtx := GenerateGiteaContext(run.ToActionRun(), nil)
|
||||||
|
|
||||||
|
jobs, err := jobparser.Parse(dwf.Content, jobparser.WithVars(vars), jobparser.WithGitContext(giteaCtx.ToGitHubContext()))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("jobparser.Parse: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(jobs) > 0 && jobs[0].RunName != "" {
|
||||||
|
run.Title = jobs[0].RunName
|
||||||
|
}
|
||||||
|
|
||||||
crons = append(crons, run)
|
crons = append(crons, run)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -192,20 +192,53 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
|
||||||
|
|
||||||
// find workflow from commit
|
// find workflow from commit
|
||||||
var workflows []*jobparser.SingleWorkflow
|
var workflows []*jobparser.SingleWorkflow
|
||||||
for _, entry := range entries {
|
var entry *git.TreeEntry
|
||||||
if entry.Name() != workflowID {
|
|
||||||
|
run := &actions_model.ActionRun{
|
||||||
|
Title: strings.SplitN(runTargetCommit.CommitMessage, "\n", 2)[0],
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Repo: repo,
|
||||||
|
OwnerID: repo.OwnerID,
|
||||||
|
WorkflowID: workflowID,
|
||||||
|
TriggerUserID: doer.ID,
|
||||||
|
TriggerUser: doer,
|
||||||
|
Ref: string(refName),
|
||||||
|
CommitSHA: runTargetCommit.ID.String(),
|
||||||
|
IsForkPullRequest: false,
|
||||||
|
Event: "workflow_dispatch",
|
||||||
|
TriggerEvent: "workflow_dispatch",
|
||||||
|
Status: actions_model.StatusWaiting,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.Name() != workflowID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
entry = e
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry == nil {
|
||||||
|
return util.ErrorWrapLocale(
|
||||||
|
util.NewNotExistErrorf("workflow %q doesn't exist", workflowID),
|
||||||
|
"actions.workflow.not_found", workflowID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
content, err := actions.GetContentFromEntry(entry)
|
content, err := actions.GetContentFromEntry(entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
workflows, err = jobparser.Parse(content)
|
|
||||||
|
giteaCtx := GenerateGiteaContext(run, nil)
|
||||||
|
|
||||||
|
workflows, err = jobparser.Parse(content, jobparser.WithGitContext(giteaCtx.ToGitHubContext()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
break
|
|
||||||
|
if len(workflows) > 0 && workflows[0].RunName != "" {
|
||||||
|
run.Title = workflows[0].RunName
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(workflows) == 0 {
|
if len(workflows) == 0 {
|
||||||
|
@ -236,25 +269,12 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
|
||||||
Inputs: inputsWithDefaults,
|
Inputs: inputsWithDefaults,
|
||||||
Sender: convert.ToUserWithAccessMode(ctx, doer, perm.AccessModeNone),
|
Sender: convert.ToUserWithAccessMode(ctx, doer, perm.AccessModeNone),
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventPayload []byte
|
var eventPayload []byte
|
||||||
if eventPayload, err = workflowDispatchPayload.JSONPayload(); err != nil {
|
if eventPayload, err = workflowDispatchPayload.JSONPayload(); err != nil {
|
||||||
return fmt.Errorf("JSONPayload: %w", err)
|
return fmt.Errorf("JSONPayload: %w", err)
|
||||||
}
|
}
|
||||||
|
run.EventPayload = string(eventPayload)
|
||||||
run := &actions_model.ActionRun{
|
|
||||||
Title: strings.SplitN(runTargetCommit.CommitMessage, "\n", 2)[0],
|
|
||||||
RepoID: repo.ID,
|
|
||||||
OwnerID: repo.OwnerID,
|
|
||||||
WorkflowID: workflowID,
|
|
||||||
TriggerUserID: doer.ID,
|
|
||||||
Ref: string(refName),
|
|
||||||
CommitSHA: runTargetCommit.ID.String(),
|
|
||||||
IsForkPullRequest: false,
|
|
||||||
Event: "workflow_dispatch",
|
|
||||||
TriggerEvent: "workflow_dispatch",
|
|
||||||
EventPayload: string(eventPayload),
|
|
||||||
Status: actions_model.StatusWaiting,
|
|
||||||
}
|
|
||||||
|
|
||||||
// cancel running jobs of the same workflow
|
// cancel running jobs of the same workflow
|
||||||
if err := CancelPreviousJobs(
|
if err := CancelPreviousJobs(
|
||||||
|
@ -280,6 +300,5 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
|
||||||
for _, job := range allJobs {
|
for _, job := range allJobs {
|
||||||
notify_service.WorkflowJobStatusUpdate(ctx, repo, doer, job, nil)
|
notify_service.WorkflowJobStatusUpdate(ctx, repo, doer, job, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -245,7 +245,7 @@ func APIContexter() func(http.Handler) http.Handler {
|
||||||
// APIErrorNotFound handles 404s for APIContext
|
// APIErrorNotFound handles 404s for APIContext
|
||||||
// String will replace message, errors will be added to a slice
|
// String will replace message, errors will be added to a slice
|
||||||
func (ctx *APIContext) APIErrorNotFound(objs ...any) {
|
func (ctx *APIContext) APIErrorNotFound(objs ...any) {
|
||||||
message := ctx.Locale.TrString("error.not_found")
|
var message string
|
||||||
var errs []string
|
var errs []string
|
||||||
for _, obj := range objs {
|
for _, obj := range objs {
|
||||||
// Ignore nil
|
// Ignore nil
|
||||||
|
@ -259,9 +259,8 @@ func (ctx *APIContext) APIErrorNotFound(objs ...any) {
|
||||||
message = obj.(string)
|
message = obj.(string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusNotFound, map[string]any{
|
ctx.JSON(http.StatusNotFound, map[string]any{
|
||||||
"message": message,
|
"message": util.IfZero(message, "not found"), // do not use locale in API
|
||||||
"url": setting.API.SwaggerURL,
|
"url": setting.API.SwaggerURL,
|
||||||
"errors": errs,
|
"errors": errs,
|
||||||
})
|
})
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
web_types "code.gitea.io/gitea/modules/web/types"
|
web_types "code.gitea.io/gitea/modules/web/types"
|
||||||
|
@ -261,3 +262,11 @@ func (ctx *Context) JSONError(msg any) {
|
||||||
panic(fmt.Sprintf("unsupported type: %T", msg))
|
panic(fmt.Sprintf("unsupported type: %T", msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) JSONErrorNotFound(optMsg ...string) {
|
||||||
|
msg := util.OptionalArg(optMsg)
|
||||||
|
if msg == "" {
|
||||||
|
msg = ctx.Locale.TrString("error.not_found")
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusNotFound, map[string]any{"errorMessage": msg, "renderFormat": "text"})
|
||||||
|
}
|
||||||
|
|
|
@ -795,8 +795,8 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) {
|
||||||
return func(ctx *Context) {
|
return func(ctx *Context) {
|
||||||
var err error
|
var err error
|
||||||
refType := detectRefType
|
refType := detectRefType
|
||||||
if ctx.Repo.Repository.IsBeingCreated() {
|
if ctx.Repo.Repository.IsBeingCreated() || ctx.Repo.Repository.IsBroken() {
|
||||||
return // no git repo, so do nothing, users will see a "migrating" UI provided by "migrate/migrating.tmpl"
|
return // no git repo, so do nothing, users will see a "migrating" UI provided by "migrate/migrating.tmpl", or empty repo guide
|
||||||
}
|
}
|
||||||
// Empty repository does not have reference information.
|
// Empty repository does not have reference information.
|
||||||
if ctx.Repo.Repository.IsEmpty {
|
if ctx.Repo.Repository.IsEmpty {
|
||||||
|
|
|
@ -98,6 +98,8 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
||||||
allowSquash := false
|
allowSquash := false
|
||||||
allowFastForwardOnly := false
|
allowFastForwardOnly := false
|
||||||
allowRebaseUpdate := false
|
allowRebaseUpdate := false
|
||||||
|
allowManualMerge := true
|
||||||
|
autodetectManualMerge := false
|
||||||
defaultDeleteBranchAfterMerge := false
|
defaultDeleteBranchAfterMerge := false
|
||||||
defaultMergeStyle := repo_model.MergeStyleMerge
|
defaultMergeStyle := repo_model.MergeStyleMerge
|
||||||
defaultAllowMaintainerEdit := false
|
defaultAllowMaintainerEdit := false
|
||||||
|
@ -111,6 +113,8 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
||||||
allowSquash = config.AllowSquash
|
allowSquash = config.AllowSquash
|
||||||
allowFastForwardOnly = config.AllowFastForwardOnly
|
allowFastForwardOnly = config.AllowFastForwardOnly
|
||||||
allowRebaseUpdate = config.AllowRebaseUpdate
|
allowRebaseUpdate = config.AllowRebaseUpdate
|
||||||
|
allowManualMerge = config.AllowManualMerge
|
||||||
|
autodetectManualMerge = config.AutodetectManualMerge
|
||||||
defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge
|
defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge
|
||||||
defaultMergeStyle = config.GetDefaultMergeStyle()
|
defaultMergeStyle = config.GetDefaultMergeStyle()
|
||||||
defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit
|
defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit
|
||||||
|
@ -235,6 +239,8 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
||||||
AllowSquash: allowSquash,
|
AllowSquash: allowSquash,
|
||||||
AllowFastForwardOnly: allowFastForwardOnly,
|
AllowFastForwardOnly: allowFastForwardOnly,
|
||||||
AllowRebaseUpdate: allowRebaseUpdate,
|
AllowRebaseUpdate: allowRebaseUpdate,
|
||||||
|
AllowManualMerge: allowManualMerge,
|
||||||
|
AutodetectManualMerge: autodetectManualMerge,
|
||||||
DefaultDeleteBranchAfterMerge: defaultDeleteBranchAfterMerge,
|
DefaultDeleteBranchAfterMerge: defaultDeleteBranchAfterMerge,
|
||||||
DefaultMergeStyle: string(defaultMergeStyle),
|
DefaultMergeStyle: string(defaultMergeStyle),
|
||||||
DefaultAllowMaintainerEdit: defaultAllowMaintainerEdit,
|
DefaultAllowMaintainerEdit: defaultAllowMaintainerEdit,
|
||||||
|
|
|
@ -148,7 +148,7 @@ func (g *GiteaLocalUploader) CreateRepo(ctx context.Context, repo *base.Reposito
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
g.repo.ObjectFormatName = objectFormat.Name()
|
g.repo.ObjectFormatName = objectFormat.Name()
|
||||||
return repo_model.UpdateRepositoryCols(ctx, g.repo, "object_format_name")
|
return repo_model.UpdateRepositoryColsNoAutoTime(ctx, g.repo, "object_format_name")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes this uploader
|
// Close closes this uploader
|
||||||
|
@ -975,7 +975,7 @@ func (g *GiteaLocalUploader) Finish(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
g.repo.Status = repo_model.RepositoryReady
|
g.repo.Status = repo_model.RepositoryReady
|
||||||
return repo_model.UpdateRepositoryCols(ctx, g.repo, "status")
|
return repo_model.UpdateRepositoryColsWithAutoTime(ctx, g.repo, "status")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GiteaLocalUploader) remapUser(ctx context.Context, source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) error {
|
func (g *GiteaLocalUploader) remapUser(ctx context.Context, source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) error {
|
||||||
|
|
|
@ -71,7 +71,7 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error
|
||||||
// erase authentication before storing in database
|
// erase authentication before storing in database
|
||||||
u.User = nil
|
u.User = nil
|
||||||
m.Repo.OriginalURL = u.String()
|
m.Repo.OriginalURL = u.String()
|
||||||
return repo_model.UpdateRepositoryCols(ctx, m.Repo, "original_url")
|
return repo_model.UpdateRepositoryColsNoAutoTime(ctx, m.Repo, "original_url")
|
||||||
}
|
}
|
||||||
|
|
||||||
// mirrorSyncResult contains information of a updated reference.
|
// mirrorSyncResult contains information of a updated reference.
|
||||||
|
@ -653,7 +653,7 @@ func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, re
|
||||||
}
|
}
|
||||||
m.Repo.IsEmpty = false
|
m.Repo.IsEmpty = false
|
||||||
// Update the is empty and default_branch columns
|
// Update the is empty and default_branch columns
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, m.Repo, "default_branch", "is_empty"); err != nil {
|
if err := repo_model.UpdateRepositoryColsWithAutoTime(ctx, m.Repo, "default_branch", "is_empty"); err != nil {
|
||||||
log.Error("Failed to update default branch of repository %-v. Error: %v", m.Repo, err)
|
log.Error("Failed to update default branch of repository %-v. Error: %v", m.Repo, err)
|
||||||
desc := fmt.Sprintf("Failed to update default branch of repository '%s': %v", m.Repo.RepoPath(), err)
|
desc := fmt.Sprintf("Failed to update default branch of repository '%s': %v", m.Repo.RepoPath(), err)
|
||||||
if err = system_model.CreateRepositoryNotice(desc); err != nil {
|
if err = system_model.CreateRepositoryNotice(desc); err != nil {
|
||||||
|
|
|
@ -64,10 +64,11 @@ func RemoveOrgUser(ctx context.Context, org *organization.Organization, user *us
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("AccessibleReposEnv: %w", err)
|
return fmt.Errorf("AccessibleReposEnv: %w", err)
|
||||||
}
|
}
|
||||||
repoIDs, err := env.RepoIDs(ctx, 1, org.NumRepos)
|
repoIDs, err := env.RepoIDs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("GetUserRepositories [%d]: %w", user.ID, err)
|
return fmt.Errorf("GetUserRepositories [%d]: %w", user.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, repoID := range repoIDs {
|
for _, repoID := range repoIDs {
|
||||||
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
|
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -100,7 +100,7 @@ func AdoptRepository(ctx context.Context, doer, owner *user_model.User, opts Cre
|
||||||
|
|
||||||
// 4 - update repository status
|
// 4 - update repository status
|
||||||
repo.Status = repo_model.RepositoryReady
|
repo.Status = repo_model.RepositoryReady
|
||||||
if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
|
if err = repo_model.UpdateRepositoryColsWithAutoTime(ctx, repo, "status"); err != nil {
|
||||||
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
|
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ func UploadAvatar(ctx context.Context, repo *repo_model.Repository, data []byte)
|
||||||
// Users can upload the same image to other repo - prefix it with ID
|
// 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
|
// Then repo will be removed - only it avatar file will be removed
|
||||||
repo.Avatar = newAvatar
|
repo.Avatar = newAvatar
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, repo, "avatar"); err != nil {
|
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "avatar"); err != nil {
|
||||||
return fmt.Errorf("UploadAvatar: Update repository avatar: %w", err)
|
return fmt.Errorf("UploadAvatar: Update repository avatar: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ func DeleteAvatar(ctx context.Context, repo *repo_model.Repository) error {
|
||||||
defer committer.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
repo.Avatar = ""
|
repo.Avatar = ""
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, repo, "avatar"); err != nil {
|
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "avatar"); err != nil {
|
||||||
return fmt.Errorf("DeleteAvatar: Update repository avatar: %w", err)
|
return fmt.Errorf("DeleteAvatar: Update repository avatar: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,5 +112,5 @@ func generateAvatar(ctx context.Context, templateRepo, generateRepo *repo_model.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return repo_model.UpdateRepositoryCols(ctx, generateRepo, "avatar")
|
return repo_model.UpdateRepositoryColsNoAutoTime(ctx, generateRepo, "avatar")
|
||||||
}
|
}
|
||||||
|
|
|
@ -321,7 +321,7 @@ func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User,
|
||||||
// 7 - update repository status to be ready
|
// 7 - update repository status to be ready
|
||||||
if needsUpdateToReady {
|
if needsUpdateToReady {
|
||||||
repo.Status = repo_model.RepositoryReady
|
repo.Status = repo_model.RepositoryReady
|
||||||
if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
|
if err = repo_model.UpdateRepositoryColsWithAutoTime(ctx, repo, "status"); err != nil {
|
||||||
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
|
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/lfs"
|
"code.gitea.io/gitea/modules/lfs"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
actions_service "code.gitea.io/gitea/services/actions"
|
||||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
|
@ -133,6 +134,14 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CleanupEphemeralRunnersByPickedTaskOfRepo deletes ephemeral global/org/user that have started any task of this repo
|
||||||
|
// The cannot pick a second task hardening for ephemeral runners expect that task objects remain available until runner deletion
|
||||||
|
// This method will delete affected ephemeral global/org/user runners
|
||||||
|
// &actions_model.ActionRunner{RepoID: repoID} does only handle ephemeral repository runners
|
||||||
|
if err := actions_service.CleanupEphemeralRunnersByPickedTaskOfRepo(ctx, repoID); err != nil {
|
||||||
|
return fmt.Errorf("cleanupEphemeralRunners: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := db.DeleteBeans(ctx,
|
if err := db.DeleteBeans(ctx,
|
||||||
&access_model.Access{RepoID: repo.ID},
|
&access_model.Access{RepoID: repo.ID},
|
||||||
&activities_model.Action{RepoID: repo.ID},
|
&activities_model.Action{RepoID: repo.ID},
|
||||||
|
|
|
@ -306,7 +306,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
||||||
|
|
||||||
if repo.IsEmpty {
|
if repo.IsEmpty {
|
||||||
if isEmpty, err := gitRepo.IsEmpty(); err == nil && !isEmpty {
|
if isEmpty, err := gitRepo.IsEmpty(); err == nil && !isEmpty {
|
||||||
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: repo.ID, IsEmpty: false, DefaultBranch: opts.NewBranch}, "is_empty", "default_branch")
|
_ = repo_model.UpdateRepositoryColsWithAutoTime(ctx, &repo_model.Repository{ID: repo.ID, IsEmpty: false, DefaultBranch: opts.NewBranch}, "is_empty", "default_branch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -198,7 +198,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
|
||||||
|
|
||||||
// 8 - update repository status to be ready
|
// 8 - update repository status to be ready
|
||||||
repo.Status = repo_model.RepositoryReady
|
repo.Status = repo_model.RepositoryReady
|
||||||
if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
|
if err = repo_model.UpdateRepositoryColsWithAutoTime(ctx, repo, "status"); err != nil {
|
||||||
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
|
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -283,7 +283,7 @@ func pushNewBranch(ctx context.Context, repo *repo_model.Repository, pusher *use
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update the is empty and default_branch columns
|
// Update the is empty and default_branch columns
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, repo, "default_branch", "is_empty"); err != nil {
|
if err := repo_model.UpdateRepositoryColsWithAutoTime(ctx, repo, "default_branch", "is_empty"); err != nil {
|
||||||
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
|
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,7 +205,7 @@ func updateRepository(ctx context.Context, repo *repo_model.Repository, visibili
|
||||||
|
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
|
|
||||||
if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil {
|
if _, err = e.ID(repo.ID).NoAutoTime().AllCols().Update(repo); err != nil {
|
||||||
return fmt.Errorf("update: %w", err)
|
return fmt.Errorf("update: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -184,7 +184,7 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
|
||||||
|
|
||||||
// 6 - update repository status to be ready
|
// 6 - update repository status to be ready
|
||||||
generateRepo.Status = repo_model.RepositoryReady
|
generateRepo.Status = repo_model.RepositoryReady
|
||||||
if err = repo_model.UpdateRepositoryCols(ctx, generateRepo, "status"); err != nil {
|
if err = repo_model.UpdateRepositoryColsWithAutoTime(ctx, generateRepo, "status"); err != nil {
|
||||||
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
|
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,7 +160,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
|
||||||
repo.OwnerName = newOwner.Name
|
repo.OwnerName = newOwner.Name
|
||||||
|
|
||||||
// Update repository.
|
// Update repository.
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, repo, "owner_id", "owner_name"); err != nil {
|
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "owner_id", "owner_name"); err != nil {
|
||||||
return fmt.Errorf("update owner: %w", err)
|
return fmt.Errorf("update owner: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,7 +304,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
|
||||||
return fmt.Errorf("deleteRepositoryTransfer: %w", err)
|
return fmt.Errorf("deleteRepositoryTransfer: %w", err)
|
||||||
}
|
}
|
||||||
repo.Status = repo_model.RepositoryReady
|
repo.Status = repo_model.RepositoryReady
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
|
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "status"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,7 +495,7 @@ func RejectRepositoryTransfer(ctx context.Context, repo *repo_model.Repository,
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.Status = repo_model.RepositoryReady
|
repo.Status = repo_model.RepositoryReady
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
|
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "status"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -543,7 +543,7 @@ func CancelRepositoryTransfer(ctx context.Context, repoTransfer *repo_model.Repo
|
||||||
}
|
}
|
||||||
|
|
||||||
repoTransfer.Repo.Status = repo_model.RepositoryReady
|
repoTransfer.Repo.Status = repo_model.RepositoryReady
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, repoTransfer.Repo, "status"); err != nil {
|
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repoTransfer.Repo, "status"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -305,7 +305,7 @@ func parseHookPullRequestEventType(event webhook_module.HookEventType) (string,
|
||||||
case webhook_module.HookEventPullRequestReviewApproved:
|
case webhook_module.HookEventPullRequestReviewApproved:
|
||||||
return "approved", nil
|
return "approved", nil
|
||||||
case webhook_module.HookEventPullRequestReviewRejected:
|
case webhook_module.HookEventPullRequestReviewRejected:
|
||||||
return "rejected", nil
|
return "requested changes", nil
|
||||||
case webhook_module.HookEventPullRequestReviewComment:
|
case webhook_module.HookEventPullRequestReviewComment:
|
||||||
return "comment", nil
|
return "comment", nil
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -365,7 +365,7 @@ func ChangeDefaultWikiBranch(ctx context.Context, repo *repo_model.Repository, n
|
||||||
}
|
}
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
repo.DefaultWikiBranch = newBranch
|
repo.DefaultWikiBranch = newBranch
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, repo, "default_wiki_branch"); err != nil {
|
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "default_wiki_branch"); err != nil {
|
||||||
return fmt.Errorf("unable to update database: %w", err)
|
return fmt.Errorf("unable to update database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,37 +5,46 @@
|
||||||
<h2>{{if $.IsFiltered}}{{ctx.Locale.Tr "actions.runs.no_results"}}{{else}}{{ctx.Locale.Tr "actions.runs.no_runs"}}{{end}}</h2>
|
<h2>{{if $.IsFiltered}}{{ctx.Locale.Tr "actions.runs.no_results"}}{{else}}{{ctx.Locale.Tr "actions.runs.no_runs"}}{{end}}</h2>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{range .Runs}}
|
{{range $run := .Runs}}
|
||||||
<div class="flex-item tw-items-center">
|
<div class="flex-item tw-items-center">
|
||||||
<div class="flex-item-leading">
|
<div class="flex-item-leading">
|
||||||
{{template "repo/actions/status" (dict "status" .Status.String)}}
|
{{template "repo/actions/status" (dict "status" $run.Status.String)}}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-item-main">
|
<div class="flex-item-main">
|
||||||
<a class="flex-item-title" title="{{.Title}}" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
|
<a class="flex-item-title" title="{{$run.Title}}" href="{{$run.Link}}">
|
||||||
{{if .Title}}{{.Title}}{{else}}{{ctx.Locale.Tr "actions.runs.empty_commit_message"}}{{end}}
|
{{or $run.Title (ctx.Locale.Tr "actions.runs.empty_commit_message")}}
|
||||||
</a>
|
</a>
|
||||||
<div class="flex-item-body">
|
<div class="flex-item-body">
|
||||||
<span><b>{{if not $.CurWorkflow}}{{.WorkflowID}} {{end}}#{{.Index}}</b>:</span>
|
<span><b>{{if not $.CurWorkflow}}{{$run.WorkflowID}} {{end}}#{{$run.Index}}</b>:</span>
|
||||||
{{- if .ScheduleID -}}
|
{{- if $run.ScheduleID -}}
|
||||||
{{ctx.Locale.Tr "actions.runs.scheduled"}}
|
{{ctx.Locale.Tr "actions.runs.scheduled"}}
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
{{ctx.Locale.Tr "actions.runs.commit"}}
|
{{ctx.Locale.Tr "actions.runs.commit"}}
|
||||||
<a href="{{$.RepoLink}}/commit/{{.CommitSHA}}">{{ShortSha .CommitSHA}}</a>
|
<a href="{{$.RepoLink}}/commit/{{$run.CommitSHA}}">{{ShortSha $run.CommitSHA}}</a>
|
||||||
{{ctx.Locale.Tr "actions.runs.pushed_by"}}
|
{{ctx.Locale.Tr "actions.runs.pushed_by"}}
|
||||||
<a href="{{.TriggerUser.HomeLink}}">{{.TriggerUser.GetDisplayName}}</a>
|
<a href="{{$run.TriggerUser.HomeLink}}">{{$run.TriggerUser.GetDisplayName}}</a>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-item-trailing">
|
<div class="flex-item-trailing">
|
||||||
{{if .IsRefDeleted}}
|
{{if $run.IsRefDeleted}}
|
||||||
<span class="ui label run-list-ref gt-ellipsis tw-line-through" data-tooltip-content="{{.PrettyRef}}">{{.PrettyRef}}</span>
|
<span class="ui label run-list-ref gt-ellipsis tw-line-through" data-tooltip-content="{{$run.PrettyRef}}">{{$run.PrettyRef}}</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a class="ui label run-list-ref gt-ellipsis" href="{{.RefLink}}" data-tooltip-content="{{.PrettyRef}}">{{.PrettyRef}}</a>
|
<a class="ui label run-list-ref gt-ellipsis" href="{{$run.RefLink}}" data-tooltip-content="{{$run.PrettyRef}}">{{$run.PrettyRef}}</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="run-list-item-right">
|
<div class="run-list-item-right">
|
||||||
<div class="run-list-meta">{{svg "octicon-calendar" 16}}{{DateUtils.TimeSince .Updated}}</div>
|
<div class="run-list-meta">{{svg "octicon-calendar" 16}}{{DateUtils.TimeSince $run.Updated}}</div>
|
||||||
<div class="run-list-meta">{{svg "octicon-stopwatch" 16}}{{.Duration}}</div>
|
<div class="run-list-meta">{{svg "octicon-stopwatch" 16}}{{$run.Duration}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{if and ($.AllowDeleteWorkflowRuns) ($run.Status.IsDone)}}
|
||||||
|
<button class="btn interact-bg link-action tw-p-2"
|
||||||
|
data-url="{{$run.Link}}/delete"
|
||||||
|
data-modal-confirm="{{ctx.Locale.Tr "actions.runs.delete.description"}}"
|
||||||
|
data-tooltip-content="{{ctx.Locale.Tr "actions.runs.delete"}}"
|
||||||
|
>
|
||||||
|
{{svg "octicon-trash"}}
|
||||||
|
</button>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -226,11 +226,19 @@
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{else if .Permission.IsAdmin}}
|
{{else}}
|
||||||
<div class="overflow-menu-items">
|
<div class="overflow-menu-items">
|
||||||
|
{{if(and .Repository.IsBeingCreated (.Permission.CanRead ctx.Consts.RepoUnitTypeCode))}}
|
||||||
|
<a class="{{if not .PageIsRepoSettings}}active {{end}}item" href="{{.RepoLink}}">
|
||||||
|
{{svg "octicon-clock"}} {{ctx.Locale.Tr "repo.migrating_status"}}
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .Permission.IsAdmin}}
|
||||||
<a class="{{if .PageIsRepoSettings}}active {{end}} item" href="{{.RepoLink}}/settings">
|
<a class="{{if .PageIsRepoSettings}}active {{end}} item" href="{{.RepoLink}}/settings">
|
||||||
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
|
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
|
||||||
</a>
|
</a>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</overflow-menu>
|
</overflow-menu>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="flex-item">
|
<div class="flex-item">
|
||||||
<div class="flex-item-main">
|
<div class="flex-item-main">
|
||||||
<div class="flex-item-title">
|
<div class="flex-item-title">
|
||||||
<a class="item muted" href="{{.Link}}/releases">
|
<a class="item muted" href="{{.RepoLink}}/releases">
|
||||||
{{ctx.Locale.Tr "repo.releases"}}
|
{{ctx.Locale.Tr "repo.releases"}}
|
||||||
<span class="ui small label">{{.NumReleases}}</span>
|
<span class="ui small label">{{.NumReleases}}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
<span class="color-text-light-2">
|
<span class="color-text-light-2">
|
||||||
{{ctx.Locale.Tr "settings.added_on" (DateUtils.AbsoluteShort .CreatedUnix)}}
|
{{ctx.Locale.Tr "settings.added_on" (DateUtils.AbsoluteShort .CreatedUnix)}}
|
||||||
</span>
|
</span>
|
||||||
<button class="ui btn interact-bg show-modal tw-p-2"
|
<button class="btn interact-bg show-modal tw-p-2"
|
||||||
data-modal="#add-secret-modal"
|
data-modal="#add-secret-modal"
|
||||||
data-modal-form.action="{{$.Link}}"
|
data-modal-form.action="{{$.Link}}"
|
||||||
data-modal-header="{{ctx.Locale.Tr "secrets.edit_secret"}}"
|
data-modal-header="{{ctx.Locale.Tr "secrets.edit_secret"}}"
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
>
|
>
|
||||||
{{svg "octicon-pencil"}}
|
{{svg "octicon-pencil"}}
|
||||||
</button>
|
</button>
|
||||||
<button class="ui btn interact-bg link-action tw-p-2"
|
<button class="btn interact-bg link-action tw-p-2"
|
||||||
data-url="{{$.Link}}/delete?id={{.ID}}"
|
data-url="{{$.Link}}/delete?id={{.ID}}"
|
||||||
data-modal-confirm="{{ctx.Locale.Tr "secrets.deletion.description"}}"
|
data-modal-confirm="{{ctx.Locale.Tr "secrets.deletion.description"}}"
|
||||||
data-tooltip-content="{{ctx.Locale.Tr "secrets.deletion"}}"
|
data-tooltip-content="{{ctx.Locale.Tr "secrets.deletion"}}"
|
||||||
|
|
|
@ -4758,6 +4758,52 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/runs/{run}": {
|
||||||
|
"delete": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Delete a workflow run",
|
||||||
|
"operationId": "deleteActionRun",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the owner",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repository",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "runid of the workflow run",
|
||||||
|
"name": "run",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "No Content"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/actions/runs/{run}/artifacts": {
|
"/repos/{owner}/{repo}/actions/runs/{run}/artifacts": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -6558,6 +6604,20 @@
|
||||||
"name": "path",
|
"name": "path",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "Only commits after this date will be returned (ISO 8601 format)",
|
||||||
|
"name": "since",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "Only commits before this date will be returned (ISO 8601 format)",
|
||||||
|
"name": "until",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "include diff stats for every commit (disable for speedup, default 'true')",
|
"description": "include diff stats for every commit (disable for speedup, default 'true')",
|
||||||
|
@ -26138,6 +26198,10 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"x-go-name": "AllowFastForwardOnly"
|
"x-go-name": "AllowFastForwardOnly"
|
||||||
},
|
},
|
||||||
|
"allow_manual_merge": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "AllowManualMerge"
|
||||||
|
},
|
||||||
"allow_merge_commits": {
|
"allow_merge_commits": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"x-go-name": "AllowMerge"
|
"x-go-name": "AllowMerge"
|
||||||
|
@ -26167,6 +26231,10 @@
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
"x-go-name": "ArchivedAt"
|
"x-go-name": "ArchivedAt"
|
||||||
},
|
},
|
||||||
|
"autodetect_manual_merge": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "AutodetectManualMerge"
|
||||||
|
},
|
||||||
"avatar_url": {
|
"avatar_url": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "AvatarURL"
|
"x-go-name": "AvatarURL"
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/json"
|
||||||
|
"code.gitea.io/gitea/routers/web/repo/actions"
|
||||||
|
|
||||||
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestActionsDeleteRun(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
testCase := struct {
|
||||||
|
treePath string
|
||||||
|
fileContent string
|
||||||
|
outcomes map[string]*mockTaskOutcome
|
||||||
|
expectedStatuses map[string]string
|
||||||
|
}{
|
||||||
|
treePath: ".gitea/workflows/test1.yml",
|
||||||
|
fileContent: `name: test1
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- .gitea/workflows/test1.yml
|
||||||
|
jobs:
|
||||||
|
job1:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo job1
|
||||||
|
job2:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo job2
|
||||||
|
job3:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo job3
|
||||||
|
`,
|
||||||
|
outcomes: map[string]*mockTaskOutcome{
|
||||||
|
"job1": {
|
||||||
|
result: runnerv1.Result_RESULT_SUCCESS,
|
||||||
|
logRows: []*runnerv1.LogRow{
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(4 * time.Second)),
|
||||||
|
Content: " \U0001F433 docker create image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(5 * time.Second)),
|
||||||
|
Content: "job1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(6 * time.Second)),
|
||||||
|
Content: "\U0001F3C1 Job succeeded",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"job2": {
|
||||||
|
result: runnerv1.Result_RESULT_SUCCESS,
|
||||||
|
logRows: []*runnerv1.LogRow{
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(4 * time.Second)),
|
||||||
|
Content: " \U0001F433 docker create image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(5 * time.Second)),
|
||||||
|
Content: "job2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(6 * time.Second)),
|
||||||
|
Content: "\U0001F3C1 Job succeeded",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"job3": {
|
||||||
|
result: runnerv1.Result_RESULT_SUCCESS,
|
||||||
|
logRows: []*runnerv1.LogRow{
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(4 * time.Second)),
|
||||||
|
Content: " \U0001F433 docker create image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(5 * time.Second)),
|
||||||
|
Content: "job3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(6 * time.Second)),
|
||||||
|
Content: "\U0001F3C1 Job succeeded",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedStatuses: map[string]string{
|
||||||
|
"job1": actions_model.StatusSuccess.String(),
|
||||||
|
"job2": actions_model.StatusSuccess.String(),
|
||||||
|
"job3": actions_model.StatusSuccess.String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
session := loginUser(t, user2.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||||
|
|
||||||
|
apiRepo := createActionsTestRepo(t, token, "actions-delete-run-test", false)
|
||||||
|
runner := newMockRunner()
|
||||||
|
runner.registerAsRepoRunner(t, user2.Name, apiRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
||||||
|
|
||||||
|
opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, "create "+testCase.treePath, testCase.fileContent)
|
||||||
|
createWorkflowFile(t, token, user2.Name, apiRepo.Name, testCase.treePath, opts)
|
||||||
|
|
||||||
|
runIndex := ""
|
||||||
|
for i := 0; i < len(testCase.outcomes); i++ {
|
||||||
|
task := runner.fetchTask(t)
|
||||||
|
jobName := getTaskJobNameByTaskID(t, token, user2.Name, apiRepo.Name, task.Id)
|
||||||
|
outcome := testCase.outcomes[jobName]
|
||||||
|
assert.NotNil(t, outcome)
|
||||||
|
runner.execTask(t, task, outcome)
|
||||||
|
runIndex = task.Context.GetFields()["run_number"].GetStringValue()
|
||||||
|
assert.Equal(t, "1", runIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(testCase.outcomes); i++ {
|
||||||
|
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d", user2.Name, apiRepo.Name, runIndex, i), map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, session),
|
||||||
|
})
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
var listResp actions.ViewResponse
|
||||||
|
err := json.Unmarshal(resp.Body.Bytes(), &listResp)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, listResp.State.Run.Jobs, 3)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d/logs", user2.Name, apiRepo.Name, runIndex, i)).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := NewRequestWithValues(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s", user2.Name, apiRepo.Name, runIndex), map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, session),
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/delete", user2.Name, apiRepo.Name, runIndex), map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, session),
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/delete", user2.Name, apiRepo.Name, runIndex), map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, session),
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
|
||||||
|
req = NewRequestWithValues(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s", user2.Name, apiRepo.Name, runIndex), map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, session),
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
|
||||||
|
for i := 0; i < len(testCase.outcomes); i++ {
|
||||||
|
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d", user2.Name, apiRepo.Name, runIndex, i), map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, session),
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d/logs", user2.Name, apiRepo.Name, runIndex, i)).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -1156,6 +1156,7 @@ jobs:
|
||||||
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
|
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
|
||||||
Title: "add workflow",
|
Title: "add workflow",
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
|
Repo: repo,
|
||||||
Event: "workflow_dispatch",
|
Event: "workflow_dispatch",
|
||||||
Ref: "refs/heads/dispatch",
|
Ref: "refs/heads/dispatch",
|
||||||
WorkflowID: "dispatch.yml",
|
WorkflowID: "dispatch.yml",
|
||||||
|
@ -1448,3 +1449,157 @@ jobs:
|
||||||
assert.Equal(t, pullRequest.MergedCommitID, actionRun.CommitSHA)
|
assert.Equal(t, pullRequest.MergedCommitID, actionRun.CommitSHA)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestActionRunNameWithContextVariables(t *testing.T) {
|
||||||
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
|
// create the repo
|
||||||
|
repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{
|
||||||
|
Name: "action-run-name-with-variables",
|
||||||
|
Description: "test action run name",
|
||||||
|
AutoInit: true,
|
||||||
|
Gitignores: "Go",
|
||||||
|
License: "MIT",
|
||||||
|
Readme: "Default",
|
||||||
|
DefaultBranch: "main",
|
||||||
|
IsPrivate: false,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, repo)
|
||||||
|
|
||||||
|
// add workflow file to the repo
|
||||||
|
addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
|
||||||
|
Files: []*files_service.ChangeRepoFile{
|
||||||
|
{
|
||||||
|
Operation: "create",
|
||||||
|
TreePath: ".gitea/workflows/runname.yml",
|
||||||
|
ContentReader: strings.NewReader(`name: test
|
||||||
|
on:
|
||||||
|
[create,delete]
|
||||||
|
run-name: ${{ gitea.actor }} is running this workflow
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo helloworld
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Message: "add workflow with run-name",
|
||||||
|
OldBranch: "main",
|
||||||
|
NewBranch: "main",
|
||||||
|
Author: &files_service.IdentityOptions{
|
||||||
|
GitUserName: user2.Name,
|
||||||
|
GitUserEmail: user2.Email,
|
||||||
|
},
|
||||||
|
Committer: &files_service.IdentityOptions{
|
||||||
|
GitUserName: user2.Name,
|
||||||
|
GitUserEmail: user2.Email,
|
||||||
|
},
|
||||||
|
Dates: &files_service.CommitDateOptions{
|
||||||
|
Author: time.Now(),
|
||||||
|
Committer: time.Now(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, addWorkflowToBaseResp)
|
||||||
|
|
||||||
|
// Get the commit ID of the default branch
|
||||||
|
gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
branch, err := git_model.GetBranch(db.DefaultContext, repo.ID, repo.DefaultBranch)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// create a branch
|
||||||
|
err = repo_service.CreateNewBranchFromCommit(db.DefaultContext, user2, repo, gitRepo, branch.CommitID, "test-action-run-name-with-variables")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
|
||||||
|
Title: user2.LoginName + " is running this workflow",
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Event: "create",
|
||||||
|
Ref: "refs/heads/test-action-run-name-with-variables",
|
||||||
|
WorkflowID: "runname.yml",
|
||||||
|
CommitSHA: branch.CommitID,
|
||||||
|
})
|
||||||
|
assert.NotNil(t, run)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionRunName(t *testing.T) {
|
||||||
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
|
// create the repo
|
||||||
|
repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{
|
||||||
|
Name: "action-run-name",
|
||||||
|
Description: "test action run-name",
|
||||||
|
AutoInit: true,
|
||||||
|
Gitignores: "Go",
|
||||||
|
License: "MIT",
|
||||||
|
Readme: "Default",
|
||||||
|
DefaultBranch: "main",
|
||||||
|
IsPrivate: false,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, repo)
|
||||||
|
|
||||||
|
// add workflow file to the repo
|
||||||
|
addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
|
||||||
|
Files: []*files_service.ChangeRepoFile{
|
||||||
|
{
|
||||||
|
Operation: "create",
|
||||||
|
TreePath: ".gitea/workflows/runname.yml",
|
||||||
|
ContentReader: strings.NewReader(`name: test
|
||||||
|
on:
|
||||||
|
[create,delete]
|
||||||
|
run-name: run name without variables
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo helloworld
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Message: "add workflow with run name",
|
||||||
|
OldBranch: "main",
|
||||||
|
NewBranch: "main",
|
||||||
|
Author: &files_service.IdentityOptions{
|
||||||
|
GitUserName: user2.Name,
|
||||||
|
GitUserEmail: user2.Email,
|
||||||
|
},
|
||||||
|
Committer: &files_service.IdentityOptions{
|
||||||
|
GitUserName: user2.Name,
|
||||||
|
GitUserEmail: user2.Email,
|
||||||
|
},
|
||||||
|
Dates: &files_service.CommitDateOptions{
|
||||||
|
Author: time.Now(),
|
||||||
|
Committer: time.Now(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, addWorkflowToBaseResp)
|
||||||
|
|
||||||
|
// Get the commit ID of the default branch
|
||||||
|
gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
branch, err := git_model.GetBranch(db.DefaultContext, repo.ID, repo.DefaultBranch)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// create a branch
|
||||||
|
err = repo_service.CreateNewBranchFromCommit(db.DefaultContext, user2, repo, gitRepo, branch.CommitID, "test-action-run-name")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
|
||||||
|
Title: "run name without variables",
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Event: "create",
|
||||||
|
Ref: "refs/heads/test-action-run-name",
|
||||||
|
WorkflowID: "runname.yml",
|
||||||
|
CommitSHA: branch.CommitID,
|
||||||
|
})
|
||||||
|
assert.NotNil(t, run)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -72,12 +73,37 @@ func TestAdminDeleteUser(t *testing.T) {
|
||||||
|
|
||||||
session := loginUser(t, "user1")
|
session := loginUser(t, "user1")
|
||||||
|
|
||||||
|
usersToDelete := []struct {
|
||||||
|
userID int64
|
||||||
|
purge bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
userID: 2,
|
||||||
|
purge: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userID: 8,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range usersToDelete {
|
||||||
|
t.Run(fmt.Sprintf("DeleteUser%d", entry.userID), func(t *testing.T) {
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: entry.userID})
|
||||||
|
assert.NotNil(t, user)
|
||||||
|
|
||||||
|
var query string
|
||||||
|
if entry.purge {
|
||||||
|
query = "?purge=true"
|
||||||
|
}
|
||||||
|
|
||||||
csrf := GetUserCSRFToken(t, session)
|
csrf := GetUserCSRFToken(t, session)
|
||||||
req := NewRequestWithValues(t, "POST", "/-/admin/users/8/delete", map[string]string{
|
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/-/admin/users/%d/delete%s", entry.userID, query), map[string]string{
|
||||||
"_csrf": csrf,
|
"_csrf": csrf,
|
||||||
})
|
})
|
||||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
|
||||||
assertUserDeleted(t, 8)
|
assertUserDeleted(t, entry.userID)
|
||||||
unittest.CheckConsistencyFor(t, &user_model.User{})
|
unittest.CheckConsistencyFor(t, &user_model.User{})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/json"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAPIActionsDeleteRunCheckPermission(t *testing.T) {
|
||||||
|
defer prepareTestEnvActionsArtifacts(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
testAPIActionsDeleteRun(t, repo, token, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIActionsDeleteRun(t *testing.T) {
|
||||||
|
defer prepareTestEnvActionsArtifacts(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
testAPIActionsDeleteRunListArtifacts(t, repo, token, 2)
|
||||||
|
testAPIActionsDeleteRunListTasks(t, repo, token, true)
|
||||||
|
testAPIActionsDeleteRun(t, repo, token, http.StatusNoContent)
|
||||||
|
|
||||||
|
testAPIActionsDeleteRunListArtifacts(t, repo, token, 0)
|
||||||
|
testAPIActionsDeleteRunListTasks(t, repo, token, false)
|
||||||
|
testAPIActionsDeleteRun(t, repo, token, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIActionsDeleteRunRunning(t *testing.T) {
|
||||||
|
defer prepareTestEnvActionsArtifacts(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/runs/793", repo.FullName())).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAPIActionsDeleteRun(t *testing.T, repo *repo_model.Repository, token string, expected int) {
|
||||||
|
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795", repo.FullName())).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAPIActionsDeleteRunListArtifacts(t *testing.T, repo *repo_model.Repository, token string, artifacts int) {
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/artifacts", repo.FullName())).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
var listResp api.ActionArtifactsResponse
|
||||||
|
err := json.Unmarshal(resp.Body.Bytes(), &listResp)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, listResp.Entries, artifacts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAPIActionsDeleteRunListTasks(t *testing.T, repo *repo_model.Repository, token string, expected bool) {
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/tasks", repo.FullName())).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
var listResp api.ActionTaskResponse
|
||||||
|
err := json.Unmarshal(resp.Body.Bytes(), &listResp)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
findTask1 := false
|
||||||
|
findTask2 := false
|
||||||
|
for _, entry := range listResp.Entries {
|
||||||
|
if entry.ID == 53 {
|
||||||
|
findTask1 = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if entry.ID == 54 {
|
||||||
|
findTask2 = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, findTask1)
|
||||||
|
assert.Equal(t, expected, findTask2)
|
||||||
|
}
|
|
@ -41,8 +41,6 @@ func testActionsRunnerAdmin(t *testing.T) {
|
||||||
runnerList := api.ActionRunnersResponse{}
|
runnerList := api.ActionRunnersResponse{}
|
||||||
DecodeJSON(t, runnerListResp, &runnerList)
|
DecodeJSON(t, runnerListResp, &runnerList)
|
||||||
|
|
||||||
assert.Len(t, runnerList.Entries, 4)
|
|
||||||
|
|
||||||
idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34349 })
|
idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34349 })
|
||||||
require.NotEqual(t, -1, idx)
|
require.NotEqual(t, -1, idx)
|
||||||
expectedRunner := runnerList.Entries[idx]
|
expectedRunner := runnerList.Entries[idx]
|
||||||
|
@ -160,16 +158,20 @@ func testActionsRunnerOwner(t *testing.T) {
|
||||||
runnerList := api.ActionRunnersResponse{}
|
runnerList := api.ActionRunnersResponse{}
|
||||||
DecodeJSON(t, runnerListResp, &runnerList)
|
DecodeJSON(t, runnerListResp, &runnerList)
|
||||||
|
|
||||||
assert.Len(t, runnerList.Entries, 1)
|
idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34347 })
|
||||||
assert.Equal(t, "runner_to_be_deleted-org", runnerList.Entries[0].Name)
|
require.NotEqual(t, -1, idx)
|
||||||
assert.Equal(t, int64(34347), runnerList.Entries[0].ID)
|
expectedRunner := runnerList.Entries[idx]
|
||||||
assert.False(t, runnerList.Entries[0].Ephemeral)
|
|
||||||
assert.Len(t, runnerList.Entries[0].Labels, 2)
|
require.NotNil(t, expectedRunner)
|
||||||
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
|
assert.Equal(t, "runner_to_be_deleted-org", expectedRunner.Name)
|
||||||
assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
|
assert.Equal(t, int64(34347), expectedRunner.ID)
|
||||||
|
assert.False(t, expectedRunner.Ephemeral)
|
||||||
|
assert.Len(t, expectedRunner.Labels, 2)
|
||||||
|
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
|
||||||
|
assert.Equal(t, "linux", expectedRunner.Labels[1].Name)
|
||||||
|
|
||||||
// Verify get the runner by id
|
// Verify get the runner by id
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
|
||||||
runnerResp := MakeRequest(t, req, http.StatusOK)
|
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
runner := api.ActionRunner{}
|
runner := api.ActionRunner{}
|
||||||
|
@ -183,11 +185,11 @@ func testActionsRunnerOwner(t *testing.T) {
|
||||||
assert.Equal(t, "linux", runner.Labels[1].Name)
|
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||||
|
|
||||||
// Verify delete the runner by id
|
// Verify delete the runner by id
|
||||||
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
|
||||||
MakeRequest(t, req, http.StatusNoContent)
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
// Verify runner deletion
|
// Verify runner deletion
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -329,4 +331,12 @@ func testActionsRunnerRepo(t *testing.T) {
|
||||||
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
|
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("DeleteAdminRunnerNotFoundUnknownID", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
// Verify delete a runner by unknown id is not found
|
||||||
|
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 4384797347934)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -100,13 +101,16 @@ func TestEmptyRepoAddFile(t *testing.T) {
|
||||||
assert.Contains(t, resp.Body.String(), "test-file.md")
|
assert.Contains(t, resp.Body.String(), "test-file.md")
|
||||||
|
|
||||||
// if the repo is in incorrect state, it should be able to self-heal (recover to correct state)
|
// if the repo is in incorrect state, it should be able to self-heal (recover to correct state)
|
||||||
|
testEmptyOrBrokenRecover := func(t *testing.T, isEmpty, isBroken bool) {
|
||||||
user30EmptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"})
|
user30EmptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"})
|
||||||
user30EmptyRepo.IsEmpty = true
|
user30EmptyRepo.IsEmpty = isEmpty
|
||||||
|
user30EmptyRepo.Status = util.Iif(isBroken, repo_model.RepositoryBroken, repo_model.RepositoryReady)
|
||||||
user30EmptyRepo.DefaultBranch = "no-such"
|
user30EmptyRepo.DefaultBranch = "no-such"
|
||||||
_, err := db.GetEngine(db.DefaultContext).ID(user30EmptyRepo.ID).Cols("is_empty", "default_branch").Update(user30EmptyRepo)
|
_, err := db.GetEngine(db.DefaultContext).ID(user30EmptyRepo.ID).Cols("is_empty", "status", "default_branch").Update(user30EmptyRepo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
user30EmptyRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"})
|
user30EmptyRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 30, Name: "empty"})
|
||||||
assert.True(t, user30EmptyRepo.IsEmpty)
|
assert.Equal(t, isEmpty, user30EmptyRepo.IsEmpty)
|
||||||
|
assert.Equal(t, isBroken, user30EmptyRepo.Status == repo_model.RepositoryBroken)
|
||||||
|
|
||||||
req = NewRequest(t, "GET", "/user30/empty")
|
req = NewRequest(t, "GET", "/user30/empty")
|
||||||
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
@ -117,6 +121,10 @@ func TestEmptyRepoAddFile(t *testing.T) {
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
assert.Contains(t, resp.Body.String(), "test-file.md")
|
assert.Contains(t, resp.Body.String(), "test-file.md")
|
||||||
}
|
}
|
||||||
|
testEmptyOrBrokenRecover(t, true, false)
|
||||||
|
testEmptyOrBrokenRecover(t, false, true)
|
||||||
|
testEmptyOrBrokenRecover(t, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
func TestEmptyRepoUploadFile(t *testing.T) {
|
func TestEmptyRepoUploadFile(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
user_service "code.gitea.io/gitea/services/user"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEphemeralActionsRunnerDeletion(t *testing.T) {
|
||||||
|
t.Run("ByTaskCompletion", testEphemeralActionsRunnerDeletionByTaskCompletion)
|
||||||
|
t.Run("ByRepository", testEphemeralActionsRunnerDeletionByRepository)
|
||||||
|
t.Run("ByUser", testEphemeralActionsRunnerDeletionByUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that the ephemeral runner is deleted when the task is finished
|
||||||
|
func testEphemeralActionsRunnerDeletionByTaskCompletion(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
_, err := actions_model.GetRunnerByID(t.Context(), 34350)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 52})
|
||||||
|
assert.Equal(t, actions_model.StatusRunning, task.Status)
|
||||||
|
|
||||||
|
task.Status = actions_model.StatusSuccess
|
||||||
|
err = actions_model.UpdateTask(t.Context(), task, "status")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = actions_model.GetRunnerByID(t.Context(), 34350)
|
||||||
|
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEphemeralActionsRunnerDeletionByRepository(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
_, err := actions_model.GetRunnerByID(t.Context(), 34350)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 52})
|
||||||
|
assert.Equal(t, actions_model.StatusRunning, task.Status)
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
|
err = repo_service.DeleteRepositoryDirectly(t.Context(), user, task.RepoID, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = actions_model.GetRunnerByID(t.Context(), 34350)
|
||||||
|
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that the ephemeral runner is deleted when a user is deleted
|
||||||
|
func testEphemeralActionsRunnerDeletionByUser(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
_, err := actions_model.GetRunnerByID(t.Context(), 34350)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 52})
|
||||||
|
assert.Equal(t, actions_model.StatusRunning, task.Status)
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
|
err = user_service.DeleteUser(t.Context(), user, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = actions_model.GetRunnerByID(t.Context(), 34350)
|
||||||
|
assert.ErrorIs(t, err, util.ErrNotExist)
|
||||||
|
}
|
|
@ -184,6 +184,15 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content,
|
||||||
return int64(id)
|
return int64(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testIssueChangeMilestone(t *testing.T, session *TestSession, repoLink string, issueID, milestoneID int64) {
|
||||||
|
req := NewRequestWithValues(t, "POST", fmt.Sprintf(repoLink+"/issues/milestone?issue_ids=%d", issueID), map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, session),
|
||||||
|
"id": strconv.FormatInt(milestoneID, 10),
|
||||||
|
})
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, `{"ok":true}`, strings.TrimSpace(resp.Body.String()))
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewIssue(t *testing.T) {
|
func TestNewIssue(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
|
|
|
@ -56,8 +56,12 @@ func TestNewWebHookLink(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAPICreateWebhookForRepo(t *testing.T, session *TestSession, userName, repoName, url, event string) {
|
func testAPICreateWebhookForRepo(t *testing.T, session *TestSession, userName, repoName, url, event string, branchFilter ...string) {
|
||||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
|
||||||
|
var branchFilterString string
|
||||||
|
if len(branchFilter) > 0 {
|
||||||
|
branchFilterString = branchFilter[0]
|
||||||
|
}
|
||||||
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+userName+"/"+repoName+"/hooks", api.CreateHookOption{
|
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+userName+"/"+repoName+"/hooks", api.CreateHookOption{
|
||||||
Type: "gitea",
|
Type: "gitea",
|
||||||
Config: api.CreateHookOptionConfig{
|
Config: api.CreateHookOptionConfig{
|
||||||
|
@ -66,6 +70,7 @@ func testAPICreateWebhookForRepo(t *testing.T, session *TestSession, userName, r
|
||||||
},
|
},
|
||||||
Events: []string{event},
|
Events: []string{event},
|
||||||
Active: true,
|
Active: true,
|
||||||
|
BranchFilter: branchFilterString,
|
||||||
}).AddTokenAuth(token)
|
}).AddTokenAuth(token)
|
||||||
MakeRequest(t, req, http.StatusCreated)
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
}
|
}
|
||||||
|
@ -241,6 +246,7 @@ func Test_WebhookIssueComment(t *testing.T) {
|
||||||
|
|
||||||
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_comment")
|
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_comment")
|
||||||
|
|
||||||
|
t.Run("create comment", func(t *testing.T) {
|
||||||
// 2. trigger the webhook
|
// 2. trigger the webhook
|
||||||
issueURL := testNewIssue(t, session, "user2", "repo1", "Title2", "Description2")
|
issueURL := testNewIssue(t, session, "user2", "repo1", "Title2", "Description2")
|
||||||
testIssueAddComment(t, session, issueURL, "issue title2 comment1", "")
|
testIssueAddComment(t, session, issueURL, "issue title2 comment1", "")
|
||||||
|
@ -255,6 +261,54 @@ func Test_WebhookIssueComment(t *testing.T) {
|
||||||
assert.Equal(t, "Description2", payloads[0].Issue.Body)
|
assert.Equal(t, "Description2", payloads[0].Issue.Body)
|
||||||
assert.Equal(t, "issue title2 comment1", payloads[0].Comment.Body)
|
assert.Equal(t, "issue title2 comment1", payloads[0].Comment.Body)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("update comment", func(t *testing.T) {
|
||||||
|
payloads = make([]api.IssueCommentPayload, 0, 2)
|
||||||
|
triggeredEvent = ""
|
||||||
|
|
||||||
|
// 2. trigger the webhook
|
||||||
|
issueURL := testNewIssue(t, session, "user2", "repo1", "Title3", "Description3")
|
||||||
|
commentID := testIssueAddComment(t, session, issueURL, "issue title3 comment1", "")
|
||||||
|
modifiedContent := "issue title2 comment1 - modified"
|
||||||
|
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, session),
|
||||||
|
"content": modifiedContent,
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
// 3. validate the webhook is triggered
|
||||||
|
assert.Equal(t, "issue_comment", triggeredEvent)
|
||||||
|
assert.Len(t, payloads, 2)
|
||||||
|
assert.EqualValues(t, "edited", payloads[1].Action)
|
||||||
|
assert.Equal(t, "repo1", payloads[1].Issue.Repo.Name)
|
||||||
|
assert.Equal(t, "user2/repo1", payloads[1].Issue.Repo.FullName)
|
||||||
|
assert.Equal(t, "Title3", payloads[1].Issue.Title)
|
||||||
|
assert.Equal(t, "Description3", payloads[1].Issue.Body)
|
||||||
|
assert.Equal(t, modifiedContent, payloads[1].Comment.Body)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Update comment with no content change", func(t *testing.T) {
|
||||||
|
payloads = make([]api.IssueCommentPayload, 0, 2)
|
||||||
|
triggeredEvent = ""
|
||||||
|
commentContent := "issue title3 comment1"
|
||||||
|
|
||||||
|
// 2. trigger the webhook
|
||||||
|
issueURL := testNewIssue(t, session, "user2", "repo1", "Title3", "Description3")
|
||||||
|
commentID := testIssueAddComment(t, session, issueURL, commentContent, "")
|
||||||
|
|
||||||
|
payloads = make([]api.IssueCommentPayload, 0, 2)
|
||||||
|
triggeredEvent = ""
|
||||||
|
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, session),
|
||||||
|
"content": commentContent,
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
// 3. validate the webhook is not triggered because no content change
|
||||||
|
assert.Empty(t, triggeredEvent)
|
||||||
|
assert.Empty(t, payloads)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_WebhookRelease(t *testing.T) {
|
func Test_WebhookRelease(t *testing.T) {
|
||||||
|
@ -322,6 +376,45 @@ func Test_WebhookPush(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_WebhookPushDevBranch(t *testing.T) {
|
||||||
|
var payloads []api.PushPayload
|
||||||
|
var triggeredEvent string
|
||||||
|
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||||
|
content, _ := io.ReadAll(r.Body)
|
||||||
|
var payload api.PushPayload
|
||||||
|
err := json.Unmarshal(content, &payload)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
payloads = append(payloads, payload)
|
||||||
|
triggeredEvent = "push"
|
||||||
|
}, http.StatusOK)
|
||||||
|
defer provider.Close()
|
||||||
|
|
||||||
|
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||||
|
// 1. create a new webhook with special webhook for repo1
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
|
// only for dev branch
|
||||||
|
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push", "develop")
|
||||||
|
|
||||||
|
// 2. this should not trigger the webhook
|
||||||
|
testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push")
|
||||||
|
assert.Empty(t, triggeredEvent)
|
||||||
|
assert.Empty(t, payloads)
|
||||||
|
|
||||||
|
// 3. trigger the webhook
|
||||||
|
testCreateFile(t, session, "user2", "repo1", "develop", "test_webhook_push.md", "# a test file for webhook push")
|
||||||
|
|
||||||
|
// 4. validate the webhook is triggered
|
||||||
|
assert.Equal(t, "push", triggeredEvent)
|
||||||
|
assert.Len(t, payloads, 1)
|
||||||
|
assert.Equal(t, "repo1", payloads[0].Repo.Name)
|
||||||
|
assert.Equal(t, "develop", payloads[0].Branch())
|
||||||
|
assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName)
|
||||||
|
assert.Len(t, payloads[0].Commits, 1)
|
||||||
|
assert.Equal(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func Test_WebhookIssue(t *testing.T) {
|
func Test_WebhookIssue(t *testing.T) {
|
||||||
var payloads []api.IssuePayload
|
var payloads []api.IssuePayload
|
||||||
var triggeredEvent string
|
var triggeredEvent string
|
||||||
|
@ -355,6 +448,78 @@ func Test_WebhookIssue(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_WebhookIssueMilestone(t *testing.T) {
|
||||||
|
var payloads []api.IssuePayload
|
||||||
|
var triggeredEvent string
|
||||||
|
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||||
|
content, _ := io.ReadAll(r.Body)
|
||||||
|
var payload api.IssuePayload
|
||||||
|
err := json.Unmarshal(content, &payload)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
payloads = append(payloads, payload)
|
||||||
|
triggeredEvent = "issues"
|
||||||
|
}, http.StatusOK)
|
||||||
|
defer provider.Close()
|
||||||
|
|
||||||
|
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||||
|
// create a new webhook with special webhook for repo1
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
|
||||||
|
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_milestone")
|
||||||
|
|
||||||
|
t.Run("assign a milestone", func(t *testing.T) {
|
||||||
|
// trigger the webhook
|
||||||
|
testIssueChangeMilestone(t, session, repo1.Link(), 1, 1)
|
||||||
|
|
||||||
|
// validate the webhook is triggered
|
||||||
|
assert.Equal(t, "issues", triggeredEvent)
|
||||||
|
assert.Len(t, payloads, 1)
|
||||||
|
assert.Equal(t, "milestoned", string(payloads[0].Action))
|
||||||
|
assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
|
||||||
|
assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
|
||||||
|
assert.Equal(t, "issue1", payloads[0].Issue.Title)
|
||||||
|
assert.Equal(t, "content for the first issue", payloads[0].Issue.Body)
|
||||||
|
assert.EqualValues(t, 1, payloads[0].Issue.Milestone.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("change a milestong", func(t *testing.T) {
|
||||||
|
// trigger the webhook again
|
||||||
|
triggeredEvent = ""
|
||||||
|
payloads = make([]api.IssuePayload, 0, 1)
|
||||||
|
// change milestone to 2
|
||||||
|
testIssueChangeMilestone(t, session, repo1.Link(), 1, 2)
|
||||||
|
|
||||||
|
// validate the webhook is triggered
|
||||||
|
assert.Equal(t, "issues", triggeredEvent)
|
||||||
|
assert.Len(t, payloads, 1)
|
||||||
|
assert.Equal(t, "milestoned", string(payloads[0].Action))
|
||||||
|
assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
|
||||||
|
assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
|
||||||
|
assert.Equal(t, "issue1", payloads[0].Issue.Title)
|
||||||
|
assert.Equal(t, "content for the first issue", payloads[0].Issue.Body)
|
||||||
|
assert.EqualValues(t, 2, payloads[0].Issue.Milestone.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("remove a milestone", func(t *testing.T) {
|
||||||
|
// trigger the webhook again
|
||||||
|
triggeredEvent = ""
|
||||||
|
payloads = make([]api.IssuePayload, 0, 1)
|
||||||
|
// change milestone to 0
|
||||||
|
testIssueChangeMilestone(t, session, repo1.Link(), 1, 0)
|
||||||
|
|
||||||
|
// validate the webhook is triggered
|
||||||
|
assert.Equal(t, "issues", triggeredEvent)
|
||||||
|
assert.Len(t, payloads, 1)
|
||||||
|
assert.Equal(t, "demilestoned", string(payloads[0].Action))
|
||||||
|
assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
|
||||||
|
assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
|
||||||
|
assert.Equal(t, "issue1", payloads[0].Issue.Title)
|
||||||
|
assert.Equal(t, "content for the first issue", payloads[0].Issue.Body)
|
||||||
|
assert.Nil(t, payloads[0].Issue.Milestone)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func Test_WebhookPullRequest(t *testing.T) {
|
func Test_WebhookPullRequest(t *testing.T) {
|
||||||
var payloads []api.PullRequestPayload
|
var payloads []api.PullRequestPayload
|
||||||
var triggeredEvent string
|
var triggeredEvent string
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
.project-header {
|
.project-header {
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0;
|
||||||
overflow-x: auto; /* in fullscreen mode, the position is fixed, so we can't use "flex wrap" which would change the height */
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-header h2 {
|
.project-header h2 {
|
||||||
|
@ -101,17 +101,11 @@
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen.projects-view .project-header {
|
.fullscreen.projects-view {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
z-index: 1000;
|
inset: 0;
|
||||||
top: 0;
|
display: flex;
|
||||||
left: 0;
|
flex-direction: column;
|
||||||
right: 0;
|
|
||||||
padding: 0.5em;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
background-color: var(--color-body);
|
|
||||||
border-bottom: 1px solid var(--color-secondary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide project-description in full-screen due to its variable height. No need to show it for better space use. */
|
/* Hide project-description in full-screen due to its variable height. No need to show it for better space use. */
|
||||||
|
@ -120,9 +114,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen.projects-view #project-board {
|
.fullscreen.projects-view #project-board {
|
||||||
position: absolute;
|
flex: 1;
|
||||||
top: 60px;
|
max-height: unset;
|
||||||
left: 0;
|
padding-bottom: 0.5em;
|
||||||
right: 0;
|
|
||||||
max-height: calc(100vh - 70px);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,6 +161,7 @@ export function autosize(textarea: HTMLTextAreaElement, {viewportMarginBottom =
|
||||||
function resizeToFit() {
|
function resizeToFit() {
|
||||||
if (isUserResized) return;
|
if (isUserResized) return;
|
||||||
if (textarea.offsetWidth <= 0 && textarea.offsetHeight <= 0) return;
|
if (textarea.offsetWidth <= 0 && textarea.offsetHeight <= 0) return;
|
||||||
|
const previousMargin = textarea.style.marginBottom;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {top, bottom} = overflowOffset();
|
const {top, bottom} = overflowOffset();
|
||||||
|
@ -176,6 +177,9 @@ export function autosize(textarea: HTMLTextAreaElement, {viewportMarginBottom =
|
||||||
const curHeight = parseFloat(computedStyle.height);
|
const curHeight = parseFloat(computedStyle.height);
|
||||||
const maxHeight = curHeight + bottom - adjustedViewportMarginBottom;
|
const maxHeight = curHeight + bottom - adjustedViewportMarginBottom;
|
||||||
|
|
||||||
|
// In Firefox, setting auto height momentarily may cause the page to scroll up
|
||||||
|
// unexpectedly, prevent this by setting a temporary margin.
|
||||||
|
textarea.style.marginBottom = `${textarea.clientHeight}px`;
|
||||||
textarea.style.height = 'auto';
|
textarea.style.height = 'auto';
|
||||||
let newHeight = textarea.scrollHeight + borderAddOn;
|
let newHeight = textarea.scrollHeight + borderAddOn;
|
||||||
|
|
||||||
|
@ -196,6 +200,12 @@ export function autosize(textarea: HTMLTextAreaElement, {viewportMarginBottom =
|
||||||
textarea.style.height = `${newHeight}px`;
|
textarea.style.height = `${newHeight}px`;
|
||||||
lastStyleHeight = textarea.style.height;
|
lastStyleHeight = textarea.style.height;
|
||||||
} finally {
|
} finally {
|
||||||
|
// restore previous margin
|
||||||
|
if (previousMargin) {
|
||||||
|
textarea.style.marginBottom = previousMargin;
|
||||||
|
} else {
|
||||||
|
textarea.style.removeProperty('margin-bottom');
|
||||||
|
}
|
||||||
// ensure that the textarea is fully scrolled to the end, when the cursor
|
// ensure that the textarea is fully scrolled to the end, when the cursor
|
||||||
// is at the end during an input event
|
// is at the end during an input event
|
||||||
if (textarea.selectionStart === textarea.selectionEnd &&
|
if (textarea.selectionStart === textarea.selectionEnd &&
|
||||||
|
|
Loading…
Reference in New Issue