0
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-07-21 16:45:03 +02:00

Merge branch 'main' into non-text-edit

This commit is contained in:
bytedream 2025-05-28 20:19:01 +02:00
commit 35d85dcf09
69 changed files with 774 additions and 759 deletions

View File

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
pgsql: pgsql:
image: postgres:12 image: postgres:14
env: env:
POSTGRES_DB: test POSTGRES_DB: test
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres

View File

@ -7,6 +7,7 @@
target_url: https://example.com/builds/ target_url: https://example.com/builds/
description: My awesome CI-service description: My awesome CI-service
context: ci/awesomeness context: ci/awesomeness
context_hash: c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7
creator_id: 2 creator_id: 2
- -
@ -18,6 +19,7 @@
target_url: https://example.com/converage/ target_url: https://example.com/converage/
description: My awesome Coverage service description: My awesome Coverage service
context: cov/awesomeness context: cov/awesomeness
context_hash: 3929ac7bccd3fa1bf9b38ddedb77973b1b9a8cfe
creator_id: 2 creator_id: 2
- -
@ -29,6 +31,7 @@
target_url: https://example.com/converage/ target_url: https://example.com/converage/
description: My awesome Coverage service description: My awesome Coverage service
context: cov/awesomeness context: cov/awesomeness
context_hash: 3929ac7bccd3fa1bf9b38ddedb77973b1b9a8cfe
creator_id: 2 creator_id: 2
- -
@ -40,6 +43,7 @@
target_url: https://example.com/builds/ target_url: https://example.com/builds/
description: My awesome CI-service description: My awesome CI-service
context: ci/awesomeness context: ci/awesomeness
context_hash: c65f4d64a3b14a3eced0c9b36799e66e1bd5ced7
creator_id: 2 creator_id: 2
- -
@ -51,4 +55,5 @@
target_url: https://example.com/builds/ target_url: https://example.com/builds/
description: My awesome deploy service description: My awesome deploy service
context: deploy/awesomeness context: deploy/awesomeness
context_hash: ae9547713a6665fc4261d0756904932085a41cf2
creator_id: 2 creator_id: 2

View File

@ -230,18 +230,24 @@ func (status *CommitStatus) HideActionsURL(ctx context.Context) {
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc // CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc
func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus { func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus {
// This function is widely used, but it is not quite right.
// Ideally it should return something like "CommitStatusSummary" with properly aggregated state.
// GitHub's behavior: if all statuses are "skipped", GitHub will return "success" as the combined status.
var lastStatus *CommitStatus var lastStatus *CommitStatus
state := api.CommitStatusSuccess state := api.CommitStatusSuccess
for _, status := range statuses { for _, status := range statuses {
if status.State.NoBetterThan(state) { if state == status.State || status.State.HasHigherPriorityThan(state) {
state = status.State state = status.State
lastStatus = status lastStatus = status
} }
} }
if lastStatus == nil { if lastStatus == nil {
if len(statuses) > 0 { if len(statuses) > 0 {
// FIXME: a bad case: Gitea just returns the first commit status, its status is "skipped" in this case.
lastStatus = statuses[0] lastStatus = statuses[0]
} else { } else {
// FIXME: another bad case: if the "statuses" slice is empty, the returned value is an invalid CommitStatus, all its fields are empty.
// Frontend code (tmpl&vue) sometimes depend on the empty fields to skip rendering commit status elements (need to double check in the future)
lastStatus = &CommitStatus{} lastStatus = &CommitStatus{}
} }
} }
@ -298,27 +304,37 @@ type CommitStatusIndex struct {
MaxIndex int64 `xorm:"index"` MaxIndex int64 `xorm:"index"`
} }
func makeRepoCommitQuery(ctx context.Context, repoID int64, sha string) *xorm.Session {
return db.GetEngine(ctx).Table(&CommitStatus{}).
Where("repo_id = ?", repoID).And("sha = ?", sha)
}
// GetLatestCommitStatus returns all statuses with a unique context for a given commit. // GetLatestCommitStatus returns all statuses with a unique context for a given commit.
func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, int64, error) { func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, error) {
getBase := func() *xorm.Session {
return db.GetEngine(ctx).Table(&CommitStatus{}).
Where("repo_id = ?", repoID).And("sha = ?", sha)
}
indices := make([]int64, 0, 10) indices := make([]int64, 0, 10)
sess := getBase().Select("max( `index` ) as `index`"). sess := makeRepoCommitQuery(ctx, repoID, sha).
GroupBy("context_hash").OrderBy("max( `index` ) desc") Select("max( `index` ) as `index`").
GroupBy("context_hash").
OrderBy("max( `index` ) desc")
if !listOptions.IsListAll() { if !listOptions.IsListAll() {
sess = db.SetSessionPagination(sess, &listOptions) sess = db.SetSessionPagination(sess, &listOptions)
} }
count, err := sess.FindAndCount(&indices) if err := sess.Find(&indices); err != nil {
if err != nil { return nil, err
return nil, count, err
} }
statuses := make([]*CommitStatus, 0, len(indices)) statuses := make([]*CommitStatus, 0, len(indices))
if len(indices) == 0 { if len(indices) == 0 {
return statuses, count, nil return statuses, nil
} }
return statuses, count, getBase().And(builder.In("`index`", indices)).Find(&statuses) err := makeRepoCommitQuery(ctx, repoID, sha).And(builder.In("`index`", indices)).Find(&statuses)
return statuses, err
}
func CountLatestCommitStatus(ctx context.Context, repoID int64, sha string) (int64, error) {
return makeRepoCommitQuery(ctx, repoID, sha).
Select("count(context_hash)").
GroupBy("context_hash").
Count()
} }
// GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs // GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs

View File

@ -55,11 +55,15 @@ func GetLatestCommitStatusForRepoAndSHAs(ctx context.Context, repoSHAs []RepoSHA
} }
func UpdateCommitStatusSummary(ctx context.Context, repoID int64, sha string) error { func UpdateCommitStatusSummary(ctx context.Context, repoID int64, sha string) error {
commitStatuses, _, err := GetLatestCommitStatus(ctx, repoID, sha, db.ListOptionsAll) commitStatuses, err := GetLatestCommitStatus(ctx, repoID, sha, db.ListOptionsAll)
if err != nil { if err != nil {
return err return err
} }
state := CalcCommitStatus(commitStatuses) // it guarantees that commitStatuses is not empty because this function is always called after a commit status is created
if len(commitStatuses) == 0 {
setting.PanicInDevOrTesting("no commit statuses found for repo %d and sha %s", repoID, sha)
}
state := CalcCommitStatus(commitStatuses) // non-empty commitStatuses is guaranteed
// mysql will return 0 when update a record which state hasn't been changed which behaviour is different from other database, // mysql will return 0 when update a record which state hasn't been changed which behaviour is different from other database,
// so we need to use insert in on duplicate // so we need to use insert in on duplicate
if setting.Database.Type.IsMySQL() { if setting.Database.Type.IsMySQL() {

View File

@ -26,7 +26,7 @@ func TestGetCommitStatuses(t *testing.T) {
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
sha1 := "1234123412341234123412341234123412341234" sha1 := "1234123412341234123412341234123412341234" // the mocked commit ID in test fixtures
statuses, maxResults, err := db.FindAndCount[git_model.CommitStatus](db.DefaultContext, &git_model.CommitStatusOptions{ statuses, maxResults, err := db.FindAndCount[git_model.CommitStatus](db.DefaultContext, &git_model.CommitStatusOptions{
ListOptions: db.ListOptions{Page: 1, PageSize: 50}, ListOptions: db.ListOptions{Page: 1, PageSize: 50},
@ -256,3 +256,26 @@ func TestCommitStatusesHideActionsURL(t *testing.T) {
assert.Empty(t, statuses[0].TargetURL) assert.Empty(t, statuses[0].TargetURL)
assert.Equal(t, "https://mycicd.org/1", statuses[1].TargetURL) assert.Equal(t, "https://mycicd.org/1", statuses[1].TargetURL)
} }
func TestGetCountLatestCommitStatus(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
sha1 := "1234123412341234123412341234123412341234" // the mocked commit ID in test fixtures
commitStatuses, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo1.ID, sha1, db.ListOptions{
Page: 1,
PageSize: 2,
})
assert.NoError(t, err)
assert.Len(t, commitStatuses, 2)
assert.Equal(t, structs.CommitStatusFailure, commitStatuses[0].State)
assert.Equal(t, "ci/awesomeness", commitStatuses[0].Context)
assert.Equal(t, structs.CommitStatusError, commitStatuses[1].State)
assert.Equal(t, "deploy/awesomeness", commitStatuses[1].Context)
count, err := git_model.CountLatestCommitStatus(db.DefaultContext, repo1.ID, sha1)
assert.NoError(t, err)
assert.EqualValues(t, 3, count)
}

View File

@ -88,6 +88,8 @@ func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {
sess.Asc("issue.created_unix").Asc("issue.id") sess.Asc("issue.created_unix").Asc("issue.id")
case "recentupdate": case "recentupdate":
sess.Desc("issue.updated_unix").Desc("issue.created_unix").Desc("issue.id") sess.Desc("issue.updated_unix").Desc("issue.created_unix").Desc("issue.id")
case "recentclose":
sess.Desc("issue.closed_unix").Desc("issue.created_unix").Desc("issue.id")
case "leastupdate": case "leastupdate":
sess.Asc("issue.updated_unix").Asc("issue.created_unix").Asc("issue.id") sess.Asc("issue.updated_unix").Asc("issue.created_unix").Asc("issue.id")
case "mostcomment": case "mostcomment":

View File

@ -152,7 +152,8 @@ func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptio
applySorts(findSession, opts.SortType, 0) applySorts(findSession, opts.SortType, 0)
findSession = db.SetSessionPagination(findSession, opts) findSession = db.SetSessionPagination(findSession, opts)
prs := make([]*PullRequest, 0, opts.PageSize) prs := make([]*PullRequest, 0, opts.PageSize)
return prs, maxResults, findSession.Find(&prs) found := findSession.Find(&prs)
return prs, maxResults, found
} }
// PullRequestList defines a list of pull requests // PullRequestList defines a list of pull requests

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestPullRequest_LoadAttributes(t *testing.T) { func TestPullRequest_LoadAttributes(t *testing.T) {
@ -76,6 +77,47 @@ func TestPullRequestsNewest(t *testing.T) {
} }
} }
func TestPullRequests_Closed_RecentSortType(t *testing.T) {
// Issue ID | Closed At. | Updated At
// 2 | 1707270001 | 1707270001
// 3 | 1707271000 | 1707279999
// 11 | 1707279999 | 1707275555
tests := []struct {
sortType string
expectedIssueIDOrder []int64
}{
{"recentupdate", []int64{3, 11, 2}},
{"recentclose", []int64{11, 3, 2}},
}
assert.NoError(t, unittest.PrepareTestDatabase())
_, err := db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707270001, updated_unix = 1707270001, is_closed = true WHERE id = 2")
require.NoError(t, err)
_, err = db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707271000, updated_unix = 1707279999, is_closed = true WHERE id = 3")
require.NoError(t, err)
_, err = db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707279999, updated_unix = 1707275555, is_closed = true WHERE id = 11")
require.NoError(t, err)
for _, test := range tests {
t.Run(test.sortType, func(t *testing.T) {
prs, _, err := issues_model.PullRequests(db.DefaultContext, 1, &issues_model.PullRequestsOptions{
ListOptions: db.ListOptions{
Page: 1,
},
State: "closed",
SortType: test.sortType,
})
require.NoError(t, err)
if assert.Len(t, prs, len(test.expectedIssueIDOrder)) {
for i := range test.expectedIssueIDOrder {
assert.Equal(t, test.expectedIssueIDOrder[i], prs[i].IssueID)
}
}
})
}
}
func TestLoadRequestedReviewers(t *testing.T) { func TestLoadRequestedReviewers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())

View File

@ -137,7 +137,7 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess
// SearchUsers takes options i.e. keyword and part of user name to search, // SearchUsers takes options i.e. keyword and part of user name to search,
// it returns results in given range and number of total results. // it returns results in given range and number of total results.
func SearchUsers(ctx context.Context, opts *SearchUserOptions) (users []*User, _ int64, _ error) { func SearchUsers(ctx context.Context, opts SearchUserOptions) (users []*User, _ int64, _ error) {
sessCount := opts.toSearchQueryBase(ctx) sessCount := opts.toSearchQueryBase(ctx)
defer sessCount.Close() defer sessCount.Close()
count, err := sessCount.Count(new(User)) count, err := sessCount.Count(new(User))
@ -152,7 +152,7 @@ func SearchUsers(ctx context.Context, opts *SearchUserOptions) (users []*User, _
sessQuery := opts.toSearchQueryBase(ctx).OrderBy(opts.OrderBy.String()) sessQuery := opts.toSearchQueryBase(ctx).OrderBy(opts.OrderBy.String())
defer sessQuery.Close() defer sessQuery.Close()
if opts.Page > 0 { if opts.Page > 0 {
sessQuery = db.SetSessionPagination(sessQuery, opts) sessQuery = db.SetSessionPagination(sessQuery, &opts)
} }
// the sql may contain JOIN, so we must only select User related columns // the sql may contain JOIN, so we must only select User related columns

View File

@ -88,7 +88,7 @@ func TestCanCreateOrganization(t *testing.T) {
func TestSearchUsers(t *testing.T) { func TestSearchUsers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
testSuccess := func(opts *user_model.SearchUserOptions, expectedUserOrOrgIDs []int64) { testSuccess := func(opts user_model.SearchUserOptions, expectedUserOrOrgIDs []int64) {
users, _, err := user_model.SearchUsers(db.DefaultContext, opts) users, _, err := user_model.SearchUsers(db.DefaultContext, opts)
assert.NoError(t, err) assert.NoError(t, err)
cassText := fmt.Sprintf("ids: %v, opts: %v", expectedUserOrOrgIDs, opts) cassText := fmt.Sprintf("ids: %v, opts: %v", expectedUserOrOrgIDs, opts)
@ -100,61 +100,61 @@ func TestSearchUsers(t *testing.T) {
} }
// test orgs // test orgs
testOrgSuccess := func(opts *user_model.SearchUserOptions, expectedOrgIDs []int64) { testOrgSuccess := func(opts user_model.SearchUserOptions, expectedOrgIDs []int64) {
opts.Type = user_model.UserTypeOrganization opts.Type = user_model.UserTypeOrganization
testSuccess(opts, expectedOrgIDs) testSuccess(opts, expectedOrgIDs)
} }
testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1, PageSize: 2}}, testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1, PageSize: 2}},
[]int64{3, 6}) []int64{3, 6})
testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 2, PageSize: 2}}, testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 2, PageSize: 2}},
[]int64{7, 17}) []int64{7, 17})
testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 3, PageSize: 2}}, testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 3, PageSize: 2}},
[]int64{19, 25}) []int64{19, 25})
testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}}, testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}},
[]int64{26, 41}) []int64{26, 41})
testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 5, PageSize: 2}}, testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 5, PageSize: 2}},
[]int64{42}) []int64{42})
testOrgSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 6, PageSize: 2}}, testOrgSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 6, PageSize: 2}},
[]int64{}) []int64{})
// test users // test users
testUserSuccess := func(opts *user_model.SearchUserOptions, expectedUserIDs []int64) { testUserSuccess := func(opts user_model.SearchUserOptions, expectedUserIDs []int64) {
opts.Type = user_model.UserTypeIndividual opts.Type = user_model.UserTypeIndividual
testSuccess(opts, expectedUserIDs) testSuccess(opts, expectedUserIDs)
} }
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}}, testUserSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}},
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40}) []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(false)}, testUserSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(false)},
[]int64{9}) []int64{9})
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)}, testUserSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40}) []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40})
testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)}, testUserSuccess(user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) []int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
// order by name asc default // order by name asc default
testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)}, testUserSuccess(user_model.SearchUserOptions{Keyword: "user1", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) []int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsAdmin: optional.Some(true)}, testUserSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsAdmin: optional.Some(true)},
[]int64{1}) []int64{1})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsRestricted: optional.Some(true)}, testUserSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsRestricted: optional.Some(true)},
[]int64{29}) []int64{29})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: optional.Some(true)}, testUserSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: optional.Some(true)},
[]int64{37}) []int64{37})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsTwoFactorEnabled: optional.Some(true)}, testUserSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsTwoFactorEnabled: optional.Some(true)},
[]int64{24}) []int64{24})
} }

