mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-18 17:22:56 +02:00
Merge branch 'go-gitea:main' into main
This commit is contained in:
commit
4d7ea0d931
@ -80,6 +80,11 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
|
||||
}
|
||||
|
||||
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()
|
||||
defer cancel()
|
||||
|
||||
|
6
flake.lock
generated
6
flake.lock
generated
@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1739214665,
|
||||
"narHash": "sha256-26L8VAu3/1YRxS8MHgBOyOM8xALdo6N0I04PgorE7UM=",
|
||||
"lastModified": 1747179050,
|
||||
"narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "64e75cd44acf21c7933d61d7721e812eac1b5a0a",
|
||||
"rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e",
|
||||
"type": "github"
|
||||
},
|
||||
"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/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
|
||||
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=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
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.4/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
||||
gitea.com/gitea/act v0.261.6 h1:CjZwKOyejonNFDmsXOw3wGm5Vet573hHM6VMLsxtvPY=
|
||||
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/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
|
||||
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
|
||||
|
@ -82,3 +82,22 @@ func calculateDuration(started, stopped timeutil.TimeStamp, status Status) time.
|
||||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
@ -14,5 +14,8 @@ func AddIndexToActionTaskStoppedLogExpired(x *xorm.Engine) error {
|
||||
Stopped timeutil.TimeStamp `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
|
||||
}
|
||||
|
51
models/migrations/v1_23/v302_test.go
Normal file
51
models/migrations/v1_23/v302_test.go
Normal file
@ -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 {
|
||||
Sha1 string `xorm:"INDEX VARCHAR(64)"`
|
||||
}
|
||||
return x.Sync(new(Release))
|
||||
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||
IgnoreDropIndices: true,
|
||||
}, new(Release))
|
||||
return err
|
||||
}
|
||||
|
40
models/migrations/v1_23/v304_test.go
Normal file
40
models/migrations/v1_23/v304_test.go
Normal file
@ -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))
|
||||
}
|
@ -166,6 +166,8 @@ type CommitsCountOptions struct {
|
||||
Not string
|
||||
Revision []string
|
||||
RelPath []string
|
||||
Since string
|
||||
Until string
|
||||
}
|
||||
|
||||
// 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
|
||||
func (c *Commit) CommitsByRange(page, pageSize int, not string) ([]*Commit, error) {
|
||||
return c.repo.commitsByRange(c.ID, page, pageSize, not)
|
||||
func (c *Commit) CommitsByRange(page, pageSize int, not, since, until string) ([]*Commit, error) {
|
||||
return c.repo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until)
|
||||
}
|
||||
|
||||
// CommitsBefore returns all the commits before current revision
|
||||
|
@ -89,7 +89,8 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
|
||||
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").
|
||||
AddOptionFormat("--skip=%d", (page-1)*pageSize).
|
||||
AddOptionFormat("--max-count=%d", pageSize).
|
||||
@ -99,6 +100,12 @@ func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not stri
|
||||
if 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})
|
||||
if err != nil {
|
||||
@ -212,6 +219,8 @@ type CommitsByFileAndRangeOptions struct {
|
||||
File string
|
||||
Not string
|
||||
Page int
|
||||
Since string
|
||||
Until string
|
||||
}
|
||||
|
||||
// CommitsByFileAndRange return the commits according revision file and the page
|
||||
@ -231,6 +240,12 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
|
||||
if 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)
|
||||
err := gitCmd.Run(repo.Ctx, &RunOpts{
|
||||
|
@ -40,7 +40,9 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
|
||||
|
||||
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 {
|
||||
return nil, runErr
|
||||
}
|
||||
@ -60,7 +62,8 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
|
||||
_ = 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 {
|
||||
gitCmd.AddArguments("--branches=*")
|
||||
} else {
|
||||
|
@ -200,5 +200,3 @@ func TestListToPushCommits(t *testing.T) {
|
||||
assert.Equal(t, now, pushCommits.Commits[1].Timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO TestPushUpdate
|
||||
|
@ -101,6 +101,8 @@ type Repository struct {
|
||||
AllowSquash bool `json:"allow_squash_merge"`
|
||||
AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge"`
|
||||
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"`
|
||||
DefaultMergeStyle string `json:"default_merge_style"`
|
||||
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>
|
||||
Robert Nuske <robert DOT nuske AT web DOT de>
|
||||
Robin Hübner <profan AT prfn DOT se>
|
||||
Ryo Hanafusa <ryo7gumi AT gmail DOT com>
|
||||
SeongJae Park <sj38 DOT park AT gmail DOT com>
|
||||
Thiago Avelino <thiago AT avelino DOT xxx>
|
||||
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
|
||||
|
@ -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.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.description=La suppression d'un secret est permanente et irréversible. Continuer ?
|
||||
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.empty_commit_message=(message de révision vide)
|
||||
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_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.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.description=Is buan rún a bhaint agus ní féidir é a chealú. Lean ort?
|
||||
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.empty_commit_message=(teachtaireacht tiomantas folamh)
|
||||
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_success=D'éirigh le sreabhadh oibre '%s' a dhíchumasú.
|
||||
|
@ -450,6 +450,7 @@ use_scratch_code=スクラッチコードを使う
|
||||
twofa_scratch_used=あなたはスクラッチコードを使用しました。 2要素認証の設定ページにリダイレクトしましたので、デバイスの登録を解除するか、新しいスクラッチコードを生成しましょう。
|
||||
twofa_passcode_incorrect=パスコードが正しくありません。デバイスを紛失した場合は、スクラッチコードを使ってサインインしてください。
|
||||
twofa_scratch_token_incorrect=スクラッチコードが正しくありません。
|
||||
twofa_required=リポジトリにアクセスするには2段階認証を設定するか、再度ログインしてください。
|
||||
login_userpass=サインイン
|
||||
login_openid=OpenID
|
||||
oauth_signup_tab=新規アカウント登録
|
||||
@ -1878,6 +1879,7 @@ pulls.add_prefix=先頭に <strong>%s</strong> を追加
|
||||
pulls.remove_prefix=先頭の <strong>%s</strong> を除去
|
||||
pulls.data_broken=このプルリクエストは、フォークの情報が見つからないため壊れています。
|
||||
pulls.files_conflicted=このプルリクエストは、ターゲットブランチと競合する変更を含んでいます。
|
||||
pulls.is_checking=マージの競合を確認しています...
|
||||
pulls.is_ancestor=このブランチは既にターゲットブランチに含まれています。マージするものはありません。
|
||||
pulls.is_empty=このブランチの変更は既にターゲットブランチにあります。これは空のコミットになります。
|
||||
pulls.required_status_check_failed=いくつかの必要なステータスチェックが成功していません。
|
||||
@ -3725,7 +3727,11 @@ creation.name_placeholder=大文字小文字の区別なし、英数字とアン
|
||||
creation.value_placeholder=内容を入力してください。前後の空白は除去されます。
|
||||
creation.description_placeholder=簡単な説明を入力してください。 (オプション)
|
||||
|
||||
save_success=シークレット "%s" を保存しました。
|
||||
save_failed=シークレットの保存に失敗しました。
|
||||
|
||||
add_secret=シークレットを追加
|
||||
edit_secret=シークレットを編集
|
||||
deletion=シークレットの削除
|
||||
deletion.description=シークレットの削除は恒久的で元に戻すことはできません。 続行しますか?
|
||||
deletion.success=シークレットを削除しました。
|
||||
@ -3842,6 +3848,8 @@ deleted.display_name=削除されたプロジェクト
|
||||
type-1.display_name=個人プロジェクト
|
||||
type-2.display_name=リポジトリ プロジェクト
|
||||
type-3.display_name=組織プロジェクト
|
||||
enter_fullscreen=フルスクリーン
|
||||
exit_fullscreen=フルスクリーンを終了
|
||||
|
||||
[git.filemode]
|
||||
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.empty_commit_message=(mensagem de cometimento vazia)
|
||||
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_success=A sequência de trabalho '%s' foi desabilitada com sucesso.
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@ -116,6 +117,16 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||
// in: query
|
||||
// description: filepath of a file/dir
|
||||
// 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
|
||||
// in: query
|
||||
// description: include diff stats for every commit (disable for speedup, default 'true')
|
||||
@ -148,6 +159,23 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||
// "409":
|
||||
// "$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 {
|
||||
ctx.JSON(http.StatusConflict, api.APIError{
|
||||
Message: "Git Repository is empty.",
|
||||
@ -198,6 +226,8 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||
RepoPath: ctx.Repo.GitRepo.Path,
|
||||
Not: not,
|
||||
Revision: []string{baseCommit.ID.String()},
|
||||
Since: since,
|
||||
Until: until,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@ -205,7 +235,7 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@ -221,6 +251,8 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||
Not: not,
|
||||
Revision: []string{sha},
|
||||
RelPath: []string{path},
|
||||
Since: since,
|
||||
Until: until,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@ -237,6 +269,8 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||
File: path,
|
||||
Not: not,
|
||||
Page: listOptions.Page,
|
||||
Since: since,
|
||||
Until: until,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
|
@ -67,6 +67,28 @@ func ListRunners(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
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
|
||||
// ownerID == 0 and repoID == 0 means any runner including global runners
|
||||
// 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 {
|
||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||
}
|
||||
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
|
||||
if err != nil {
|
||||
ctx.APIErrorNotFound(err)
|
||||
return
|
||||
}
|
||||
if !runner.EditableInContext(ownerID, repoID) {
|
||||
ctx.APIErrorNotFound("No permission to get this runner")
|
||||
runner, ok := getRunnerByID(ctx, ownerID, repoID, runnerID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
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
|
||||
// Access rights are checked at the API route level
|
||||
func DeleteRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
||||
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 {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if !runner.EditableInContext(ownerID, repoID) {
|
||||
ctx.APIErrorNotFound("No permission to delete this runner")
|
||||
runner, ok := getRunnerByID(ctx, ownerID, repoID, runnerID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err = actions_model.DeleteRunner(ctx, runner.ID)
|
||||
err := actions_model.DeleteRunner(ctx, runner.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
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")
|
||||
return false
|
||||
}
|
||||
if !validation.IsValidURL(form.Config["url"]) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
|
||||
return false
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
// an error, write to `ctx` accordingly. Return (webhook, ok)
|
||||
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
|
||||
}
|
||||
|
||||
if len(form.Events) == 0 {
|
||||
form.Events = []string{"push"}
|
||||
}
|
||||
if form.Config["is_system_webhook"] != "" {
|
||||
sw, err := strconv.ParseBool(form.Config["is_system_webhook"])
|
||||
if err != nil {
|
||||
@ -183,31 +220,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
|
||||
IsSystemWebhook: isSystemWebhook,
|
||||
HookEvent: &webhook_module.HookEvent{
|
||||
ChooseEvents: true,
|
||||
HookEvents: webhook_module.HookEvents{
|
||||
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),
|
||||
},
|
||||
HookEvents: updateHookEvents(form.Events),
|
||||
BranchFilter: form.BranchFilter,
|
||||
},
|
||||
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 {
|
||||
if form.Config != nil {
|
||||
if url, ok := form.Config["url"]; ok {
|
||||
if !validation.IsValidURL(url) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
|
||||
return false
|
||||
}
|
||||
w.URL = url
|
||||
}
|
||||
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
|
||||
if len(form.Events) == 0 {
|
||||
form.Events = []string{"push"}
|
||||
}
|
||||
w.HookEvents = updateHookEvents(form.Events)
|
||||
w.PushOnly = false
|
||||
w.SendEverything = false
|
||||
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
|
||||
|
||||
err := w.SetHeaderAuthorization(form.AuthorizationHeader)
|
||||
@ -373,23 +381,6 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
|
||||
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 {
|
||||
ctx.APIErrorInternal(err)
|
||||
return false
|
||||
|
82
routers/api/v1/utils/hook_test.go
Normal file
82
routers/api/v1/utils/hook_test.go
Normal file
@ -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())
|
||||
})
|
||||
}
|
21
routers/api/v1/utils/main_test.go
Normal file
21
routers/api/v1/utils/main_test.go
Normal file
@ -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()
|
||||
},
|
||||
})
|
||||
}
|
@ -15,7 +15,7 @@ import (
|
||||
|
||||
// ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed
|
||||
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 {
|
||||
ctx.ServerError("ShowBranchFeed", err)
|
||||
return
|
||||
|
@ -611,20 +611,20 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions
|
||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.HTTPError(http.StatusNotFound, err.Error())
|
||||
ctx.NotFound(nil)
|
||||
return nil, nil
|
||||
}
|
||||
ctx.HTTPError(http.StatusInternalServerError, err.Error())
|
||||
ctx.ServerError("GetRunByIndex", err)
|
||||
return nil, nil
|
||||
}
|
||||
run.Repo = ctx.Repo.Repository
|
||||
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
||||
if err != nil {
|
||||
ctx.HTTPError(http.StatusInternalServerError, err.Error())
|
||||
ctx.ServerError("GetRunJobsByRunID", err)
|
||||
return nil, nil
|
||||
}
|
||||
if len(jobs) == 0 {
|
||||
ctx.HTTPError(http.StatusNotFound)
|
||||
ctx.NotFound(nil)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ func Commits(ctx *context.Context) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
ctx.ServerError("CommitsByRange", err)
|
||||
return
|
||||
|
@ -15,11 +15,15 @@ import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"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
|
||||
// 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{}
|
||||
_ = json.Unmarshal([]byte(run.EventPayload), &event)
|
||||
|
||||
@ -42,7 +46,7 @@ func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.Actio
|
||||
|
||||
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
|
||||
"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.
|
||||
@ -160,3 +164,37 @@ func mergeTwoOutputs(o1, o2 map[string]string) map[string]string {
|
||||
}
|
||||
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{
|
||||
Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
|
||||
RepoID: input.Repo.ID,
|
||||
Repo: input.Repo,
|
||||
OwnerID: input.Repo.OwnerID,
|
||||
WorkflowID: dwf.EntryName,
|
||||
TriggerUserID: input.Doer.ID,
|
||||
TriggerUser: input.Doer,
|
||||
Ref: ref,
|
||||
CommitSHA: commit.ID.String(),
|
||||
IsForkPullRequest: isForkPullRequest,
|
||||
@ -333,12 +335,18 @@ func handleWorkflows(
|
||||
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 {
|
||||
log.Error("jobparser.Parse: %v", err)
|
||||
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
|
||||
if run.Event == webhook_module.HookEventPush ||
|
||||
run.Event == webhook_module.HookEventPullRequestSync {
|
||||
@ -508,9 +516,11 @@ func handleSchedules(
|
||||
run := &actions_model.ActionSchedule{
|
||||
Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
|
||||
RepoID: input.Repo.ID,
|
||||
Repo: input.Repo,
|
||||
OwnerID: input.Repo.OwnerID,
|
||||
WorkflowID: dwf.EntryName,
|
||||
TriggerUserID: user_model.ActionsUserID,
|
||||
TriggerUser: user_model.NewActionsUser(),
|
||||
Ref: ref,
|
||||
CommitSHA: commit.ID.String(),
|
||||
Event: input.Event,
|
||||
@ -518,6 +528,25 @@ func handleSchedules(
|
||||
Specs: schedules,
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -192,22 +192,55 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
|
||||
|
||||
// find workflow from commit
|
||||
var workflows []*jobparser.SingleWorkflow
|
||||
for _, entry := range entries {
|
||||
if entry.Name() != workflowID {
|
||||
var entry *git.TreeEntry
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
content, err := actions.GetContentFromEntry(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workflows, err = jobparser.Parse(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
giteaCtx := GenerateGiteaContext(run, nil)
|
||||
|
||||
workflows, err = jobparser.Parse(content, jobparser.WithGitContext(giteaCtx.ToGitHubContext()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(workflows) > 0 && workflows[0].RunName != "" {
|
||||
run.Title = workflows[0].RunName
|
||||
}
|
||||
|
||||
if len(workflows) == 0 {
|
||||
return util.ErrorWrapLocale(
|
||||
util.NewNotExistErrorf("workflow %q doesn't exist", workflowID),
|
||||
@ -236,25 +269,12 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
|
||||
Inputs: inputsWithDefaults,
|
||||
Sender: convert.ToUserWithAccessMode(ctx, doer, perm.AccessModeNone),
|
||||
}
|
||||
|
||||
var eventPayload []byte
|
||||
if eventPayload, err = workflowDispatchPayload.JSONPayload(); err != nil {
|
||||
return fmt.Errorf("JSONPayload: %w", err)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
run.EventPayload = string(eventPayload)
|
||||
|
||||
// cancel running jobs of the same workflow
|
||||
if err := CancelPreviousJobs(
|
||||
@ -280,6 +300,5 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
|
||||
for _, job := range allJobs {
|
||||
notify_service.WorkflowJobStatusUpdate(ctx, repo, doer, job, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -98,6 +98,8 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
||||
allowSquash := false
|
||||
allowFastForwardOnly := false
|
||||
allowRebaseUpdate := false
|
||||
allowManualMerge := true
|
||||
autodetectManualMerge := false
|
||||
defaultDeleteBranchAfterMerge := false
|
||||
defaultMergeStyle := repo_model.MergeStyleMerge
|
||||
defaultAllowMaintainerEdit := false
|
||||
@ -111,6 +113,8 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
||||
allowSquash = config.AllowSquash
|
||||
allowFastForwardOnly = config.AllowFastForwardOnly
|
||||
allowRebaseUpdate = config.AllowRebaseUpdate
|
||||
allowManualMerge = config.AllowManualMerge
|
||||
autodetectManualMerge = config.AutodetectManualMerge
|
||||
defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge
|
||||
defaultMergeStyle = config.GetDefaultMergeStyle()
|
||||
defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit
|
||||
@ -235,6 +239,8 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
||||
AllowSquash: allowSquash,
|
||||
AllowFastForwardOnly: allowFastForwardOnly,
|
||||
AllowRebaseUpdate: allowRebaseUpdate,
|
||||
AllowManualMerge: allowManualMerge,
|
||||
AutodetectManualMerge: autodetectManualMerge,
|
||||
DefaultDeleteBranchAfterMerge: defaultDeleteBranchAfterMerge,
|
||||
DefaultMergeStyle: string(defaultMergeStyle),
|
||||
DefaultAllowMaintainerEdit: defaultAllowMaintainerEdit,
|
||||
|
@ -305,7 +305,7 @@ func parseHookPullRequestEventType(event webhook_module.HookEventType) (string,
|
||||
case webhook_module.HookEventPullRequestReviewApproved:
|
||||
return "approved", nil
|
||||
case webhook_module.HookEventPullRequestReviewRejected:
|
||||
return "rejected", nil
|
||||
return "requested changes", nil
|
||||
case webhook_module.HookEventPullRequestReviewComment:
|
||||
return "comment", nil
|
||||
default:
|
||||
|
22
templates/swagger/v1_json.tmpl
generated
22
templates/swagger/v1_json.tmpl
generated
@ -6604,6 +6604,20 @@
|
||||
"name": "path",
|
||||
"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",
|
||||
"description": "include diff stats for every commit (disable for speedup, default 'true')",
|
||||
@ -26184,6 +26198,10 @@
|
||||
"type": "boolean",
|
||||
"x-go-name": "AllowFastForwardOnly"
|
||||
},
|
||||
"allow_manual_merge": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "AllowManualMerge"
|
||||
},
|
||||
"allow_merge_commits": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "AllowMerge"
|
||||
@ -26213,6 +26231,10 @@
|
||||
"format": "date-time",
|
||||
"x-go-name": "ArchivedAt"
|
||||
},
|
||||
"autodetect_manual_merge": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "AutodetectManualMerge"
|
||||
},
|
||||
"avatar_url": {
|
||||
"type": "string",
|
||||
"x-go-name": "AvatarURL"
|
||||
|
@ -1156,6 +1156,7 @@ jobs:
|
||||
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
|
||||
Title: "add workflow",
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
Event: "workflow_dispatch",
|
||||
Ref: "refs/heads/dispatch",
|
||||
WorkflowID: "dispatch.yml",
|
||||
@ -1448,3 +1449,157 @@ jobs:
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
@ -329,4 +329,12 @@ func testActionsRunnerRepo(t *testing.T) {
|
||||
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
.project-header {
|
||||
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 {
|
||||
@ -101,17 +101,11 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fullscreen.projects-view .project-header {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0.5em;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
background-color: var(--color-body);
|
||||
border-bottom: 1px solid var(--color-secondary);
|
||||
.fullscreen.projects-view {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 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 {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-height: calc(100vh - 70px);
|
||||
flex: 1;
|
||||
max-height: unset;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user