View File

@ -43,21 +43,23 @@ func IsWorkflow(path string) bool {
return strings.HasPrefix(path, ".gitea/workflows") || strings.HasPrefix(path, ".github/workflows") return strings.HasPrefix(path, ".gitea/workflows") || strings.HasPrefix(path, ".github/workflows")
} }
func ListWorkflows(commit *git.Commit) (git.Entries, error) { func ListWorkflows(commit *git.Commit) (string, git.Entries, error) {
tree, err := commit.SubTree(".gitea/workflows") rpath := ".gitea/workflows"
tree, err := commit.SubTree(rpath)
if _, ok := err.(git.ErrNotExist); ok { if _, ok := err.(git.ErrNotExist); ok {
tree, err = commit.SubTree(".github/workflows") rpath = ".github/workflows"
tree, err = commit.SubTree(rpath)
} }
if _, ok := err.(git.ErrNotExist); ok { if _, ok := err.(git.ErrNotExist); ok {
return nil, nil return "", nil, nil
} }
if err != nil { if err != nil {
return nil, err return "", nil, err
} }
entries, err := tree.ListEntriesRecursiveFast() entries, err := tree.ListEntriesRecursiveFast()
if err != nil { if err != nil {
return nil, err return "", nil, err
} }
ret := make(git.Entries, 0, len(entries)) ret := make(git.Entries, 0, len(entries))
@ -66,7 +68,7 @@ func ListWorkflows(commit *git.Commit) (git.Entries, error) {
ret = append(ret, entry) ret = append(ret, entry)
} }
} }
return ret, nil return rpath, ret, nil
} }
func GetContentFromEntry(entry *git.TreeEntry) ([]byte, error) { func GetContentFromEntry(entry *git.TreeEntry) ([]byte, error) {
@ -102,7 +104,7 @@ func DetectWorkflows(
payload api.Payloader, payload api.Payloader,
detectSchedule bool, detectSchedule bool,
) ([]*DetectedWorkflow, []*DetectedWorkflow, error) { ) ([]*DetectedWorkflow, []*DetectedWorkflow, error) {
entries, err := ListWorkflows(commit) _, entries, err := ListWorkflows(commit)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -147,7 +149,7 @@ func DetectWorkflows(
} }
func DetectScheduledWorkflows(gitRepo *git.Repository, commit *git.Commit) ([]*DetectedWorkflow, error) { func DetectScheduledWorkflows(gitRepo *git.Repository, commit *git.Commit) ([]*DetectedWorkflow, error) {
entries, err := ListWorkflows(commit) _, entries, err := ListWorkflows(commit)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -132,18 +132,22 @@ func (r *BlameReader) Close() error {
} }
// CreateBlameReader creates reader for given repository, commit and file // CreateBlameReader creates reader for given repository, commit and file
func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) { func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (rd *BlameReader, err error) {
reader, stdout, err := os.Pipe() var ignoreRevsFileName string
if err != nil { var ignoreRevsFileCleanup func()
return nil, err defer func() {
} if err != nil && ignoreRevsFileCleanup != nil {
ignoreRevsFileCleanup()
}
}()
cmd := NewCommandNoGlobals("blame", "--porcelain") cmd := NewCommandNoGlobals("blame", "--porcelain")
var ignoreRevsFileName string
var ignoreRevsFileCleanup func() // TODO: maybe it should check the returned err in a defer func to make sure the cleanup could always be executed correctly
if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore { if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore {
ignoreRevsFileName, ignoreRevsFileCleanup = tryCreateBlameIgnoreRevsFile(commit) ignoreRevsFileName, ignoreRevsFileCleanup, err = tryCreateBlameIgnoreRevsFile(commit)
if err != nil && !IsErrNotExist(err) {
return nil, err
}
if ignoreRevsFileName != "" { if ignoreRevsFileName != "" {
// Possible improvement: use --ignore-revs-file /dev/stdin on unix // Possible improvement: use --ignore-revs-file /dev/stdin on unix
// There is no equivalent on Windows. May be implemented if Gitea uses an external git backend. // There is no equivalent on Windows. May be implemented if Gitea uses an external git backend.
@ -154,6 +158,10 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath
cmd.AddDynamicArguments(commit.ID.String()).AddDashesAndList(file) cmd.AddDynamicArguments(commit.ID.String()).AddDashesAndList(file)
done := make(chan error, 1) done := make(chan error, 1)
reader, stdout, err := os.Pipe()
if err != nil {
return nil, err
}
go func() { go func() {
stderr := bytes.Buffer{} stderr := bytes.Buffer{}
// TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close" // TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close"
@ -182,33 +190,29 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath
}, nil }, nil
} }
func tryCreateBlameIgnoreRevsFile(commit *Commit) (string, func()) { func tryCreateBlameIgnoreRevsFile(commit *Commit) (string, func(), error) {
entry, err := commit.GetTreeEntryByPath(".git-blame-ignore-revs") entry, err := commit.GetTreeEntryByPath(".git-blame-ignore-revs")
if err != nil { if err != nil {
log.Error("Unable to get .git-blame-ignore-revs file: GetTreeEntryByPath: %v", err) return "", nil, err
return "", nil
} }
r, err := entry.Blob().DataAsync() r, err := entry.Blob().DataAsync()
if err != nil { if err != nil {
log.Error("Unable to get .git-blame-ignore-revs file data: DataAsync: %v", err) return "", nil, err
return "", nil
} }
defer r.Close() defer r.Close()
f, cleanup, err := setting.AppDataTempDir("git-repo-content").CreateTempFileRandom("git-blame-ignore-revs") f, cleanup, err := setting.AppDataTempDir("git-repo-content").CreateTempFileRandom("git-blame-ignore-revs")
if err != nil { if err != nil {
log.Error("Unable to get .git-blame-ignore-revs file data: CreateTempFileRandom: %v", err) return "", nil, err
return "", nil
} }
filename := f.Name() filename := f.Name()
_, err = io.Copy(f, r) _, err = io.Copy(f, r)
_ = f.Close() _ = f.Close()
if err != nil { if err != nil {
cleanup() cleanup()
log.Error("Unable to get .git-blame-ignore-revs file data: Copy: %v", err) return "", nil, err
return "", nil
} }
return filename, cleanup return filename, cleanup, nil
} }

View File

@ -34,7 +34,7 @@ type Commit struct {
// CommitSignature represents a git commit signature part. // CommitSignature represents a git commit signature part.
type CommitSignature struct { type CommitSignature struct {
Signature string Signature string
Payload string // TODO check if can be reconstruct from the rest of commit information to not have duplicate data Payload string
} }
// Message returns the commit message. Same as retrieving CommitMessage directly. // Message returns the commit message. Same as retrieving CommitMessage directly.

View File

@ -6,10 +6,44 @@ package git
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"fmt"
"io" "io"
"strings"
) )
const (
commitHeaderGpgsig = "gpgsig"
commitHeaderGpgsigSha256 = "gpgsig-sha256"
)
func assignCommitFields(gitRepo *Repository, commit *Commit, headerKey string, headerValue []byte) error {
if len(headerValue) > 0 && headerValue[len(headerValue)-1] == '\n' {
headerValue = headerValue[:len(headerValue)-1] // remove trailing newline
}
switch headerKey {
case "tree":
objID, err := NewIDFromString(string(headerValue))
if err != nil {
return fmt.Errorf("invalid tree ID %q: %w", string(headerValue), err)
}
commit.Tree = *NewTree(gitRepo, objID)
case "parent":
objID, err := NewIDFromString(string(headerValue))
if err != nil {
return fmt.Errorf("invalid parent ID %q: %w", string(headerValue), err)
}
commit.Parents = append(commit.Parents, objID)
case "author":
commit.Author.Decode(headerValue)
case "committer":
commit.Committer.Decode(headerValue)
case commitHeaderGpgsig, commitHeaderGpgsigSha256:
// if there are duplicate "gpgsig" and "gpgsig-sha256" headers, then the signature must have already been invalid
// so we don't need to handle duplicate headers here
commit.Signature = &CommitSignature{Signature: string(headerValue)}
}
return nil
}
// CommitFromReader will generate a Commit from a provided reader // CommitFromReader will generate a Commit from a provided reader
// We need this to interpret commits from cat-file or cat-file --batch // We need this to interpret commits from cat-file or cat-file --batch
// //
@ -21,90 +55,46 @@ func CommitFromReader(gitRepo *Repository, objectID ObjectID, reader io.Reader)
Committer: &Signature{}, Committer: &Signature{},
} }
payloadSB := new(strings.Builder) bufReader := bufio.NewReader(reader)
signatureSB := new(strings.Builder) inHeader := true
messageSB := new(strings.Builder) var payloadSB, messageSB bytes.Buffer
message := false var headerKey string
pgpsig := false var headerValue []byte
bufReader, ok := reader.(*bufio.Reader)
if !ok {
bufReader = bufio.NewReader(reader)
}
readLoop:
for { for {
line, err := bufReader.ReadBytes('\n') line, err := bufReader.ReadBytes('\n')
if err != nil { if err != nil && err != io.EOF {
if err == io.EOF { return nil, fmt.Errorf("unable to read commit %q: %w", objectID.String(), err)
if message { }
_, _ = messageSB.Write(line) if len(line) == 0 {
break
}
if inHeader {
inHeader = !(len(line) == 1 && line[0] == '\n') // still in header if line is not just a newline
k, v, _ := bytes.Cut(line, []byte{' '})
if len(k) != 0 || !inHeader {
if headerKey != "" {
if err = assignCommitFields(gitRepo, commit, headerKey, headerValue); err != nil {
return nil, fmt.Errorf("unable to parse commit %q: %w", objectID.String(), err)
}
} }
_, _ = payloadSB.Write(line) headerKey = string(k) // it also resets the headerValue to empty string if not inHeader
break readLoop headerValue = v
} else {
headerValue = append(headerValue, v...)
} }
return nil, err if headerKey != commitHeaderGpgsig && headerKey != commitHeaderGpgsigSha256 {
}
if pgpsig {
if len(line) > 0 && line[0] == ' ' {
_, _ = signatureSB.Write(line[1:])
continue
}
pgpsig = false
}
if !message {
// This is probably not correct but is copied from go-gits interpretation...
trimmed := bytes.TrimSpace(line)
if len(trimmed) == 0 {
message = true
_, _ = payloadSB.Write(line) _, _ = payloadSB.Write(line)
continue
}
split := bytes.SplitN(trimmed, []byte{' '}, 2)
var data []byte
if len(split) > 1 {
data = split[1]
}
switch string(split[0]) {
case "tree":
commit.Tree = *NewTree(gitRepo, MustIDFromString(string(data)))
_, _ = payloadSB.Write(line)
case "parent":
commit.Parents = append(commit.Parents, MustIDFromString(string(data)))
_, _ = payloadSB.Write(line)
case "author":
commit.Author = &Signature{}
commit.Author.Decode(data)
_, _ = payloadSB.Write(line)
case "committer":
commit.Committer = &Signature{}
commit.Committer.Decode(data)
_, _ = payloadSB.Write(line)
case "encoding":
_, _ = payloadSB.Write(line)
case "gpgsig":
fallthrough
case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.
_, _ = signatureSB.Write(data)
_ = signatureSB.WriteByte('\n')
pgpsig = true
} }
} else { } else {
_, _ = messageSB.Write(line) _, _ = messageSB.Write(line)
_, _ = payloadSB.Write(line) _, _ = payloadSB.Write(line)
} }
} }
commit.CommitMessage = messageSB.String()
commit.Signature = &CommitSignature{
Signature: signatureSB.String(),
Payload: payloadSB.String(),
}
if len(commit.Signature.Signature) == 0 {
commit.Signature = nil
}
commit.CommitMessage = messageSB.String()
if commit.Signature != nil {
commit.Signature.Payload = payloadSB.String()
}
return commit, nil return commit, nil
} }

View File

@ -60,8 +60,7 @@ func TestGetFullCommitIDErrorSha256(t *testing.T) {
} }
func TestCommitFromReaderSha256(t *testing.T) { func TestCommitFromReaderSha256(t *testing.T) {
commitString := `9433b2a62b964c17a4485ae180f45f595d3e69d31b786087775e28c6b6399df0 commit 1114 commitString := `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8 parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
author Adam Majer <amajer@suse.de> 1698676906 +0100 author Adam Majer <amajer@suse.de> 1698676906 +0100
committer Adam Majer <amajer@suse.de> 1698676906 +0100 committer Adam Majer <amajer@suse.de> 1698676906 +0100
@ -112,8 +111,7 @@ VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X
HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR
8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6 8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6
=xybZ =xybZ
-----END PGP SIGNATURE----- -----END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
`, commitFromReader.Signature.Signature)
assert.Equal(t, `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e assert.Equal(t, `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8 parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
author Adam Majer <amajer@suse.de> 1698676906 +0100 author Adam Majer <amajer@suse.de> 1698676906 +0100

View File

@ -59,8 +59,7 @@ func TestGetFullCommitIDError(t *testing.T) {
} }
func TestCommitFromReader(t *testing.T) { func TestCommitFromReader(t *testing.T) {
commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074 commitString := `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
parent 37991dec2c8e592043f47155ce4808d4580f9123 parent 37991dec2c8e592043f47155ce4808d4580f9123
author silverwind <me@silverwind.io> 1563741793 +0200 author silverwind <me@silverwind.io> 1563741793 +0200
committer silverwind <me@silverwind.io> 1563741793 +0200 committer silverwind <me@silverwind.io> 1563741793 +0200
@ -108,8 +107,7 @@ sD53z/f0J+We4VZjY+pidvA9BGZPFVdR3wd3xGs8/oH6UWaLJAMGkLG6dDb3qDLm
mfeFhT57UbE4qukTDIQ0Y0WM40UYRTakRaDY7ubhXgLgx09Cnp9XTVMsHgT6j9/i mfeFhT57UbE4qukTDIQ0Y0WM40UYRTakRaDY7ubhXgLgx09Cnp9XTVMsHgT6j9/i
1pxsB104XLWjQHTjr1JtiaBQEwFh9r2OKTcpvaLcbNtYpo7CzOs= 1pxsB104XLWjQHTjr1JtiaBQEwFh9r2OKTcpvaLcbNtYpo7CzOs=
=FRsO =FRsO
-----END PGP SIGNATURE----- -----END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
`, commitFromReader.Signature.Signature)
assert.Equal(t, `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930 assert.Equal(t, `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
parent 37991dec2c8e592043f47155ce4808d4580f9123 parent 37991dec2c8e592043f47155ce4808d4580f9123
author silverwind <me@silverwind.io> 1563741793 +0200 author silverwind <me@silverwind.io> 1563741793 +0200
@ -126,8 +124,7 @@ empty commit`, commitFromReader.Signature.Payload)
} }
func TestCommitWithEncodingFromReader(t *testing.T) { func TestCommitWithEncodingFromReader(t *testing.T) {
commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074 commitString := `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
parent 47b24e7ab977ed31c5a39989d570847d6d0052af parent 47b24e7ab977ed31c5a39989d570847d6d0052af
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100 author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100 committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
@ -172,8 +169,7 @@ SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F
yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz
jw4YcO5u jw4YcO5u
=r3UU =r3UU
-----END PGP SIGNATURE----- -----END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
`, commitFromReader.Signature.Signature)
assert.Equal(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5 assert.Equal(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
parent 47b24e7ab977ed31c5a39989d570847d6d0052af parent 47b24e7ab977ed31c5a39989d570847d6d0052af
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100 author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100

View File

@ -18,6 +18,8 @@ const (
CommitStatusFailure CommitStatusState = "failure" CommitStatusFailure CommitStatusState = "failure"
// CommitStatusWarning is for when the CommitStatus is Warning // CommitStatusWarning is for when the CommitStatus is Warning
CommitStatusWarning CommitStatusState = "warning" CommitStatusWarning CommitStatusState = "warning"
// CommitStatusSkipped is for when CommitStatus is Skipped
CommitStatusSkipped CommitStatusState = "skipped"
) )
var commitStatusPriorities = map[CommitStatusState]int{ var commitStatusPriorities = map[CommitStatusState]int{
@ -26,25 +28,17 @@ var commitStatusPriorities = map[CommitStatusState]int{
CommitStatusWarning: 2, CommitStatusWarning: 2,
CommitStatusPending: 3, CommitStatusPending: 3,
CommitStatusSuccess: 4, CommitStatusSuccess: 4,
CommitStatusSkipped: 5,
} }
func (css CommitStatusState) String() string { func (css CommitStatusState) String() string {
return string(css) return string(css)
} }
// NoBetterThan returns true if this State is no better than the given State // HasHigherPriorityThan returns true if this state has higher priority than the other
// This function only handles the states defined in CommitStatusPriorities // Undefined states are considered to have the highest priority like CommitStatusError(0)
func (css CommitStatusState) NoBetterThan(css2 CommitStatusState) bool { func (css CommitStatusState) HasHigherPriorityThan(other CommitStatusState) bool {
// NoBetterThan only handles the 5 states above return commitStatusPriorities[css] < commitStatusPriorities[other]
if _, exist := commitStatusPriorities[css]; !exist {
return false
}
if _, exist := commitStatusPriorities[css2]; !exist {
return false
}
return commitStatusPriorities[css] <= commitStatusPriorities[css2]
} }
// IsPending represents if commit status state is pending // IsPending represents if commit status state is pending

View File

@ -10,165 +10,21 @@ import (
) )
func TestNoBetterThan(t *testing.T) { func TestNoBetterThan(t *testing.T) {
type args struct {
css CommitStatusState
css2 CommitStatusState
}
var unExpectedState CommitStatusState
tests := []struct { tests := []struct {
name string s1, s2 CommitStatusState
args args higher bool
want bool
}{ }{
{ {CommitStatusError, CommitStatusFailure, true},
name: "success is no better than success", {CommitStatusFailure, CommitStatusWarning, true},
args: args{ {CommitStatusWarning, CommitStatusPending, true},
css: CommitStatusSuccess, {CommitStatusPending, CommitStatusSuccess, true},
css2: CommitStatusSuccess, {CommitStatusSuccess, CommitStatusSkipped, true},
},
want: true, {CommitStatusError, "unknown-xxx", false},
}, {"unknown-xxx", CommitStatusFailure, true},
{
name: "success is no better than pending",
args: args{
css: CommitStatusSuccess,
css2: CommitStatusPending,
},
want: false,
},
{
name: "success is no better than failure",
args: args{
css: CommitStatusSuccess,
css2: CommitStatusFailure,
},
want: false,
},
{
name: "success is no better than error",
args: args{
css: CommitStatusSuccess,
css2: CommitStatusError,
},
want: false,
},
{
name: "pending is no better than success",
args: args{
css: CommitStatusPending,
css2: CommitStatusSuccess,
},
want: true,
},
{
name: "pending is no better than pending",
args: args{
css: CommitStatusPending,
css2: CommitStatusPending,
},
want: true,
},
{
name: "pending is no better than failure",
args: args{
css: CommitStatusPending,
css2: CommitStatusFailure,
},
want: false,
},
{
name: "pending is no better than error",
args: args{
css: CommitStatusPending,
css2: CommitStatusError,
},
want: false,
},
{
name: "failure is no better than success",
args: args{
css: CommitStatusFailure,
css2: CommitStatusSuccess,
},
want: true,
},
{
name: "failure is no better than pending",
args: args{
css: CommitStatusFailure,
css2: CommitStatusPending,
},
want: true,
},
{
name: "failure is no better than failure",
args: args{
css: CommitStatusFailure,
css2: CommitStatusFailure,
},
want: true,
},
{
name: "failure is no better than error",
args: args{
css: CommitStatusFailure,
css2: CommitStatusError,
},
want: false,
},
{
name: "error is no better than success",
args: args{
css: CommitStatusError,
css2: CommitStatusSuccess,
},
want: true,
},
{
name: "error is no better than pending",
args: args{
css: CommitStatusError,
css2: CommitStatusPending,
},
want: true,
},
{
name: "error is no better than failure",
args: args{
css: CommitStatusError,
css2: CommitStatusFailure,
},
want: true,
},
{
name: "error is no better than error",
args: args{
css: CommitStatusError,
css2: CommitStatusError,
},
want: true,
},
{
name: "unExpectedState is no better than success",
args: args{
css: unExpectedState,
css2: CommitStatusSuccess,
},
want: false,
},
{
name: "unExpectedState is no better than unExpectedState",
args: args{
css: unExpectedState,
css2: unExpectedState,
},
want: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.higher, tt.s1.HasHigherPriorityThan(tt.s2), "s1=%s, s2=%s, expected=%v", tt.s1, tt.s2, tt.higher)
result := tt.args.css.NoBetterThan(tt.args.css2)
assert.Equal(t, tt.want, result)
})
} }
assert.False(t, CommitStatusError.HasHigherPriorityThan(CommitStatusError))
} }

View File

@ -3817,6 +3817,7 @@ runs.expire_log_message = Logs have been purged because they were too old.
runs.delete = Delete workflow run 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.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. runs.not_done = This workflow run is not done.
runs.view_workflow_file = View workflow file
workflow.disable = Disable Workflow workflow.disable = Disable Workflow
workflow.disable_success = Workflow '%s' disabled successfully. workflow.disable_success = Workflow '%s' disabled successfully.

View File

@ -1190,7 +1190,7 @@ issues.context.edit=Modifica
issues.context.delete=Elimina issues.context.delete=Elimina
issues.reopen_issue=Riapri issues.reopen_issue=Riapri
issues.create_comment=Commento issues.create_comment=Commento
issues.closed_at=`chiuso questo probleam <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.closed_at=`ha chiuso questo problema <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at=`riaperto questo problema <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.reopened_at=`riaperto questo problema <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commit_ref_at=`ha fatto riferimento a questa issue dal commit <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.commit_ref_at=`ha fatto riferimento a questa issue dal commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_issue_from=`<a href="%[3]s">ha fatto riferimento a questo problema %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_issue_from=`<a href="%[3]s">ha fatto riferimento a questo problema %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`

View File

@ -3809,6 +3809,9 @@ runs.no_workflows.documentation=Gitea Actions の詳細については、<a targ
runs.no_runs=ワークフローはまだ実行されていません。 runs.no_runs=ワークフローはまだ実行されていません。
runs.empty_commit_message=(空のコミットメッセージ) runs.empty_commit_message=(空のコミットメッセージ)
runs.expire_log_message=ログは古すぎるため消去されています。 runs.expire_log_message=ログは古すぎるため消去されています。
runs.delete=ワークフローの実行を削除
runs.delete.description=このワークフローを完全に削除してもよろしいですか?この操作は元に戻せません。
runs.not_done=このワークフローの実行は完了していません。
workflow.disable=ワークフローを無効にする workflow.disable=ワークフローを無効にする
workflow.disable_success=ワークフロー '%s' が無効になりました。 workflow.disable_success=ワークフロー '%s' が無効になりました。

View File

@ -1523,7 +1523,7 @@ issues.remove_labels=removeu os rótulos %s %s
issues.add_remove_labels=adicionou o(s) rótulo(s) %s e removeu %s %s issues.add_remove_labels=adicionou o(s) rótulo(s) %s e removeu %s %s
issues.add_milestone_at=`adicionou esta questão à etapa <b>%s</b> %s` issues.add_milestone_at=`adicionou esta questão à etapa <b>%s</b> %s`
issues.add_project_at=`adicionou esta questão ao planeamento <b>%s</b> %s` issues.add_project_at=`adicionou esta questão ao planeamento <b>%s</b> %s`
issues.move_to_column_of_project=`isto foi movido para %s dentro de %s em %s` issues.move_to_column_of_project=`moveu isto para %s em %s %s`
issues.change_milestone_at=`modificou a etapa de <b>%s</b> para <b>%s</b> %s` issues.change_milestone_at=`modificou a etapa de <b>%s</b> para <b>%s</b> %s`
issues.change_project_at=`modificou o planeamento de <b>%s</b> para <b>%s</b> %s` issues.change_project_at=`modificou o planeamento de <b>%s</b> para <b>%s</b> %s`
issues.remove_milestone_at=`removeu esta questão da etapa <b>%s</b> %s` issues.remove_milestone_at=`removeu esta questão da etapa <b>%s</b> %s`

View File

@ -101,7 +101,7 @@ func GetAllOrgs(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx) listOptions := utils.GetListOptions(ctx)
users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer, Actor: ctx.Doer,
Type: user_model.UserTypeOrganization, Type: user_model.UserTypeOrganization,
OrderBy: db.SearchOrderByAlphabetically, OrderBy: db.SearchOrderByAlphabetically,

View File

@ -423,7 +423,7 @@ func SearchUsers(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx) listOptions := utils.GetListOptions(ctx)
users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer, Actor: ctx.Doer,
Type: user_model.UserTypeIndividual, Type: user_model.UserTypeIndividual,
LoginName: ctx.FormTrim("login_name"), LoginName: ctx.FormTrim("login_name"),

View File

@ -384,13 +384,13 @@ func (Action) CreateVariable(ctx *context.APIContext) {
// "$ref": "#/definitions/CreateVariableOption" // "$ref": "#/definitions/CreateVariableOption"
// responses: // responses:
// "201": // "201":
// description: response when creating an org-level variable // description: successfully created the org-level variable
// "204":
// description: response when creating an org-level variable
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "409":
// "$ref": "#/responses/notFound" // description: variable name already exists.
// "500":
// "$ref": "#/responses/error"
opt := web.GetForm(ctx).(*api.CreateVariableOption) opt := web.GetForm(ctx).(*api.CreateVariableOption)
@ -419,7 +419,7 @@ func (Action) CreateVariable(ctx *context.APIContext) {
return return
} }
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusCreated)
} }
// UpdateVariable update an org-level variable // UpdateVariable update an org-level variable

View File

@ -201,7 +201,7 @@ func GetAll(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx) listOptions := utils.GetListOptions(ctx)
publicOrgs, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ publicOrgs, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer, Actor: ctx.Doer,
ListOptions: listOptions, ListOptions: listOptions,
Type: user_model.UserTypeOrganization, Type: user_model.UserTypeOrganization,

View File

@ -339,12 +339,12 @@ func (Action) CreateVariable(ctx *context.APIContext) {
// responses: // responses:
// "201": // "201":
// description: response when creating a repo-level variable // description: response when creating a repo-level variable
// "204":
// description: response when creating a repo-level variable
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "409":
// "$ref": "#/responses/notFound" // description: variable name already exists.
// "500":
// "$ref": "#/responses/error"
opt := web.GetForm(ctx).(*api.CreateVariableOption) opt := web.GetForm(ctx).(*api.CreateVariableOption)
@ -373,7 +373,7 @@ func (Action) CreateVariable(ctx *context.APIContext) {
return return
} }
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusCreated)
} }
// UpdateVariable update a repo-level variable // UpdateVariable update a repo-level variable

View File

@ -73,7 +73,7 @@ func ListPullRequests(ctx *context.APIContext) {
// in: query // in: query
// description: Type of sort // description: Type of sort
// type: string // type: string
// enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority] // enum: [oldest, recentupdate, recentclose, leastupdate, mostcomment, leastcomment, priority]
// - name: milestone // - name: milestone
// in: query // in: query
// description: ID of the milestone // description: ID of the milestone

View File

@ -258,19 +258,24 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
statuses, count, err := git_model.GetLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String(), utils.GetListOptions(ctx)) statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String(), utils.GetListOptions(ctx))
if err != nil { if err != nil {
ctx.APIErrorInternal(fmt.Errorf("GetLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err)) ctx.APIErrorInternal(fmt.Errorf("GetLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err))
return return
} }
count, err := git_model.CountLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String())
if err != nil {
ctx.APIErrorInternal(fmt.Errorf("CountLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err))
return
}
ctx.SetTotalCountHeader(count)
if len(statuses) == 0 { if len(statuses) == 0 {
ctx.JSON(http.StatusOK, &api.CombinedStatus{}) ctx.JSON(http.StatusOK, &api.CombinedStatus{})
return return
} }
combiStatus := convert.ToCombinedStatus(ctx, statuses, convert.ToRepo(ctx, repo, ctx.Repo.Permission)) combiStatus := convert.ToCombinedStatus(ctx, statuses, convert.ToRepo(ctx, repo, ctx.Repo.Permission))
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, combiStatus) ctx.JSON(http.StatusOK, combiStatus)
} }

View File

@ -127,13 +127,11 @@ func CreateVariable(ctx *context.APIContext) {
// "$ref": "#/definitions/CreateVariableOption" // "$ref": "#/definitions/CreateVariableOption"
// responses: // responses:
// "201": // "201":
// description: response when creating a variable // description: successfully created the user-level variable
// "204":
// description: response when creating a variable
// "400": // "400":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "409":
// "$ref": "#/responses/notFound" // description: variable name already exists.
opt := web.GetForm(ctx).(*api.CreateVariableOption) opt := web.GetForm(ctx).(*api.CreateVariableOption)
@ -162,7 +160,7 @@ func CreateVariable(ctx *context.APIContext) {
return return
} }
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusCreated)
} }
// UpdateVariable update a user-level variable which is created by current doer // UpdateVariable update a user-level variable which is created by current doer

View File

@ -73,7 +73,7 @@ func Search(ctx *context.APIContext) {
if ctx.PublicOnly { if ctx.PublicOnly {
visible = []structs.VisibleType{structs.VisibleTypePublic} visible = []structs.VisibleType{structs.VisibleTypePublic}
} }
users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ users, maxResults, err = user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer, Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"), Keyword: ctx.FormTrim("q"),
UID: uid, UID: uid,

View File

@ -27,7 +27,7 @@ func Organizations(ctx *context.Context) {
ctx.SetFormString("sort", UserSearchDefaultAdminSort) ctx.SetFormString("sort", UserSearchDefaultAdminSort)
} }
explore.RenderUserSearch(ctx, &user_model.SearchUserOptions{ explore.RenderUserSearch(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer, Actor: ctx.Doer,
Type: user_model.UserTypeOrganization, Type: user_model.UserTypeOrganization,
IncludeReserved: true, // administrator needs to list all accounts include reserved IncludeReserved: true, // administrator needs to list all accounts include reserved

View File

@ -64,7 +64,7 @@ func Users(ctx *context.Context) {
"SortType": sortType, "SortType": sortType,
} }
explore.RenderUserSearch(ctx, &user_model.SearchUserOptions{ explore.RenderUserSearch(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer, Actor: ctx.Doer,
Type: user_model.UserTypeIndividual, Type: user_model.UserTypeIndividual,
ListOptions: db.ListOptions{ ListOptions: db.ListOptions{

View File

@ -44,7 +44,7 @@ func Organizations(ctx *context.Context) {
ctx.SetFormString("sort", sortOrder) ctx.SetFormString("sort", sortOrder)
} }
RenderUserSearch(ctx, &user_model.SearchUserOptions{ RenderUserSearch(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer, Actor: ctx.Doer,
Type: user_model.UserTypeOrganization, Type: user_model.UserTypeOrganization,
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum}, ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},

View File

@ -32,7 +32,7 @@ func isKeywordValid(keyword string) bool {
} }
// RenderUserSearch render user search page // RenderUserSearch render user search page
func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, tplName templates.TplName) { func RenderUserSearch(ctx *context.Context, opts user_model.SearchUserOptions, tplName templates.TplName) {
// Sitemap index for sitemap paths // Sitemap index for sitemap paths
opts.Page = int(ctx.PathParamInt64("idx")) opts.Page = int(ctx.PathParamInt64("idx"))
isSitemap := ctx.PathParam("idx") != "" isSitemap := ctx.PathParam("idx") != ""
@ -151,7 +151,7 @@ func Users(ctx *context.Context) {
ctx.SetFormString("sort", sortOrder) ctx.SetFormString("sort", sortOrder)
} }
RenderUserSearch(ctx, &user_model.SearchUserOptions{ RenderUserSearch(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer, Actor: ctx.Doer,
Type: user_model.UserTypeIndividual, Type: user_model.UserTypeIndividual,
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum}, ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},

View File

@ -68,7 +68,7 @@ func Home(ctx *context.Context) {
func HomeSitemap(ctx *context.Context) { func HomeSitemap(ctx *context.Context) {
m := sitemap.NewSitemapIndex() m := sitemap.NewSitemapIndex()
if !setting.Service.Explore.DisableUsersPage { if !setting.Service.Explore.DisableUsersPage {
_, cnt, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ _, cnt, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Type: user_model.UserTypeIndividual, Type: user_model.UserTypeIndividual,
ListOptions: db.ListOptions{PageSize: 1}, ListOptions: db.ListOptions{PageSize: 1},
IsActive: optional.Some(true), IsActive: optional.Some(true),

View File

@ -126,7 +126,7 @@ func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) (
var curWorkflow *model.Workflow var curWorkflow *model.Workflow
entries, err := actions.ListWorkflows(commit) _, entries, err := actions.ListWorkflows(commit)
if err != nil { if err != nil {
ctx.ServerError("ListWorkflows", err) ctx.ServerError("ListWorkflows", err)
return nil return nil

View File

@ -64,6 +64,36 @@ func View(ctx *context_module.Context) {
ctx.HTML(http.StatusOK, tplViewActions) ctx.HTML(http.StatusOK, tplViewActions)
} }
func ViewWorkflowFile(ctx *context_module.Context) {
runIndex := getRunIndex(ctx)
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
if err != nil {
ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool {
return errors.Is(err, util.ErrNotExist)
}, err)
return
}
commit, err := ctx.Repo.GitRepo.GetCommit(run.CommitSHA)
if err != nil {
ctx.NotFoundOrServerError("GetCommit", func(err error) bool {
return errors.Is(err, util.ErrNotExist)
}, err)
return
}
rpath, entries, err := actions.ListWorkflows(commit)
if err != nil {
ctx.ServerError("ListWorkflows", err)
return
}
for _, entry := range entries {
if entry.Name() == run.WorkflowID {
ctx.Redirect(fmt.Sprintf("%s/src/commit/%s/%s/%s", ctx.Repo.RepoLink, url.PathEscape(run.CommitSHA), util.PathEscapeSegments(rpath), util.PathEscapeSegments(run.WorkflowID)))
return
}
}
ctx.NotFound(nil)
}
type LogCursor struct { type LogCursor struct {
Step int `json:"step"` Step int `json:"step"`
Cursor int64 `json:"cursor"` Cursor int64 `json:"cursor"`

View File

@ -377,7 +377,7 @@ func Diff(ctx *context.Context) {
ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML() ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
} }
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll) statuses, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)
if err != nil { if err != nil {
log.Error("GetLatestCommitStatus: %v", err) log.Error("GetLatestCommitStatus: %v", err)
} }

View File

@ -291,7 +291,7 @@ func prepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
if len(compareInfo.Commits) != 0 { if len(compareInfo.Commits) != 0 {
sha := compareInfo.Commits[0].ID.String() sha := compareInfo.Commits[0].ID.String()
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptionsAll) commitStatuses, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptionsAll)
if err != nil { if err != nil {
ctx.ServerError("GetLatestCommitStatus", err) ctx.ServerError("GetLatestCommitStatus", err)
return nil return nil
@ -358,7 +358,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err) ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
return nil return nil
} }
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll) commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll)
if err != nil { if err != nil {
ctx.ServerError("GetLatestCommitStatus", err) ctx.ServerError("GetLatestCommitStatus", err)
return nil return nil
@ -454,7 +454,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
return nil return nil
} }
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll) commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll)
if err != nil { if err != nil {
ctx.ServerError("GetLatestCommitStatus", err) ctx.ServerError("GetLatestCommitStatus", err)
return nil return nil

View File

@ -130,7 +130,7 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
} }
if canReadActions { if canReadActions {
statuses, _, err := git_model.GetLatestCommitStatus(ctx, r.Repo.ID, r.Sha1, db.ListOptionsAll) statuses, err := git_model.GetLatestCommitStatus(ctx, r.Repo.ID, r.Sha1, db.ListOptionsAll)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -131,7 +131,7 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
ctx.Data["LatestCommitVerification"] = verification ctx.Data["LatestCommitVerification"] = verification
ctx.Data["LatestCommitUser"] = user_model.ValidateCommitWithEmail(ctx, latestCommit) ctx.Data["LatestCommitUser"] = user_model.ValidateCommitWithEmail(ctx, latestCommit)
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, latestCommit.ID.String(), db.ListOptionsAll) statuses, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, latestCommit.ID.String(), db.ListOptionsAll)
if err != nil { if err != nil {
log.Error("GetLatestCommitStatus: %v", err) log.Error("GetLatestCommitStatus: %v", err)
} }

View File

@ -16,7 +16,7 @@ import (
// SearchCandidates searches candidate users for dropdown list // SearchCandidates searches candidate users for dropdown list
func SearchCandidates(ctx *context.Context) { func SearchCandidates(ctx *context.Context) {
users, _, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ users, _, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer, Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"), Keyword: ctx.FormTrim("q"),
Type: user_model.UserTypeIndividual, Type: user_model.UserTypeIndividual,

View File

@ -1445,6 +1445,7 @@ func registerWebRoutes(m *web.Router) {
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun) m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
m.Get("/logs", actions.Logs) m.Get("/logs", actions.Logs)
}) })
m.Get("/workflow", actions.ViewWorkflowFile)
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.Post("/delete", reqRepoActionsWriter, actions.Delete)

View File

@ -92,7 +92,7 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
} }
ctxname := fmt.Sprintf("%s / %s (%s)", runName, job.Name, event) ctxname := fmt.Sprintf("%s / %s (%s)", runName, job.Name, event)
state := toCommitStatus(job.Status) state := toCommitStatus(job.Status)
if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll); err == nil { if statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll); err == nil {
for _, v := range statuses { for _, v := range statuses {
if v.Context == ctxname { if v.Context == ctxname {
if v.State == state { if v.State == state {
@ -149,12 +149,14 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
func toCommitStatus(status actions_model.Status) api.CommitStatusState { func toCommitStatus(status actions_model.Status) api.CommitStatusState {
switch status { switch status {
case actions_model.StatusSuccess, actions_model.StatusSkipped: case actions_model.StatusSuccess:
return api.CommitStatusSuccess return api.CommitStatusSuccess
case actions_model.StatusFailure, actions_model.StatusCancelled: case actions_model.StatusFailure, actions_model.StatusCancelled:
return api.CommitStatusFailure return api.CommitStatusFailure
case actions_model.StatusWaiting, actions_model.StatusBlocked, actions_model.StatusRunning: case actions_model.StatusWaiting, actions_model.StatusBlocked, actions_model.StatusRunning:
return api.CommitStatusPending return api.CommitStatusPending
case actions_model.StatusSkipped:
return api.CommitStatusSkipped
default: default:
return api.CommitStatusError return api.CommitStatusError
} }

View File

@ -31,16 +31,6 @@ import (
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
) )
func getActionWorkflowPath(commit *git.Commit) string {
paths := []string{".gitea/workflows", ".github/workflows"}
for _, treePath := range paths {
if _, err := commit.SubTree(treePath); err == nil {
return treePath
}
}
return ""
}
func getActionWorkflowEntry(ctx *context.APIContext, commit *git.Commit, folder string, entry *git.TreeEntry) *api.ActionWorkflow { func getActionWorkflowEntry(ctx *context.APIContext, commit *git.Commit, folder string, entry *git.TreeEntry) *api.ActionWorkflow {
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions) cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
cfg := cfgUnit.ActionsConfig() cfg := cfgUnit.ActionsConfig()
@ -109,14 +99,12 @@ func ListActionWorkflows(ctx *context.APIContext) ([]*api.ActionWorkflow, error)
return nil, err return nil, err
} }
entries, err := actions.ListWorkflows(defaultBranchCommit) folder, entries, err := actions.ListWorkflows(defaultBranchCommit)
if err != nil { if err != nil {
ctx.APIError(http.StatusNotFound, err.Error()) ctx.APIError(http.StatusNotFound, err.Error())
return nil, err return nil, err
} }
folder := getActionWorkflowPath(defaultBranchCommit)
workflows := make([]*api.ActionWorkflow, len(entries)) workflows := make([]*api.ActionWorkflow, len(entries))
for i, entry := range entries { for i, entry := range entries {
workflows[i] = getActionWorkflowEntry(ctx, defaultBranchCommit, folder, entry) workflows[i] = getActionWorkflowEntry(ctx, defaultBranchCommit, folder, entry)
@ -185,7 +173,7 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re
} }
// get workflow entry from runTargetCommit // get workflow entry from runTargetCommit
entries, err := actions.ListWorkflows(runTargetCommit) _, entries, err := actions.ListWorkflows(runTargetCommit)
if err != nil { if err != nil {
return err return err
} }

View File

@ -42,13 +42,14 @@ func ToCombinedStatus(ctx context.Context, statuses []*git_model.CommitStatus, r
SHA: statuses[0].SHA, SHA: statuses[0].SHA,
TotalCount: len(statuses), TotalCount: len(statuses),
Repository: repo, Repository: repo,
URL: "", URL: "", // never set or used?
State: api.CommitStatusSuccess,
} }
retStatus.Statuses = make([]*api.CommitStatus, 0, len(statuses)) retStatus.Statuses = make([]*api.CommitStatus, 0, len(statuses))
for _, status := range statuses { for _, status := range statuses {
retStatus.Statuses = append(retStatus.Statuses, ToCommitStatus(ctx, status)) retStatus.Statuses = append(retStatus.Statuses, ToCommitStatus(ctx, status))
if retStatus.State == "" || status.State.NoBetterThan(retStatus.State) { if status.State.HasHigherPriorityThan(retStatus.State) {
retStatus.State = status.State retStatus.State = status.State
} }
} }
@ -57,9 +58,13 @@ func ToCombinedStatus(ctx context.Context, statuses []*git_model.CommitStatus, r
// > failure if any of the contexts report as error or failure // > failure if any of the contexts report as error or failure
// > pending if there are no statuses or a context is pending // > pending if there are no statuses or a context is pending
// > success if the latest status for all contexts is success // > success if the latest status for all contexts is success
if retStatus.State.IsError() { switch retStatus.State {
retStatus.State = api.CommitStatusFailure case api.CommitStatusSkipped:
retStatus.State = api.CommitStatusSuccess // all skipped means success
case api.CommitStatusPending, api.CommitStatusSuccess:
// use the current state for pending or success
default:
retStatus.State = api.CommitStatusFailure // otherwise, it is a failure
} }
return retStatus return retStatus
} }

View File

@ -84,7 +84,7 @@ func ParseCommitsWithStatus(ctx context.Context, oldCommits []*asymkey_model.Sig
commit := &git_model.SignCommitWithStatuses{ commit := &git_model.SignCommitWithStatuses{
SignCommit: c, SignCommit: c,
} }
statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, commit.ID.String(), db.ListOptions{}) statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, commit.ID.String(), db.ListOptionsAll)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -54,6 +54,8 @@ func ToggleAssigneeWithNotify(ctx context.Context, issue *issues_model.Issue, do
if err != nil { if err != nil {
return false, nil, err return false, nil, err
} }
issue.AssigneeID = assigneeID
issue.Assignee = assignee
notify_service.IssueChangeAssignee(ctx, doer, issue, assignee, removed, comment) notify_service.IssueChangeAssignee(ctx, doer, issue, assignee, removed, comment)

View File

@ -180,11 +180,15 @@ func (c *CodeCommitDownloader) GetPullRequests(ctx context.Context, page, perPag
continue continue
} }
target := orig.PullRequestTargets[0] target := orig.PullRequestTargets[0]
description := ""
if orig.Description != nil {
description = *orig.Description
}
pr := &base.PullRequest{ pr := &base.PullRequest{
Number: number, Number: number,
Title: *orig.Title, Title: *orig.Title,
PosterName: c.getUsernameFromARN(*orig.AuthorArn), PosterName: c.getUsernameFromARN(*orig.AuthorArn),
Content: *orig.Description, Content: description,
State: "open", State: "open",
Created: *orig.CreationDate, Created: *orig.CreationDate,
Updated: *orig.LastActivityDate, Updated: *orig.LastActivityDate,

View File

@ -46,59 +46,33 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus,
// If required rule not match any action, then it is pending // If required rule not match any action, then it is pending
if targetStatus == "" { if targetStatus == "" {
if structs.CommitStatusPending.NoBetterThan(returnedStatus) { if structs.CommitStatusPending.HasHigherPriorityThan(returnedStatus) {
returnedStatus = structs.CommitStatusPending returnedStatus = structs.CommitStatusPending
} }
break break
} }
if targetStatus.NoBetterThan(returnedStatus) { if targetStatus.HasHigherPriorityThan(returnedStatus) {
returnedStatus = targetStatus returnedStatus = targetStatus
} }
} }
} }
if matchedCount == 0 && returnedStatus == structs.CommitStatusSuccess { if matchedCount == 0 && returnedStatus == structs.CommitStatusSuccess {
status := git_model.CalcCommitStatus(commitStatuses) if len(commitStatuses) == 0 {
if status != nil { // "no statuses" should mean "pending"
return status.State return structs.CommitStatusPending
} }
return structs.CommitStatusSuccess status := git_model.CalcCommitStatus(commitStatuses)
if status.State == structs.CommitStatusSkipped {
return structs.CommitStatusSuccess // if all statuses are skipped, return success
}
return status.State
} }
return returnedStatus return returnedStatus
} }
// IsCommitStatusContextSuccess returns true if all required status check contexts succeed.
func IsCommitStatusContextSuccess(commitStatuses []*git_model.CommitStatus, requiredContexts []string) bool {
// If no specific context is required, require that last commit status is a success
if len(requiredContexts) == 0 {
status := git_model.CalcCommitStatus(commitStatuses)
if status == nil || status.State != structs.CommitStatusSuccess {
return false
}
return true
}
for _, ctx := range requiredContexts {
var found bool
for _, commitStatus := range commitStatuses {
if commitStatus.Context == ctx {
if commitStatus.State != structs.CommitStatusSuccess {
return false
}
found = true
break
}
}
if !found {
return false
}
}
return true
}
// IsPullCommitStatusPass returns if all required status checks PASS // IsPullCommitStatusPass returns if all required status checks PASS
func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (bool, error) { func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (bool, error) {
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
@ -151,7 +125,7 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR
return "", errors.Wrap(err, "LoadBaseRepo") return "", errors.Wrap(err, "LoadBaseRepo")
} }
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll) commitStatuses, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
if err != nil { if err != nil {
return "", errors.Wrap(err, "GetLatestCommitStatus") return "", errors.Wrap(err, "GetLatestCommitStatus")
} }

View File

@ -14,52 +14,70 @@ import (
) )
func TestMergeRequiredContextsCommitStatus(t *testing.T) { func TestMergeRequiredContextsCommitStatus(t *testing.T) {
testCases := [][]*git_model.CommitStatus{ cases := []struct {
commitStatuses []*git_model.CommitStatus
requiredContexts []string
expected structs.CommitStatusState
}{
{ {
{Context: "Build 1", State: structs.CommitStatusSuccess}, commitStatuses: []*git_model.CommitStatus{},
{Context: "Build 2", State: structs.CommitStatusSuccess}, requiredContexts: []string{},
{Context: "Build 3", State: structs.CommitStatusSuccess}, expected: structs.CommitStatusPending,
}, },
{ {
{Context: "Build 1", State: structs.CommitStatusSuccess}, commitStatuses: []*git_model.CommitStatus{
{Context: "Build 2", State: structs.CommitStatusSuccess}, {Context: "Build xxx", State: structs.CommitStatusSkipped},
{Context: "Build 2t", State: structs.CommitStatusPending}, },
requiredContexts: []string{"Build*"},
expected: structs.CommitStatusSuccess,
}, },
{ {
{Context: "Build 1", State: structs.CommitStatusSuccess}, commitStatuses: []*git_model.CommitStatus{
{Context: "Build 2", State: structs.CommitStatusSuccess}, {Context: "Build 1", State: structs.CommitStatusSkipped},
{Context: "Build 2t", State: structs.CommitStatusFailure}, {Context: "Build 2", State: structs.CommitStatusSuccess},
{Context: "Build 3", State: structs.CommitStatusSuccess},
},
requiredContexts: []string{"Build*"},
expected: structs.CommitStatusSuccess,
}, },
{ {
{Context: "Build 1", State: structs.CommitStatusSuccess}, commitStatuses: []*git_model.CommitStatus{
{Context: "Build 2", State: structs.CommitStatusSuccess}, {Context: "Build 1", State: structs.CommitStatusSuccess},
{Context: "Build 2t", State: structs.CommitStatusSuccess}, {Context: "Build 2", State: structs.CommitStatusSuccess},
{Context: "Build 2t", State: structs.CommitStatusPending},
},
requiredContexts: []string{"Build*", "Build 2t*"},
expected: structs.CommitStatusPending,
}, },
{ {
{Context: "Build 1", State: structs.CommitStatusSuccess}, commitStatuses: []*git_model.CommitStatus{
{Context: "Build 2", State: structs.CommitStatusSuccess}, {Context: "Build 1", State: structs.CommitStatusSuccess},
{Context: "Build 2t", State: structs.CommitStatusSuccess}, {Context: "Build 2", State: structs.CommitStatusSuccess},
{Context: "Build 2t", State: structs.CommitStatusFailure},
},
requiredContexts: []string{"Build*", "Build 2t*"},
expected: structs.CommitStatusFailure,
},
{
commitStatuses: []*git_model.CommitStatus{
{Context: "Build 1", State: structs.CommitStatusSuccess},
{Context: "Build 2", State: structs.CommitStatusSuccess},
{Context: "Build 2t", State: structs.CommitStatusSuccess},
},
requiredContexts: []string{"Build*", "Build 2t*", "Build 3*"},
expected: structs.CommitStatusPending,
},
{
commitStatuses: []*git_model.CommitStatus{
{Context: "Build 1", State: structs.CommitStatusSuccess},
{Context: "Build 2", State: structs.CommitStatusSuccess},
{Context: "Build 2t", State: structs.CommitStatusSuccess},
},
requiredContexts: []string{"Build*", "Build *", "Build 2t*", "Build 1*"},
expected: structs.CommitStatusSuccess,
}, },
} }
testCasesRequiredContexts := [][]string{ for i, c := range cases {
{"Build*"}, assert.Equal(t, c.expected, MergeRequiredContextsCommitStatus(c.commitStatuses, c.requiredContexts), "case %d", i)
{"Build*", "Build 2t*"},
{"Build*", "Build 2t*"},
{"Build*", "Build 2t*", "Build 3*"},
{"Build*", "Build *", "Build 2t*", "Build 1*"},
}
testCasesExpected := []structs.CommitStatusState{
structs.CommitStatusSuccess,
structs.CommitStatusPending,
structs.CommitStatusFailure,
structs.CommitStatusPending,
structs.CommitStatusSuccess,
}
for i, commitStatuses := range testCases {
if MergeRequiredContextsCommitStatus(commitStatuses, testCasesRequiredContexts[i]) != testCasesExpected[i] {
assert.Fail(t, "Test case failed", "Test case %d failed", i+1)
}
} }
} }

View File

@ -1004,7 +1004,7 @@ func getAllCommitStatus(ctx context.Context, gitRepo *git.Repository, pr *issues
return nil, nil, shaErr return nil, nil, shaErr
} }
statuses, _, err = git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll) statuses, err = git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
lastStatus = git_model.CalcCommitStatus(statuses) lastStatus = git_model.CalcCommitStatus(statuses)
return statuses, lastStatus, err return statuses, lastStatus, err
} }

View File

@ -121,7 +121,7 @@ func (graph *Graph) LoadAndProcessCommits(ctx context.Context, repository *repo_
return repo_model.IsOwnerMemberCollaborator(ctx, repository, user.ID) return repo_model.IsOwnerMemberCollaborator(ctx, repository, user.ID)
}, &keyMap) }, &keyMap)
statuses, _, err := git_model.GetLatestCommitStatus(ctx, repository.ID, c.Commit.ID.String(), db.ListOptions{}) statuses, err := git_model.GetLatestCommitStatus(ctx, repository.ID, c.Commit.ID.String(), db.ListOptionsAll)
if err != nil { if err != nil {
log.Error("GetLatestCommitStatus: %v", err) log.Error("GetLatestCommitStatus: %v", err)
} else { } else {

View File

@ -36,15 +36,20 @@
<div class="run-list-meta">{{svg "octicon-calendar" 16}}{{DateUtils.TimeSince $run.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}}{{$run.Duration}}</div> <div class="run-list-meta">{{svg "octicon-stopwatch" 16}}{{$run.Duration}}</div>
</div> </div>
{{if and ($.AllowDeleteWorkflowRuns) ($run.Status.IsDone)}} <div class="ui dropdown jump tw-p-2">
<button class="btn interact-bg link-action tw-p-2" {{svg "octicon-kebab-horizontal"}}
data-url="{{$run.Link}}/delete" <div class="menu flex-items-menu">
data-modal-confirm="{{ctx.Locale.Tr "actions.runs.delete.description"}}" <a class="item" href="{{$run.Link}}/workflow">{{svg "octicon-play"}}{{ctx.Locale.Tr "actions.runs.view_workflow_file"}}</a>
data-tooltip-content="{{ctx.Locale.Tr "actions.runs.delete"}}" {{if and $.AllowDeleteWorkflowRuns $run.Status.IsDone}}
> <a class="item link-action"
{{svg "octicon-trash"}} data-url="{{$run.Link}}/delete"
</button> data-modal-confirm="{{ctx.Locale.Tr "actions.runs.delete.description"}}"
{{end}} >
{{svg "octicon-trash"}}{{ctx.Locale.Tr "actions.runs.delete"}}
</a>
{{end}}
</div>
</div>
</div> </div>
</div> </div>
{{end}} {{end}}

View File

@ -14,3 +14,6 @@
{{if eq .State "warning"}} {{if eq .State "warning"}}
{{svg "gitea-exclamation" 18 "commit-status icon text yellow"}} {{svg "gitea-exclamation" 18 "commit-status icon text yellow"}}
{{end}} {{end}}
{{if eq .State "skipped"}}
{{svg "octicon-skip" 18 "commit-status icon text grey"}}
{{end}}

View File

@ -2259,16 +2259,16 @@
], ],
"responses": { "responses": {
"201": { "201": {
"description": "response when creating an org-level variable" "description": "successfully created the org-level variable"
},
"204": {
"description": "response when creating an org-level variable"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
}, },
"404": { "409": {
"$ref": "#/responses/notFound" "description": "variable name already exists."
},
"500": {
"$ref": "#/responses/error"
} }
} }
}, },
@ -5263,14 +5263,14 @@
"201": { "201": {
"description": "response when creating a repo-level variable" "description": "response when creating a repo-level variable"
}, },
"204": {
"description": "response when creating a repo-level variable"
},
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
}, },
"404": { "409": {
"$ref": "#/responses/notFound" "description": "variable name already exists."
},
"500": {
"$ref": "#/responses/error"
} }
} }
}, },
@ -12871,6 +12871,7 @@
"enum": [ "enum": [
"oldest", "oldest",
"recentupdate", "recentupdate",
"recentclose",
"leastupdate", "leastupdate",
"mostcomment", "mostcomment",
"leastcomment", "leastcomment",
@ -17863,16 +17864,13 @@
], ],
"responses": { "responses": {
"201": { "201": {
"description": "response when creating a variable" "description": "successfully created the user-level variable"
},
"204": {
"description": "response when creating a variable"
}, },
"400": { "400": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
}, },
"404": { "409": {
"$ref": "#/responses/notFound" "description": "variable name already exists."
} }
} }
}, },

View File

@ -633,7 +633,7 @@ jobs:
assert.NotEmpty(t, addFileResp) assert.NotEmpty(t, addFileResp)
sha = addFileResp.Commit.SHA sha = addFileResp.Commit.SHA
assert.Eventually(t, func() bool { assert.Eventually(t, func() bool {
latestCommitStatuses, _, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo.ID, sha, db.ListOptionsAll) latestCommitStatuses, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo.ID, sha, db.ListOptionsAll)
assert.NoError(t, err) assert.NoError(t, err)
if len(latestCommitStatuses) == 0 { if len(latestCommitStatuses) == 0 {
return false return false
@ -676,7 +676,7 @@ jobs:
} }
func checkCommitStatusAndInsertFakeStatus(t *testing.T, repo *repo_model.Repository, sha string) { func checkCommitStatusAndInsertFakeStatus(t *testing.T, repo *repo_model.Repository, sha string) {
latestCommitStatuses, _, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo.ID, sha, db.ListOptionsAll) latestCommitStatuses, err := git_model.GetLatestCommitStatus(db.DefaultContext, repo.ID, sha, db.ListOptionsAll)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, latestCommitStatuses, 1) assert.Len(t, latestCommitStatuses, 1)
assert.Equal(t, api.CommitStatusPending, latestCommitStatuses[0].State) assert.Equal(t, api.CommitStatusPending, latestCommitStatuses[0].State)

View File

@ -35,11 +35,11 @@ func TestAPIRepoVariables(t *testing.T) {
}, },
{ {
Name: "_", Name: "_",
ExpectedStatus: http.StatusNoContent, ExpectedStatus: http.StatusCreated,
}, },
{ {
Name: "TEST_VAR", Name: "TEST_VAR",
ExpectedStatus: http.StatusNoContent, ExpectedStatus: http.StatusCreated,
}, },
{ {
Name: "test_var", Name: "test_var",
@ -81,7 +81,7 @@ func TestAPIRepoVariables(t *testing.T) {
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
Value: "initial_val", Value: "initial_val",
}).AddTokenAuth(token) }).AddTokenAuth(token)
MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusCreated)
cases := []struct { cases := []struct {
Name string Name string
@ -138,7 +138,7 @@ func TestAPIRepoVariables(t *testing.T) {
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
Value: "initial_val", Value: "initial_val",
}).AddTokenAuth(token) }).AddTokenAuth(token)
MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusCreated)
req = NewRequest(t, "DELETE", url).AddTokenAuth(token) req = NewRequest(t, "DELETE", url).AddTokenAuth(token)
MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusNoContent)

View File

@ -29,11 +29,11 @@ func TestAPIUserVariables(t *testing.T) {
}, },
{ {
Name: "_", Name: "_",
ExpectedStatus: http.StatusNoContent, ExpectedStatus: http.StatusCreated,
}, },
{ {
Name: "TEST_VAR", Name: "TEST_VAR",
ExpectedStatus: http.StatusNoContent, ExpectedStatus: http.StatusCreated,
}, },
{ {
Name: "test_var", Name: "test_var",
@ -75,7 +75,7 @@ func TestAPIUserVariables(t *testing.T) {
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
Value: "initial_val", Value: "initial_val",
}).AddTokenAuth(token) }).AddTokenAuth(token)
MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusCreated)
cases := []struct { cases := []struct {
Name string Name string
@ -132,7 +132,7 @@ func TestAPIUserVariables(t *testing.T) {
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{
Value: "initial_val", Value: "initial_val",
}).AddTokenAuth(token) }).AddTokenAuth(token)
MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusCreated)
req = NewRequest(t, "DELETE", url).AddTokenAuth(token) req = NewRequest(t, "DELETE", url).AddTokenAuth(token)
MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusNoContent)

View File

@ -151,6 +151,15 @@ func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content
return issueURL return issueURL
} }
func testIssueAssign(t *testing.T, session *TestSession, repoLink string, issueID, assigneeID int64) {
req := NewRequestWithValues(t, "POST", fmt.Sprintf(repoLink+"/issues/assignee?issue_ids=%d", issueID), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
"id": strconv.FormatInt(assigneeID, 10),
"action": "", // empty action means assign
})
session.MakeRequest(t, req, http.StatusOK)
}
func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) int64 { func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) int64 {
req := NewRequest(t, "GET", issueURL) req := NewRequest(t, "GET", issueURL)
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)

View File

@ -131,19 +131,19 @@ func (m *mockWebhookProvider) Close() {
} }
func Test_WebhookCreate(t *testing.T) { func Test_WebhookCreate(t *testing.T) {
var payloads []api.CreatePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.CreatePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = string(webhook_module.HookEventCreate)
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var payloads []api.CreatePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.CreatePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = string(webhook_module.HookEventCreate)
}, http.StatusOK)
defer provider.Close()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -163,19 +163,19 @@ func Test_WebhookCreate(t *testing.T) {
} }
func Test_WebhookDelete(t *testing.T) { func Test_WebhookDelete(t *testing.T) {
var payloads []api.DeletePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.DeletePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "delete"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var payloads []api.DeletePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.DeletePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "delete"
}, http.StatusOK)
defer provider.Close()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -196,19 +196,19 @@ func Test_WebhookDelete(t *testing.T) {
} }
func Test_WebhookFork(t *testing.T) { func Test_WebhookFork(t *testing.T) {
var payloads []api.ForkPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.ForkPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "fork"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var payloads []api.ForkPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.ForkPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "fork"
}, http.StatusOK)
defer provider.Close()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user1") session := loginUser(t, "user1")
@ -228,19 +228,19 @@ func Test_WebhookFork(t *testing.T) {
} }
func Test_WebhookIssueComment(t *testing.T) { func Test_WebhookIssueComment(t *testing.T) {
var payloads []api.IssueCommentPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.IssueCommentPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "issue_comment"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var payloads []api.IssueCommentPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.IssueCommentPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "issue_comment"
}, http.StatusOK)
defer provider.Close()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -312,19 +312,19 @@ func Test_WebhookIssueComment(t *testing.T) {
} }
func Test_WebhookRelease(t *testing.T) { func Test_WebhookRelease(t *testing.T) {
var payloads []api.ReleasePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.ReleasePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "release"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var payloads []api.ReleasePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.ReleasePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "release"
}, http.StatusOK)
defer provider.Close()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -345,19 +345,19 @@ func Test_WebhookRelease(t *testing.T) {
} }
func Test_WebhookPush(t *testing.T) { func Test_WebhookPush(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) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
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()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -416,19 +416,19 @@ func Test_WebhookPushDevBranch(t *testing.T) {
} }
func Test_WebhookIssue(t *testing.T) { func Test_WebhookIssue(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) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
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()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -445,6 +445,45 @@ func Test_WebhookIssue(t *testing.T) {
assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
assert.Equal(t, "Title1", payloads[0].Issue.Title) assert.Equal(t, "Title1", payloads[0].Issue.Title)
assert.Equal(t, "Description1", payloads[0].Issue.Body) assert.Equal(t, "Description1", payloads[0].Issue.Body)
assert.Positive(t, payloads[0].Issue.Created.Unix())
assert.Positive(t, payloads[0].Issue.Updated.Unix())
})
}
func Test_WebhookIssueAssign(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var payloads []api.PullRequestPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.PullRequestPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "pull_request_assign"
}, http.StatusOK)
defer provider.Close()
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request_assign")
// 2. trigger the webhook, issue 2 is a pull request
testIssueAssign(t, session, repo1.Link(), 2, user2.ID)
// 3. validate the webhook is triggered
assert.Equal(t, "pull_request_assign", triggeredEvent)
assert.Len(t, payloads, 1)
assert.EqualValues(t, "assigned", payloads[0].Action)
assert.Equal(t, "repo1", payloads[0].PullRequest.Base.Repository.Name)
assert.Equal(t, "user2/repo1", payloads[0].PullRequest.Base.Repository.FullName)
assert.Equal(t, "issue2", payloads[0].PullRequest.Title)
assert.Equal(t, "content for the second issue", payloads[0].PullRequest.Body)
assert.Equal(t, user2.ID, payloads[0].PullRequest.Assignee.ID)
}) })
} }
@ -521,19 +560,19 @@ func Test_WebhookIssueMilestone(t *testing.T) {
} }
func Test_WebhookPullRequest(t *testing.T) { func Test_WebhookPullRequest(t *testing.T) {
var payloads []api.PullRequestPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.PullRequestPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "pull_request"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var payloads []api.PullRequestPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.PullRequestPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "pull_request"
}, http.StatusOK)
defer provider.Close()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -558,19 +597,19 @@ func Test_WebhookPullRequest(t *testing.T) {
} }
func Test_WebhookPullRequestComment(t *testing.T) { func Test_WebhookPullRequestComment(t *testing.T) {
var payloads []api.IssueCommentPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.IssueCommentPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "pull_request_comment"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var payloads []api.IssueCommentPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.IssueCommentPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "pull_request_comment"
}, http.StatusOK)
defer provider.Close()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -596,19 +635,19 @@ func Test_WebhookPullRequestComment(t *testing.T) {
} }
func Test_WebhookWiki(t *testing.T) { func Test_WebhookWiki(t *testing.T) {
var payloads []api.WikiPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.WikiPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "wiki"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var payloads []api.WikiPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.WikiPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "wiki"
}, http.StatusOK)
defer provider.Close()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -628,19 +667,19 @@ func Test_WebhookWiki(t *testing.T) {
} }
func Test_WebhookRepository(t *testing.T) { func Test_WebhookRepository(t *testing.T) {
var payloads []api.RepositoryPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.RepositoryPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "repository"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var payloads []api.RepositoryPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.RepositoryPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "repository"
}, http.StatusOK)
defer provider.Close()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user1") session := loginUser(t, "user1")
@ -660,19 +699,19 @@ func Test_WebhookRepository(t *testing.T) {
} }
func Test_WebhookPackage(t *testing.T) { func Test_WebhookPackage(t *testing.T) {
var payloads []api.PackagePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.PackagePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "package"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var payloads []api.PackagePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.PackagePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "package"
}, http.StatusOK)
defer provider.Close()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user1") session := loginUser(t, "user1")
@ -697,24 +736,24 @@ func Test_WebhookPackage(t *testing.T) {
} }
func Test_WebhookStatus(t *testing.T) { func Test_WebhookStatus(t *testing.T) {
var payloads []api.CommitStatusPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
assert.Contains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should contain status")
assert.Contains(t, r.Header["X-Github-Hook-Installation-Target-Type"], "repository", "X-GitHub-Hook-Installation-Target-Type should contain repository")
assert.Contains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should contain status")
assert.Contains(t, r.Header["X-Gitea-Hook-Installation-Target-Type"], "repository", "X-Gitea-Hook-Installation-Target-Type should contain repository")
assert.Contains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should contain status")
content, _ := io.ReadAll(r.Body)
var payload api.CommitStatusPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "status"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var payloads []api.CommitStatusPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
assert.Contains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should contain status")
assert.Contains(t, r.Header["X-Github-Hook-Installation-Target-Type"], "repository", "X-GitHub-Hook-Installation-Target-Type should contain repository")
assert.Contains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should contain status")
assert.Contains(t, r.Header["X-Gitea-Hook-Installation-Target-Type"], "repository", "X-Gitea-Hook-Installation-Target-Type should contain repository")
assert.Contains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should contain status")
content, _ := io.ReadAll(r.Body)
var payload api.CommitStatusPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "status"
}, http.StatusOK)
defer provider.Close()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -750,16 +789,16 @@ func Test_WebhookStatus(t *testing.T) {
} }
func Test_WebhookStatus_NoWrongTrigger(t *testing.T) { func Test_WebhookStatus_NoWrongTrigger(t *testing.T) {
var trigger string
provider := newMockWebhookProvider(func(r *http.Request) {
assert.NotContains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should not contain status")
assert.NotContains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should not contain status")
assert.NotContains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should not contain status")
trigger = "push"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var trigger string
provider := newMockWebhookProvider(func(r *http.Request) {
assert.NotContains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should not contain status")
assert.NotContains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should not contain status")
assert.NotContains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should not contain status")
trigger = "push"
}, http.StatusOK)
defer provider.Close()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2") session := loginUser(t, "user2")
@ -775,22 +814,22 @@ func Test_WebhookStatus_NoWrongTrigger(t *testing.T) {
} }
func Test_WebhookWorkflowJob(t *testing.T) { func Test_WebhookWorkflowJob(t *testing.T) {
var payloads []api.WorkflowJobPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
assert.Contains(t, r.Header["X-Github-Event-Type"], "workflow_job", "X-GitHub-Event-Type should contain workflow_job")
assert.Contains(t, r.Header["X-Gitea-Event-Type"], "workflow_job", "X-Gitea-Event-Type should contain workflow_job")
assert.Contains(t, r.Header["X-Gogs-Event-Type"], "workflow_job", "X-Gogs-Event-Type should contain workflow_job")
content, _ := io.ReadAll(r.Body)
var payload api.WorkflowJobPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "workflow_job"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
var payloads []api.WorkflowJobPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
assert.Contains(t, r.Header["X-Github-Event-Type"], "workflow_job", "X-GitHub-Event-Type should contain workflow_job")
assert.Contains(t, r.Header["X-Gitea-Event-Type"], "workflow_job", "X-Gitea-Event-Type should contain workflow_job")
assert.Contains(t, r.Header["X-Gogs-Event-Type"], "workflow_job", "X-Gogs-Event-Type should contain workflow_job")
content, _ := io.ReadAll(r.Body)
var payload api.WorkflowJobPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "workflow_job"
}, http.StatusOK)
defer provider.Close()
// 1. create a new webhook with special webhook for repo1 // 1. create a new webhook with special webhook for repo1
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
session := loginUser(t, "user2") session := loginUser(t, "user2")

View File

@ -815,10 +815,6 @@ overflow-menu .ui.label {
display: block; display: block;
} }
.code-view .lines-num span::after {
cursor: pointer;
}
.lines-type-marker { .lines-type-marker {
vertical-align: top; vertical-align: top;
white-space: nowrap; white-space: nowrap;
@ -855,39 +851,13 @@ overflow-menu .ui.label {
.lines-escape { .lines-escape {
width: 0; width: 0;
white-space: nowrap; white-space: nowrap;
padding: 0;
} }
.lines-code { .lines-code {
padding-left: 5px; padding-left: 5px;
} }
.file-view tr.active {
color: inherit !important;
background: inherit !important;
}
.file-view tr.active .lines-num,
.file-view tr.active .lines-code {
background: var(--color-highlight-bg) !important;
}
.file-view tr.active:last-of-type .lines-code {
border-bottom-right-radius: var(--border-radius);
}
.file-view tr.active .lines-num {
position: relative;
}
.file-view tr.active .lines-num::before {
content: "";
position: absolute;
left: 0;
width: 2px;
height: 100%;
background: var(--color-highlight-fg);
}
.code-inner { .code-inner {
font: 12px var(--fonts-monospace); font: 12px var(--fonts-monospace);
white-space: pre-wrap; white-space: pre-wrap;
@ -938,12 +908,12 @@ overflow-menu .ui.label {
margin-right: 4px; margin-right: 4px;
} }
.top-line-blame { tr.top-line-blame {
border-top: 1px solid var(--color-secondary); border-top: 1px solid var(--color-secondary);
} }
.code-view tr.top-line-blame:first-of-type { tr.top-line-blame:first-of-type {
border-top: none; border-top: none; /* merge code lines belonging to the same commit into one block */
} }
.lines-code .bottom-line, .lines-code .bottom-line,
@ -951,15 +921,6 @@ overflow-menu .ui.label {
border-bottom: 1px solid var(--color-secondary); border-bottom: 1px solid var(--color-secondary);
} }
.code-view {
background: var(--color-code-bg);
border-radius: var(--border-radius);
}
.code-view table {
width: 100%;
}
.migrate .svg.gitea-git { .migrate .svg.gitea-git {
color: var(--color-git); color: var(--color-git);
} }

View File

@ -62,7 +62,7 @@
@import "./repo/issue-label.css"; @import "./repo/issue-label.css";
@import "./repo/issue-list.css"; @import "./repo/issue-list.css";
@import "./repo/list-header.css"; @import "./repo/list-header.css";
@import "./repo/linebutton.css"; @import "./repo/file-view.css";
@import "./repo/wiki.css"; @import "./repo/wiki.css";
@import "./repo/header.css"; @import "./repo/header.css";
@import "./repo/home.css"; @import "./repo/home.css";

View File

@ -309,10 +309,18 @@
box-sizing: initial; box-sizing: initial;
} }
.file-view.markup {
padding: 1em 2em;
}
.file-view.markup:has(.file-not-rendered-prompt) {
padding: 0; /* let the file-not-rendered-prompt layout itself */
}
/* this background ensures images can break <hr>. We can only do this on /* this background ensures images can break <hr>. We can only do this on
cases where the background is known and not transparent. */ cases where the background is known and not transparent. */
.markup.file-view img, .file-view.markup img,
.markup.file-view video, .file-view.markup video,
.comment-body .markup img, /* regular comment */ .comment-body .markup img, /* regular comment */
.comment-body .markup video, .comment-body .markup video,
.comment-content .markup img, /* code comment */ .comment-content .markup img, /* code comment */

View File

@ -1238,21 +1238,6 @@ td .commit-summary {
white-space: nowrap; white-space: nowrap;
} }
.file-view.markup {
padding: 1em 2em;
}
.file-view.markup:has(.file-not-rendered-prompt) {
padding: 0; /* let the file-not-rendered-prompt layout itself */
}
.file-not-rendered-prompt {
padding: 1rem;
text-align: center;
font-size: 1rem !important; /* use consistent styles for various containers (code, markup, etc) */
line-height: var(--line-height-default) !important; /* same as above */
}
.repository .activity-header { .repository .activity-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View File

@ -0,0 +1,58 @@
.file-view tr.active {
background: var(--color-highlight-bg);
}
.file-view tr.active:last-of-type .lines-code {
border-bottom-right-radius: var(--border-radius);
}
.file-view tr.active .lines-num {
position: relative;
}
.file-view tr.active .lines-num::before {
content: "";
position: absolute;
left: 0;
width: 2px;
height: 100%;
background: var(--color-highlight-fg);
}
.file-view .file-not-rendered-prompt {
padding: 1rem;
text-align: center;
font-size: 1rem !important; /* use consistent styles for various containers (code, markup, etc) */
line-height: var(--line-height-default) !important; /* same as above */
}
/* ".code-view" is always used with ".file-view", to show the code of a file */
.file-view.code-view {
background: var(--color-code-bg);
border-radius: var(--border-radius);
}
.file-view.code-view table {
width: 100%;
}
.file-view.code-view .lines-num span::after {
cursor: pointer;
}
.file-view.code-view .lines-num:hover {
color: var(--color-text-dark);
}
.file-view.code-view .ui.button.code-line-button {
border: 1px solid var(--color-secondary);
padding: 1px 4px;
margin: 0;
min-height: 0;
position: absolute;
left: 6px;
}
.file-view.code-view .ui.button.code-line-button:hover {
background: var(--color-secondary);
}

View File

@ -1,16 +0,0 @@
.code-view .lines-num:hover {
color: var(--color-text-dark) !important;
}
.ui.button.code-line-button {
border: 1px solid var(--color-secondary);
padding: 1px 4px;
margin: 0;
min-height: 0;
position: absolute;
left: 6px;
}
.ui.button.code-line-button:hover {
background: var(--color-secondary);
}

View File

@ -6,7 +6,7 @@ import {fomanticQuery} from '../modules/fomantic/base.ts';
const {appSubUrl, assetUrlPrefix, pageData} = window.config; const {appSubUrl, assetUrlPrefix, pageData} = window.config;
type CommitStatus = 'pending' | 'success' | 'error' | 'failure' | 'warning'; type CommitStatus = 'pending' | 'success' | 'error' | 'failure' | 'warning' | 'skipped';
type CommitStatusMap = { type CommitStatusMap = {
[status in CommitStatus]: { [status in CommitStatus]: {
@ -22,6 +22,7 @@ const commitStatus: CommitStatusMap = {
error: {name: 'gitea-exclamation', color: 'red'}, error: {name: 'gitea-exclamation', color: 'red'},
failure: {name: 'octicon-x', color: 'red'}, failure: {name: 'octicon-x', color: 'red'},
warning: {name: 'gitea-exclamation', color: 'yellow'}, warning: {name: 'gitea-exclamation', color: 'yellow'},
skipped: {name: 'octicon-skip', color: 'grey'},
}; };
export default defineComponent({ export default defineComponent({