mirror of
1
Fork 0

Merge pull request '[TESTS] fail when log.Error is called' (#2657) from oliverpool/forgejo:fail_test_on_log_error into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2657
This commit is contained in:
Earl Warren 2024-03-24 07:28:31 +00:00
commit 5a18b74632
46 changed files with 732 additions and 105 deletions

View File

@ -271,13 +271,19 @@ package "code.gitea.io/gitea/modules/sync"
package "code.gitea.io/gitea/modules/testlogger" package "code.gitea.io/gitea/modules/testlogger"
func (*testLoggerWriterCloser).pushT func (*testLoggerWriterCloser).pushT
func (*testLoggerWriterCloser).Write func (*testLoggerWriterCloser).Log
func (*testLoggerWriterCloser).recordError
func (*testLoggerWriterCloser).printMsg
func (*testLoggerWriterCloser).popT func (*testLoggerWriterCloser).popT
func (*testLoggerWriterCloser).Close
func (*testLoggerWriterCloser).Reset func (*testLoggerWriterCloser).Reset
func PrintCurrentTest func PrintCurrentTest
func Printf func Printf
func NewTestLoggerWriter func NewTestLoggerWriter
func (*TestLogEventWriter).Base
func (*TestLogEventWriter).GetLevel
func (*TestLogEventWriter).GetWriterName
func (*TestLogEventWriter).GetWriterType
func (*TestLogEventWriter).Run
package "code.gitea.io/gitea/modules/timeutil" package "code.gitea.io/gitea/modules/timeutil"
func GetExecutableModTime func GetExecutableModTime

View File

@ -6,6 +6,7 @@
repo_id: 2 # private repo_id: 2 # private
is_private: true is_private: true
created_unix: 1603228283 created_unix: 1603228283
content: '1|' # issueId 4
- -
id: 2 id: 2

View File

@ -17,6 +17,195 @@
updated: 1683636626 updated: 1683636626
need_approval: 0 need_approval: 0
approved_by: 0 approved_by: 0
event_payload: |
{
"after": "7a3858dc7f059543a8807a8b551304b7e362a7ef",
"before": "0000000000000000000000000000000000000000",
"commits": [
{
"added": [
".forgejo/workflows/test.yml"
],
"author": {
"email": "root@example.com",
"name": "username",
"username": "root"
},
"committer": {
"email": "root@example.com",
"name": "username",
"username": "root"
},
"id": "7a3858dc7f059543a8807a8b551304b7e362a7ef",
"message": "initial commit\n",
"modified": [],
"removed": [],
"timestamp": "2024-01-24T18:59:25Z",
"url": "http://10.201.14.40:3000/root/example-push/commit/7a3858dc7f059543a8807a8b551304b7e362a7ef",
"verification": null
}
],
"compare_url": "http://10.201.14.40:3000/",
"head_commit": {
"added": [
".forgejo/workflows/test.yml"
],
"author": {
"email": "root@example.com",
"name": "username",
"username": "root"
},
"committer": {
"email": "root@example.com",
"name": "username",
"username": "root"
},
"id": "7a3858dc7f059543a8807a8b551304b7e362a7ef",
"message": "initial commit\n",
"modified": [],
"removed": [],
"timestamp": "2024-01-24T18:59:25Z",
"url": "http://10.201.14.40:3000/root/example-push/commit/7a3858dc7f059543a8807a8b551304b7e362a7ef",
"verification": null
},
"pusher": {
"active": false,
"avatar_url": "http://10.201.14.40:3000/avatars/04edfc0ef6c6cf6d6b88fbc69f9f9071",
"created": "2024-01-24T18:57:32Z",
"description": "",
"email": "root@noreply.10.201.14.40",
"followers_count": 0,
"following_count": 0,
"full_name": "",
"id": 1,
"is_admin": false,
"language": "",
"last_login": "0001-01-01T00:00:00Z",
"location": "",
"login": "root",
"login_name": "",
"prohibit_login": false,
"restricted": false,
"starred_repos_count": 0,
"username": "root",
"visibility": "public",
"website": ""
},
"ref": "refs/heads/main",
"repository": {
"allow_merge_commits": true,
"allow_rebase": true,
"allow_rebase_explicit": true,
"allow_rebase_update": true,
"allow_squash_merge": true,
"archived": false,
"archived_at": "1970-01-01T00:00:00Z",
"avatar_url": "",
"clone_url": "http://10.201.14.40:3000/root/example-push.git",
"created_at": "2024-01-24T18:59:25Z",
"default_allow_maintainer_edit": false,
"default_branch": "main",
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"description": "",
"empty": false,
"fork": false,
"forks_count": 0,
"full_name": "root/example-push",
"has_actions": true,
"has_issues": true,
"has_packages": true,
"has_projects": true,
"has_pull_requests": true,
"has_releases": true,
"has_wiki": true,
"html_url": "http://10.201.14.40:3000/root/example-push",
"id": 2,
"ignore_whitespace_conflicts": false,
"internal": false,
"internal_tracker": {
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true,
"enable_time_tracker": true
},
"language": "",
"languages_url": "http://10.201.14.40:3000/api/v1/repos/root/example-push/languages",
"link": "",
"mirror": false,
"mirror_interval": "",
"mirror_updated": "0001-01-01T00:00:00Z",
"name": "example-push",
"object_format_name": "",
"open_issues_count": 0,
"open_pr_counter": 0,
"original_url": "",
"owner": {
"active": false,
"avatar_url": "http://10.201.14.40:3000/avatars/04edfc0ef6c6cf6d6b88fbc69f9f9071",
"created": "2024-01-24T18:57:32Z",
"description": "",
"email": "root@example.com",
"followers_count": 0,
"following_count": 0,
"full_name": "",
"id": 1,
"is_admin": false,
"language": "",
"last_login": "0001-01-01T00:00:00Z",
"location": "",
"login": "root",
"login_name": "",
"prohibit_login": false,
"restricted": false,
"starred_repos_count": 0,
"username": "root",
"visibility": "public",
"website": ""
},
"parent": null,
"permissions": {
"admin": true,
"pull": true,
"push": true
},
"private": false,
"release_counter": 0,
"repo_transfer": null,
"size": 25,
"ssh_url": "forgejo@10.201.14.40:root/example-push.git",
"stars_count": 0,
"template": false,
"updated_at": "2024-01-24T18:59:25Z",
"url": "http://10.201.14.40:3000/api/v1/repos/root/example-push",
"watchers_count": 1,
"website": ""
},
"sender": {
"active": false,
"avatar_url": "http://10.201.14.40:3000/avatars/04edfc0ef6c6cf6d6b88fbc69f9f9071",
"created": "2024-01-24T18:57:32Z",
"description": "",
"email": "root@noreply.10.201.14.40",
"followers_count": 0,
"following_count": 0,
"full_name": "",
"id": 1,
"is_admin": false,
"language": "",
"last_login": "0001-01-01T00:00:00Z",
"location": "",
"login": "root",
"login_name": "",
"prohibit_login": false,
"restricted": false,
"starred_repos_count": 0,
"username": "root",
"visibility": "public",
"website": ""
},
"total_commits": 0
}
- -
id: 792 id: 792
title: "update actions" title: "update actions"
@ -36,3 +225,191 @@
updated: 1683636626 updated: 1683636626
need_approval: 0 need_approval: 0
approved_by: 0 approved_by: 0
event_payload: |
{
"after": "7a3858dc7f059543a8807a8b551304b7e362a7ef",
"before": "0000000000000000000000000000000000000000",
"commits": [
{
"added": [
".forgejo/workflows/test.yml"
],
"author": {
"email": "root@example.com",
"name": "username",
"username": "root"
},
"committer": {
"email": "root@example.com",
"name": "username",
"username": "root"
},
"id": "7a3858dc7f059543a8807a8b551304b7e362a7ef",
"message": "initial commit\n",
"modified": [],
"removed": [],
"timestamp": "2024-01-24T18:59:25Z",
"url": "http://10.201.14.40:3000/root/example-push/commit/7a3858dc7f059543a8807a8b551304b7e362a7ef",
"verification": null
}
],
"compare_url": "http://10.201.14.40:3000/",
"head_commit": {
"added": [
".forgejo/workflows/test.yml"
],
"author": {
"email": "root@example.com",
"name": "username",
"username": "root"
},
"committer": {
"email": "root@example.com",
"name": "username",
"username": "root"
},
"id": "7a3858dc7f059543a8807a8b551304b7e362a7ef",
"message": "initial commit\n",
"modified": [],
"removed": [],
"timestamp": "2024-01-24T18:59:25Z",
"url": "http://10.201.14.40:3000/root/example-push/commit/7a3858dc7f059543a8807a8b551304b7e362a7ef",
"verification": null
},
"pusher": {
"active": false,
"avatar_url": "http://10.201.14.40:3000/avatars/04edfc0ef6c6cf6d6b88fbc69f9f9071",
"created": "2024-01-24T18:57:32Z",
"description": "",
"email": "root@noreply.10.201.14.40",
"followers_count": 0,
"following_count": 0,
"full_name": "",
"id": 1,
"is_admin": false,
"language": "",
"last_login": "0001-01-01T00:00:00Z",
"location": "",
"login": "root",
"login_name": "",
"prohibit_login": false,
"restricted": false,
"starred_repos_count": 0,
"username": "root",
"visibility": "public",
"website": ""
},
"ref": "refs/heads/main",
"repository": {
"allow_merge_commits": true,
"allow_rebase": true,
"allow_rebase_explicit": true,
"allow_rebase_update": true,
"allow_squash_merge": true,
"archived": false,
"archived_at": "1970-01-01T00:00:00Z",
"avatar_url": "",
"clone_url": "http://10.201.14.40:3000/root/example-push.git",
"created_at": "2024-01-24T18:59:25Z",
"default_allow_maintainer_edit": false,
"default_branch": "main",
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"description": "",
"empty": false,
"fork": false,
"forks_count": 0,
"full_name": "root/example-push",
"has_actions": true,
"has_issues": true,
"has_packages": true,
"has_projects": true,
"has_pull_requests": true,
"has_releases": true,
"has_wiki": true,
"html_url": "http://10.201.14.40:3000/root/example-push",
"id": 2,
"ignore_whitespace_conflicts": false,
"internal": false,
"internal_tracker": {
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true,
"enable_time_tracker": true
},
"language": "",
"languages_url": "http://10.201.14.40:3000/api/v1/repos/root/example-push/languages",
"link": "",
"mirror": false,
"mirror_interval": "",
"mirror_updated": "0001-01-01T00:00:00Z",
"name": "example-push",
"object_format_name": "",
"open_issues_count": 0,
"open_pr_counter": 0,
"original_url": "",
"owner": {
"active": false,
"avatar_url": "http://10.201.14.40:3000/avatars/04edfc0ef6c6cf6d6b88fbc69f9f9071",
"created": "2024-01-24T18:57:32Z",
"description": "",
"email": "root@example.com",
"followers_count": 0,
"following_count": 0,
"full_name": "",
"id": 1,
"is_admin": false,
"language": "",
"last_login": "0001-01-01T00:00:00Z",
"location": "",
"login": "root",
"login_name": "",
"prohibit_login": false,
"restricted": false,
"starred_repos_count": 0,
"username": "root",
"visibility": "public",
"website": ""
},
"parent": null,
"permissions": {
"admin": true,
"pull": true,
"push": true
},
"private": false,
"release_counter": 0,
"repo_transfer": null,
"size": 25,
"ssh_url": "forgejo@10.201.14.40:root/example-push.git",
"stars_count": 0,
"template": false,
"updated_at": "2024-01-24T18:59:25Z",
"url": "http://10.201.14.40:3000/api/v1/repos/root/example-push",
"watchers_count": 1,
"website": ""
},
"sender": {
"active": false,
"avatar_url": "http://10.201.14.40:3000/avatars/04edfc0ef6c6cf6d6b88fbc69f9f9071",
"created": "2024-01-24T18:57:32Z",
"description": "",
"email": "root@noreply.10.201.14.40",
"followers_count": 0,
"following_count": 0,
"full_name": "",
"id": 1,
"is_admin": false,
"language": "",
"last_login": "0001-01-01T00:00:00Z",
"location": "",
"login": "root",
"login_name": "",
"prohibit_login": false,
"restricted": false,
"starred_repos_count": 0,
"username": "root",
"visibility": "public",
"website": ""
},
"total_commits": 0
}

View File

@ -1,15 +1,17 @@
- -
id: 1 id: 1
repo_id: 1 repo_id: 1
url: www.example.com/url1 url: http://www.example.com/url1
http_method: POST
content_type: 1 # json content_type: 1 # json
events: '{"push_only":true,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":false}}' events: '{"push_only":true,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":false}}'
is_active: true is_active: false # disable to prevent sending hook task during unrelated tests
- -
id: 2 id: 2
repo_id: 1 repo_id: 1
url: www.example.com/url2 url: http://www.example.com/url2
http_method: POST
content_type: 1 # json content_type: 1 # json
events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}' events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}'
is_active: false is_active: false
@ -18,14 +20,16 @@
id: 3 id: 3
owner_id: 3 owner_id: 3
repo_id: 3 repo_id: 3
url: www.example.com/url3 url: http://www.example.com/url3
http_method: POST
content_type: 1 # json content_type: 1 # json
events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}' events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}'
is_active: true is_active: false
- -
id: 4 id: 4
repo_id: 2 repo_id: 2
url: www.example.com/url4 url: http://www.example.com/url4
http_method: POST
content_type: 1 # json content_type: 1 # json
events: '{"push_only":true,"branch_filter":"{master,feature*}"}' events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
is_active: true is_active: false

View File

@ -199,22 +199,17 @@ func (status *CommitStatus) LocaleString(lang translation.Locale) string {
// 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 {
var lastStatus *CommitStatus if len(statuses) == 0 {
state := api.CommitStatusSuccess return nil
for _, status := range statuses { }
if status.State.NoBetterThan(state) {
state = status.State latestWorstStatus := statuses[0]
lastStatus = status for _, status := range statuses[1:] {
if status.State.NoBetterThan(latestWorstStatus.State) {
latestWorstStatus = status
} }
} }
if lastStatus == nil { return latestWorstStatus
if len(statuses) > 0 {
lastStatus = statuses[0]
} else {
lastStatus = &CommitStatus{}
}
}
return lastStatus
} }
// CommitStatusOptions holds the options for query commit statuses // CommitStatusOptions holds the options for query commit statuses

View File

@ -141,16 +141,20 @@ func Test_CalcCommitStatus(t *testing.T) {
statuses: []*git_model.CommitStatus{ statuses: []*git_model.CommitStatus{
{ {
State: structs.CommitStatusSuccess, State: structs.CommitStatusSuccess,
ID: 1,
}, },
{ {
State: structs.CommitStatusSuccess, State: structs.CommitStatusSuccess,
ID: 2,
}, },
{ {
State: structs.CommitStatusSuccess, State: structs.CommitStatusSuccess,
ID: 3,
}, },
}, },
expected: &git_model.CommitStatus{ expected: &git_model.CommitStatus{
State: structs.CommitStatusSuccess, State: structs.CommitStatusSuccess,
ID: 3,
}, },
}, },
{ {
@ -169,6 +173,10 @@ func Test_CalcCommitStatus(t *testing.T) {
State: structs.CommitStatusError, State: structs.CommitStatusError,
}, },
}, },
{
statuses: []*git_model.CommitStatus{},
expected: nil,
},
} }
for _, kase := range kases { for _, kase := range kases {

View File

@ -500,7 +500,7 @@ func (issue *Issue) GetLastEventLabelFake() string {
// GetIssueByIndex returns raw issue without loading attributes by index in a repository. // GetIssueByIndex returns raw issue without loading attributes by index in a repository.
func GetIssueByIndex(ctx context.Context, repoID, index int64) (*Issue, error) { func GetIssueByIndex(ctx context.Context, repoID, index int64) (*Issue, error) {
if index < 1 { if index < 1 {
return nil, ErrIssueNotExist{} return nil, ErrIssueNotExist{0, repoID, index}
} }
issue := &Issue{ issue := &Issue{
RepoID: repoID, RepoID: repoID,

View File

@ -160,6 +160,10 @@ func MainTest(m *testing.M) {
exitStatus := m.Run() exitStatus := m.Run()
if err := testlogger.WriterCloser.Reset(); err != nil && exitStatus == 0 {
fmt.Printf("testlogger.WriterCloser.Reset: %v\n", err)
os.Exit(1)
}
if err := removeAllWithRetry(setting.RepoRootPath); err != nil { if err := removeAllWithRetry(setting.RepoRootPath); err != nil {
fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err) fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err)
} }

View File

@ -23,9 +23,9 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
Type int Type int
// Permissions // Permissions
IsAdmin bool IsAdmin bool
IsRestricted bool `xorm:"NOT NULL DEFAULT false"` // IsRestricted bool `xorm:"NOT NULL DEFAULT false"` glitch: this column was added in v1_12/v121.go
Visibility int `xorm:"NOT NULL DEFAULT 0"` // Visibility int `xorm:"NOT NULL DEFAULT 0"` glitch: this column was added in v1_12/v124.go
} }
type Review struct { type Review struct {
@ -51,9 +51,9 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
ReviewTypeReject int = 3 ReviewTypeReject int = 3
// VisibleTypePublic Visible for everyone // VisibleTypePublic Visible for everyone
VisibleTypePublic int = 0 // VisibleTypePublic int = 0
// VisibleTypePrivate Visible only for organization's members // VisibleTypePrivate Visible only for organization's members
VisibleTypePrivate int = 2 // VisibleTypePrivate int = 2
// unit.UnitTypeCode is unit type code // unit.UnitTypeCode is unit type code
UnitTypeCode int = 1 UnitTypeCode int = 1
@ -145,9 +145,9 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
hasOrgVisible := true hasOrgVisible := true
// Not SignedUser // Not SignedUser
if user == nil { if user == nil {
hasOrgVisible = repoOwner.Visibility == VisibleTypePublic // hasOrgVisible = repoOwner.Visibility == VisibleTypePublic // VisibleTypePublic is the default
} else if !user.IsAdmin { } else if !user.IsAdmin {
hasMemberWithUserID, err := sess. _, err := sess.
Where("uid=?", user.ID). Where("uid=?", user.ID).
And("org_id=?", repoOwner.ID). And("org_id=?", repoOwner.ID).
Table("org_user"). Table("org_user").
@ -155,9 +155,10 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
if err != nil { if err != nil {
hasOrgVisible = false hasOrgVisible = false
} }
if (repoOwner.Visibility == VisibleTypePrivate || user.IsRestricted) && !hasMemberWithUserID { // VisibleTypePublic is the default so the condition below is always false
hasOrgVisible = false // if (repoOwner.Visibility == VisibleTypePrivate) && !hasMemberWithUserID {
} // hasOrgVisible = false
// }
} }
isCollaborator, err := sess.Get(&Collaboration{RepoID: repo.ID, UserID: user.ID}) isCollaborator, err := sess.Get(&Collaboration{RepoID: repo.ID, UserID: user.ID})
@ -195,7 +196,7 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
if user != nil { if user != nil {
userID = user.ID userID = user.ID
restricted = user.IsRestricted restricted = false
} }
if !restricted && !repo.IsPrivate { if !restricted && !repo.IsPrivate {
@ -284,7 +285,7 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
} }
// for a public repo on an organization, a non-restricted user has read permission on non-team defined units. // for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
if !found && !repo.IsPrivate && !user.IsRestricted { if !found && !repo.IsPrivate {
if _, ok := perm.UnitsMode[u.Type]; !ok { if _, ok := perm.UnitsMode[u.Type]; !ok {
perm.UnitsMode[u.Type] = AccessModeRead perm.UnitsMode[u.Type] = AccessModeRead
} }

View File

@ -94,7 +94,7 @@ func FixMergeBase(x *xorm.Engine) error {
} else { } else {
parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath}) parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) log.Warn("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue continue
} }
parents := strings.Split(strings.TrimSpace(parentsString), " ") parents := strings.Split(strings.TrimSpace(parentsString), " ")

View File

@ -81,7 +81,7 @@ func RefixMergeBase(x *xorm.Engine) error {
parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath}) parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil { if err != nil {
log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) log.Warn("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue continue
} }
parents := strings.Split(strings.TrimSpace(parentsString), " ") parents := strings.Split(strings.TrimSpace(parentsString), " ")

View File

@ -124,6 +124,9 @@ func TestGetWebhookByOwnerID(t *testing.T) {
func TestGetActiveWebhooksByRepoID(t *testing.T) { func TestGetActiveWebhooksByRepoID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
activateWebhook(t, 1)
hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{RepoID: 1, IsActive: optional.Some(true)}) hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{RepoID: 1, IsActive: optional.Some(true)})
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, hooks, 1) { if assert.Len(t, hooks, 1) {
@ -144,6 +147,9 @@ func TestGetWebhooksByRepoID(t *testing.T) {
func TestGetActiveWebhooksByOwnerID(t *testing.T) { func TestGetActiveWebhooksByOwnerID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
activateWebhook(t, 3)
hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{OwnerID: 3, IsActive: optional.Some(true)}) hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{OwnerID: 3, IsActive: optional.Some(true)})
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, hooks, 1) { if assert.Len(t, hooks, 1) {
@ -152,8 +158,18 @@ func TestGetActiveWebhooksByOwnerID(t *testing.T) {
} }
} }
func activateWebhook(t *testing.T, hookID int64) {
t.Helper()
updated, err := db.GetEngine(db.DefaultContext).ID(hookID).Cols("is_active").Update(Webhook{IsActive: true})
assert.Equal(t, int64(1), updated)
assert.NoError(t, err)
}
func TestGetWebhooksByOwnerID(t *testing.T) { func TestGetWebhooksByOwnerID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
activateWebhook(t, 3)
hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{OwnerID: 3}) hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{OwnerID: 3})
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, hooks, 1) { if assert.Len(t, hooks, 1) {

View File

@ -334,7 +334,10 @@ func (q *WorkerPoolQueue[T]) doRun() {
// since we are already in a "flush" operation, so the dispatching function shouldn't read the flush chan. // since we are already in a "flush" operation, so the dispatching function shouldn't read the flush chan.
q.doDispatchBatchToWorker(wg, skipFlushChan) q.doDispatchBatchToWorker(wg, skipFlushChan)
q.doFlush(wg, flush) q.doFlush(wg, flush)
case err := <-wg.popItemErr: case err, errOk := <-wg.popItemErr:
if !errOk {
return
}
if !q.isCtxRunCanceled() { if !q.isCtxRunCanceled() {
log.Error("Failed to pop item from queue %q (doRun): %v", q.GetName(), err) log.Error("Failed to pop item from queue %q (doRun): %v", q.GetName(), err)
} }

View File

@ -4,8 +4,11 @@
package testlogger package testlogger
import ( import (
"bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io"
"os" "os"
"runtime" "runtime"
"strings" "strings"
@ -27,53 +30,146 @@ var WriterCloser = &testLoggerWriterCloser{}
type testLoggerWriterCloser struct { type testLoggerWriterCloser struct {
sync.RWMutex sync.RWMutex
t []testing.TB t []testing.TB
errs []error // stack of error, parallel to the stack of testing.TB
err error // fallback if the stack is empty
} }
func (w *testLoggerWriterCloser) pushT(t testing.TB) { func (w *testLoggerWriterCloser) pushT(t testing.TB) {
w.Lock() w.Lock()
w.t = append(w.t, t) w.t = append(w.t, t)
w.errs = append(w.errs, nil)
w.Unlock() w.Unlock()
} }
func (w *testLoggerWriterCloser) Write(p []byte) (int, error) { func (w *testLoggerWriterCloser) Log(level log.Level, msg string) {
if len(msg) > 0 && msg[len(msg)-1] == '\n' {
msg = msg[:len(msg)-1]
}
w.printMsg(msg)
if level >= log.ERROR {
w.recordError(msg)
}
}
// list of error message which will not fail the test
// ideally this list should be empty, however ensuring that it does not grow
// is already a good first step.
var ignoredErrorMessageSuffixes = []string{
// only seen on mysql tests https://codeberg.org/forgejo/forgejo/pulls/2657#issuecomment-1693055
`table columns using inconsistent collation, they should use "utf8mb4_0900_ai_ci". Please go to admin panel Self Check page`,
// TestAPIDeleteReleaseByTagName
// action notification were a commit cannot be computed (because the commit got deleted)
`Notify() [E] an error occurred while executing the DeleteRelease actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/release-tag, rel_path: ]`,
`Notify() [E] an error occurred while executing the PushCommits actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/release-tag, rel_path: ]`,
// TestAPIRepoTags
`Notify() [E] an error occurred while executing the DeleteRelease actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/gitea/22, rel_path: ]`,
`Notify() [E] an error occurred while executing the PushCommits actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/gitea/22, rel_path: ]`,
// TestAPIDeleteTagByName
`Notify() [E] an error occurred while executing the DeleteRelease actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/delete-tag, rel_path: ]`,
`Notify() [E] an error occurred while executing the PushCommits actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/delete-tag, rel_path: ]`,
// TestAPIGenerateRepo
`Notify() [E] an error occurred while executing the CreateRepository actions method: gitRepo.GetCommit: object does not exist [id: , rel_path: ]`,
// TestAPIPullReview
`PullRequestReview() [E] Unsupported review webhook type`,
// TestAPIPullReviewRequest
`ToAPIPullRequest() [E] unable to resolve PR head ref`,
// TestAPILFSUpload
`Put() [E] Whilst putting LFS OID[ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb]: Failed to copy to tmpPath: ca/97/8112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb Error: content size does not match`,
`[E] Error putting LFS MetaObject [ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb] into content store. Error: content size does not match`,
`UploadHandler() [E] Upload does not match LFS MetaObject [ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb]. Error: content size does not match`,
`Put() [E] Whilst putting LFS OID[2581dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a]: Failed to copy to tmpPath: 25/81/dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a Error: content hash does not match OID`,
`[E] Error putting LFS MetaObject [2581dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a] into content store. Error: content hash does not match OID`,
`UploadHandler() [E] Upload does not match LFS MetaObject [2581dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a]. Error: content hash does not match OID`,
`UploadHandler() [E] Upload does not match LFS MetaObject [83de2e488b89a0aa1c97496b888120a28b0c1e15463a4adb8405578c540f36d4]. Error: content size does not match`,
// TestAPILFSVerify
`getAuthenticatedMeta() [E] Unable to get LFS OID[fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042] Error: LFS Meta object does not exist`,
// TestAPIUpdateOrgAvatar
`UpdateAvatar() [E] UploadAvatar: image.DecodeConfig: image: unknown format`,
// TestGetAttachment
`/data/attachments/a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18: no such file or directory`,
// TestBlockUser
`BlockedUsersUnblock() [E] IsOrganization: org3 is an organization not a user`,
`BlockedUsersBlock() [E] IsOrganization: org3 is an organization not a user`,
`Action() [E] Cannot perform this action on an organization "unblock"`,
`Action() [E] Cannot perform this action on an organization "block"`,
// TestBlockActions
`/gitea-repositories/user10/repo7.git Error: no such file or directory`,
// TestE2e/explore.test.e2e
`TrString() [E] Missing translation "more_items"`,
// TestRebuildCargo
`RebuildCargoIndex() [E] RebuildIndex failed: GetRepositoryByOwnerAndName: repository does not exist [id: 0, uid: 0, owner_name: user2, name: _cargo-index]`,
}
func (w *testLoggerWriterCloser) recordError(msg string) {
for _, s := range ignoredErrorMessageSuffixes {
if strings.HasSuffix(msg, s) {
return
}
}
w.Lock()
defer w.Unlock()
err := w.err
if len(w.errs) > 0 {
err = w.errs[len(w.errs)-1]
}
err = errors.Join(err, errors.New(msg))
if len(w.errs) > 0 {
w.errs[len(w.errs)-1] = err
} else {
w.err = err
}
}
func (w *testLoggerWriterCloser) printMsg(msg string) {
// There was a data race problem: the logger system could still try to output logs after the runner is finished. // There was a data race problem: the logger system could still try to output logs after the runner is finished.
// So we must ensure that the "t" in stack is still valid. // So we must ensure that the "t" in stack is still valid.
w.RLock() w.RLock()
defer w.RUnlock() defer w.RUnlock()
var t testing.TB
if len(w.t) > 0 { if len(w.t) > 0 {
t = w.t[len(w.t)-1] t := w.t[len(w.t)-1]
} t.Log(msg)
} else {
if len(p) > 0 && p[len(p)-1] == '\n' {
p = p[:len(p)-1]
}
if t == nil {
// if there is no running test, the log message should be outputted to console, to avoid losing important information. // if there is no running test, the log message should be outputted to console, to avoid losing important information.
// the "???" prefix is used to match the "===" and "+++" in PrintCurrentTest // the "???" prefix is used to match the "===" and "+++" in PrintCurrentTest
return fmt.Fprintf(os.Stdout, "??? [TestLogger] %s\n", p) fmt.Fprintln(os.Stdout, "??? [TestLogger]", msg)
} }
t.Log(string(p))
return len(p), nil
} }
func (w *testLoggerWriterCloser) popT() { func (w *testLoggerWriterCloser) popT() error {
w.Lock() w.Lock()
defer w.Unlock()
if len(w.t) > 0 { if len(w.t) > 0 {
w.t = w.t[:len(w.t)-1] w.t = w.t[:len(w.t)-1]
err := w.errs[len(w.errs)-1]
w.errs = w.errs[:len(w.errs)-1]
return err
} }
w.Unlock() return w.err
} }
func (w *testLoggerWriterCloser) Close() error { func (w *testLoggerWriterCloser) Reset() error {
return nil
}
func (w *testLoggerWriterCloser) Reset() {
w.Lock() w.Lock()
if len(w.t) > 0 { if len(w.t) > 0 {
for _, t := range w.t { for _, t := range w.t {
@ -84,8 +180,12 @@ func (w *testLoggerWriterCloser) Reset() {
t.Errorf("Unclosed logger writer in test: %s", t.Name()) t.Errorf("Unclosed logger writer in test: %s", t.Name())
} }
w.t = nil w.t = nil
w.errs = nil
} }
err := w.err
w.err = nil
w.Unlock() w.Unlock()
return err
} }
// PrintCurrentTest prints the current test to os.Stdout // PrintCurrentTest prints the current test to os.Stdout
@ -132,7 +232,10 @@ func PrintCurrentTest(t testing.TB, skip ...int) func() {
_, _ = fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", t.Name(), flushTook) _, _ = fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", t.Name(), flushTook)
} }
} }
WriterCloser.popT()
if err := WriterCloser.popT(); err != nil {
t.Errorf("testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
}
} }
} }
@ -146,19 +249,72 @@ func Printf(format string, args ...any) {
_, _ = fmt.Fprintf(os.Stdout, "\t"+format, args...) _, _ = fmt.Fprintf(os.Stdout, "\t"+format, args...)
} }
// TestLogEventWriter is a logger which will write to the testing log
type TestLogEventWriter struct {
*log.EventWriterBaseImpl
}
// NewTestLoggerWriter creates a TestLogEventWriter as a log.LoggerProvider // NewTestLoggerWriter creates a TestLogEventWriter as a log.LoggerProvider
func NewTestLoggerWriter(name string, mode log.WriterMode) log.EventWriter { func NewTestLoggerWriter(name string, mode log.WriterMode) log.EventWriter {
w := &TestLogEventWriter{} w := &TestLogEventWriter{}
w.EventWriterBaseImpl = log.NewEventWriterBase(name, "test-log-writer", mode) w.base = log.NewEventWriterBase(name, "test-log-writer", mode)
w.OutputWriteCloser = WriterCloser w.writer = WriterCloser
return w return w
} }
// TestLogEventWriter is a logger which will write to the testing log
type TestLogEventWriter struct {
base *log.EventWriterBaseImpl
writer *testLoggerWriterCloser
}
// Base implements log.EventWriter.
func (t *TestLogEventWriter) Base() *log.EventWriterBaseImpl {
return t.base
}
// GetLevel implements log.EventWriter.
func (t *TestLogEventWriter) GetLevel() log.Level {
return t.base.GetLevel()
}
// GetWriterName implements log.EventWriter.
func (t *TestLogEventWriter) GetWriterName() string {
return t.base.GetWriterName()
}
// GetWriterType implements log.EventWriter.
func (t *TestLogEventWriter) GetWriterType() string {
return t.base.GetWriterType()
}
// Run implements log.EventWriter.
func (t *TestLogEventWriter) Run(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case event, ok := <-t.base.Queue:
if !ok {
return
}
var errorMsg string
switch msg := event.Msg.(type) {
case string:
errorMsg = msg
case []byte:
errorMsg = string(msg)
case io.WriterTo:
var buf bytes.Buffer
if _, err := msg.WriteTo(&buf); err != nil {
panic(err)
}
errorMsg = buf.String()
default:
errorMsg = fmt.Sprint(msg)
}
t.writer.Log(event.Origin.Level, errorMsg)
}
}
}
func init() { func init() {
const relFilePath = "modules/testlogger/testlogger.go" const relFilePath = "modules/testlogger/testlogger.go"
_, filename, _, _ := runtime.Caller(0) _, filename, _, _ := runtime.Caller(0)

View File

@ -114,16 +114,22 @@ func (l *locale) TrString(trKey string, trArgs ...any) string {
format := trKey format := trKey
idx, ok := l.store.trKeyToIdxMap[trKey] idx, ok := l.store.trKeyToIdxMap[trKey]
found := false
if ok { if ok {
if msg, ok := l.idxToMsgMap[idx]; ok { if msg, ok := l.idxToMsgMap[idx]; ok {
format = msg // use the found translation format = msg // use the found translation
found = true
} else if def, ok := l.store.localeMap[l.store.defaultLang]; ok { } else if def, ok := l.store.localeMap[l.store.defaultLang]; ok {
// try to use default locale's translation // try to use default locale's translation
if msg, ok := def.idxToMsgMap[idx]; ok { if msg, ok := def.idxToMsgMap[idx]; ok {
format = msg format = msg
found = true
} }
} }
} }
if !found {
log.Error("Missing translation %q", trKey)
}
msg, err := Format(format, trArgs...) msg, err := Format(format, trArgs...)
if err != nil { if err != nil {

View File

@ -1655,6 +1655,7 @@ config.default_keep_email_private = أخفِ عناوين البريد الإل
config.default_allow_create_organization = اسمح بإنشاء المنظمات مبدئيا config.default_allow_create_organization = اسمح بإنشاء المنظمات مبدئيا
config.enable_timetracking = فعّل تتبع الوقت config.enable_timetracking = فعّل تتبع الوقت
config.default_enable_timetracking = فعّل تتبع الوقت مبدئيا config.default_enable_timetracking = فعّل تتبع الوقت مبدئيا
config.allow_dots_in_usernames = السماح للمستخدمين بوضع نقاط في أسمائهم. لا يؤثر على الحسابات الموجودة.
config.default_allow_only_contributors_to_track_time = اسمح للمشتركين في المستودع موحدهم بتتبع الوقت config.default_allow_only_contributors_to_track_time = اسمح للمشتركين في المستودع موحدهم بتتبع الوقت
[form] [form]

View File

@ -3224,6 +3224,7 @@ config.default_keep_email_private=Ve výchozím nastavení skrýt e-mailové adr
config.default_allow_create_organization=Povolit ve výchozím nastavení vytvářet organizace config.default_allow_create_organization=Povolit ve výchozím nastavení vytvářet organizace
config.enable_timetracking=Povolit sledování času config.enable_timetracking=Povolit sledování času
config.default_enable_timetracking=Povolit ve výchozím nastavení sledování času config.default_enable_timetracking=Povolit ve výchozím nastavení sledování času
config.allow_dots_in_usernames = Povolit uživatelům používat tečky ve svých uživatelských jménech. Neovlivní stávající účty.
config.default_allow_only_contributors_to_track_time=Povolit sledování času pouze přispěvatelům config.default_allow_only_contributors_to_track_time=Povolit sledování času pouze přispěvatelům
config.no_reply_address=Skrytá e-mailová doména config.no_reply_address=Skrytá e-mailová doména
config.default_visibility_organization=Výchozí viditelnost nových organizací config.default_visibility_organization=Výchozí viditelnost nových organizací

View File

@ -3204,6 +3204,7 @@ config.default_keep_email_private=E-Mail-Adressen standardmäßig verbergen
config.default_allow_create_organization=Erstellen von Organisationen standardmäßig erlauben config.default_allow_create_organization=Erstellen von Organisationen standardmäßig erlauben
config.enable_timetracking=Zeiterfassung aktivieren config.enable_timetracking=Zeiterfassung aktivieren
config.default_enable_timetracking=Zeiterfassung standardmäßig aktivieren config.default_enable_timetracking=Zeiterfassung standardmäßig aktivieren
config.allow_dots_in_usernames = Erlaubt Benutzern die Verwendung von Punkten in ihren Benutzernamen. Hat keine Auswirkungen auf bestehende Konten.
config.default_allow_only_contributors_to_track_time=Nur Mitarbeitern erlauben, die Zeiterfassung zu nutzen config.default_allow_only_contributors_to_track_time=Nur Mitarbeitern erlauben, die Zeiterfassung zu nutzen
config.no_reply_address=Versteckte E-Mail-Domain config.no_reply_address=Versteckte E-Mail-Domain
config.default_visibility_organization=Standard-Sichtbarkeit für neue Organisationen config.default_visibility_organization=Standard-Sichtbarkeit für neue Organisationen

View File

@ -3192,6 +3192,7 @@ config.default_keep_email_private=Να αποκρύπτονται οι διευ
config.default_allow_create_organization=Να επιτρέπεται η δημιουργία οργανισμών από προεπιλογή config.default_allow_create_organization=Να επιτρέπεται η δημιουργία οργανισμών από προεπιλογή
config.enable_timetracking=Ενεργοποίηση καταγραφής χρόνου config.enable_timetracking=Ενεργοποίηση καταγραφής χρόνου
config.default_enable_timetracking=Ενεργοποίηση καταγραφής χρόνου από προεπιλογή config.default_enable_timetracking=Ενεργοποίηση καταγραφής χρόνου από προεπιλογή
config.allow_dots_in_usernames = Επιτρέπει την χρήση τελείων σε ονόματα χρηστών. Δεν θα επηρεαστούν λογαριασμοί που ήδη υπάρχουν.
config.default_allow_only_contributors_to_track_time=Να επιτρέπεται η καταγραφή χρόνου μόνο από συνεισφέροντες config.default_allow_only_contributors_to_track_time=Να επιτρέπεται η καταγραφή χρόνου μόνο από συνεισφέροντες
config.no_reply_address=Κρυφό email domain config.no_reply_address=Κρυφό email domain
config.default_visibility_organization=Προεπιλεγμένη ορατότητα νέων οργανισμών config.default_visibility_organization=Προεπιλεγμένη ορατότητα νέων οργανισμών

View File

@ -3215,6 +3215,7 @@ config.default_keep_email_private = Hide email addresses by default
config.default_allow_create_organization = Allow creation of organizations by default config.default_allow_create_organization = Allow creation of organizations by default
config.enable_timetracking = Enable time tracking config.enable_timetracking = Enable time tracking
config.default_enable_timetracking = Enable time tracking by default config.default_enable_timetracking = Enable time tracking by default
config.allow_dots_in_usernames = Allow users to use dots in their usernames. Doesn't affect existing accounts.
config.default_allow_only_contributors_to_track_time = Let only contributors track time config.default_allow_only_contributors_to_track_time = Let only contributors track time
config.no_reply_address = Hidden email domain config.no_reply_address = Hidden email domain
config.default_visibility_organization = Default visibility of new organizations config.default_visibility_organization = Default visibility of new organizations

View File

@ -303,6 +303,7 @@ smtp_from_invalid = La «Sendu retleterojn kiel» adreso malvalidas
[admin] [admin]
config.app_data_path = Programdatuja doseiervojo config.app_data_path = Programdatuja doseiervojo
config.allow_dots_in_usernames = Permesi ĉeeston de punktoj en uzantonomoj. Ne efikas je jamaj kontoj.
[home] [home]
filter = Aliaj filtriloj filter = Aliaj filtriloj

View File

@ -3107,6 +3107,7 @@ config.default_keep_email_private=Ocultar direcciones de correo electrónico por
config.default_allow_create_organization=Permitir la creación de organizaciones por defecto config.default_allow_create_organization=Permitir la creación de organizaciones por defecto
config.enable_timetracking=Habilitar seguimiento de tiempo config.enable_timetracking=Habilitar seguimiento de tiempo
config.default_enable_timetracking=Habilitar seguimiento de tiempo por defecto config.default_enable_timetracking=Habilitar seguimiento de tiempo por defecto
config.allow_dots_in_usernames = Permite utilizar puntos en los nombres de usuario. No tiene efecto sobre cuentas existentes.
config.default_allow_only_contributors_to_track_time=Deje que solo los colaboradores hagan un seguimiento del tiempo config.default_allow_only_contributors_to_track_time=Deje que solo los colaboradores hagan un seguimiento del tiempo
config.no_reply_address=Dominio de correos electrónicos ocultos config.no_reply_address=Dominio de correos electrónicos ocultos
config.default_visibility_organization=Visibilidad por defecto para nuevas organizaciones config.default_visibility_organization=Visibilidad por defecto para nuevas organizaciones

View File

@ -3229,6 +3229,7 @@ config.default_keep_email_private=Masquer les adresses courriel par défaut
config.default_allow_create_organization=Autoriser la création d'organisations par défaut config.default_allow_create_organization=Autoriser la création d'organisations par défaut
config.enable_timetracking=Activer le suivi du temps config.enable_timetracking=Activer le suivi du temps
config.default_enable_timetracking=Activer le suivi de temps par défaut config.default_enable_timetracking=Activer le suivi de temps par défaut
config.allow_dots_in_usernames = Les points sont autorisés dans les noms d'utilisateurs. Sans effet sur les comptes existants.
config.default_allow_only_contributors_to_track_time=Restreindre le suivi de temps aux contributeurs config.default_allow_only_contributors_to_track_time=Restreindre le suivi de temps aux contributeurs
config.no_reply_address=Domaine pour les courriels cachés config.no_reply_address=Domaine pour les courriels cachés
config.default_visibility_organization=Visibilité par défaut des nouvelles organisations config.default_visibility_organization=Visibilité par défaut des nouvelles organisations

View File

@ -3145,6 +3145,7 @@ config.default_keep_email_private=Nascondi indirizzo email in modo predefinito
config.default_allow_create_organization=Consenti la creazione di organizzazioni in modo predefinito config.default_allow_create_organization=Consenti la creazione di organizzazioni in modo predefinito
config.enable_timetracking=Abilita il cronografo config.enable_timetracking=Abilita il cronografo
config.default_enable_timetracking=Attiva il cronografo di default config.default_enable_timetracking=Attiva il cronografo di default
config.allow_dots_in_usernames = Consenti l'uso del punto nel nome utente. Non impatta gli account già esistenti.
config.default_allow_only_contributors_to_track_time=Consenti soltanto ai contributori di utilizzare il cronografo config.default_allow_only_contributors_to_track_time=Consenti soltanto ai contributori di utilizzare il cronografo
config.no_reply_address=Dominio email nascosto config.no_reply_address=Dominio email nascosto
config.default_visibility_organization=Visibilità predefinita per le nuove organizzazioni config.default_visibility_organization=Visibilità predefinita per le nuove organizzazioni

View File

@ -3184,6 +3184,7 @@ config.default_keep_email_private=デフォルトでメールアドレスを隠
config.default_allow_create_organization=デフォルトで組織の作成を許可 config.default_allow_create_organization=デフォルトで組織の作成を許可
config.enable_timetracking=タイムトラッキング有効 config.enable_timetracking=タイムトラッキング有効
config.default_enable_timetracking=デフォルトでタイムトラッキング有効 config.default_enable_timetracking=デフォルトでタイムトラッキング有効
config.allow_dots_in_usernames = ユーザー名にドットを使用できるようにします。既存のアカウントには影響しません。
config.default_allow_only_contributors_to_track_time=コントリビューターだけタイムトラッキングする config.default_allow_only_contributors_to_track_time=コントリビューターだけタイムトラッキングする
config.no_reply_address=メールを隠すときのドメイン config.no_reply_address=メールを隠すときのドメイン
config.default_visibility_organization=新しい組織のデフォルトの表示設定 config.default_visibility_organization=新しい組織のデフォルトの表示設定

View File

@ -3096,6 +3096,7 @@ config.default_keep_email_private=Verberg standaard alle e-mailadressen
config.default_allow_create_organization=Standaard toestaan om organisaties aan te maken config.default_allow_create_organization=Standaard toestaan om organisaties aan te maken
config.enable_timetracking=Tijdregistratie inschakelen config.enable_timetracking=Tijdregistratie inschakelen
config.default_enable_timetracking=Tijdregistratie standaard inschakelen config.default_enable_timetracking=Tijdregistratie standaard inschakelen
config.allow_dots_in_usernames = Sta gebruikers toe om punten te gebruiken in hun gebruikersnaam. Heeft geen invloed op bestaande accounts.
config.default_allow_only_contributors_to_track_time=Sta alleen bijdragers toe tijdregistratie te gebruiken config.default_allow_only_contributors_to_track_time=Sta alleen bijdragers toe tijdregistratie te gebruiken
config.no_reply_address=Verborgen e-maildomein config.no_reply_address=Verborgen e-maildomein
config.default_visibility_organization=Standaard zichtbaarheid voor nieuwe organisaties config.default_visibility_organization=Standaard zichtbaarheid voor nieuwe organisaties

View File

@ -3096,6 +3096,7 @@ config.default_keep_email_private=Ocultar endereços de e-mail por padrão
config.default_allow_create_organization=Permitir a criação de organizações por padrão config.default_allow_create_organization=Permitir a criação de organizações por padrão
config.enable_timetracking=Habilitar Cronômetro config.enable_timetracking=Habilitar Cronômetro
config.default_enable_timetracking=Habilitar o Cronômetro por Padrão config.default_enable_timetracking=Habilitar o Cronômetro por Padrão
config.allow_dots_in_usernames = Permitir pontos em nomes de usuário. Esta opção não afeta contas já existentes.
config.default_allow_only_contributors_to_track_time=Permitir que apenas os colaboradores acompanhem o contador de tempo config.default_allow_only_contributors_to_track_time=Permitir que apenas os colaboradores acompanhem o contador de tempo
config.no_reply_address=Ocultar domínio de e-mail config.no_reply_address=Ocultar domínio de e-mail
config.default_visibility_organization=Visibilidade padrão para novas organizações config.default_visibility_organization=Visibilidade padrão para novas organizações

View File

@ -3189,6 +3189,7 @@ config.default_keep_email_private=Скрывать адреса эл. почты
config.default_allow_create_organization=Разрешить создание организаций по умолчанию config.default_allow_create_organization=Разрешить создание организаций по умолчанию
config.enable_timetracking=Отслеживание времени config.enable_timetracking=Отслеживание времени
config.default_enable_timetracking=Включить отслеживание времени по умолчанию config.default_enable_timetracking=Включить отслеживание времени по умолчанию
config.allow_dots_in_usernames = Разрешить точки в логинах пользователей. Это не повлияет на уже созданные учётные записи.
config.default_allow_only_contributors_to_track_time=Подсчитывать время могут только соавторы config.default_allow_only_contributors_to_track_time=Подсчитывать время могут только соавторы
config.no_reply_address=Домен скрытых адресов почты config.no_reply_address=Домен скрытых адресов почты
config.default_visibility_organization=Видимость новых организаций по умолчанию config.default_visibility_organization=Видимость новых организаций по умолчанию

View File

@ -267,6 +267,7 @@ config.git_config = Konfiguracija Git
config.git_max_diff_line_characters = Največ različnih znakov na vrstico config.git_max_diff_line_characters = Največ različnih znakov na vrstico
notices.view_detail_header = Podrobnosti obvestila notices.view_detail_header = Podrobnosti obvestila
config.log_config = Konfiguracija dnevnika config.log_config = Konfiguracija dnevnika
config.allow_dots_in_usernames = Uporabnikom dovolite uporabo pik v uporabniških imenih. Ne vpliva na obstoječe račune.
[repo] [repo]
migrate.github_token_desc = Tu lahko vstavite enega ali več žetonov, ločenih z vejico, da bo selitev hitrejša zaradi omejitve hitrosti GitHub API. OPOZORILO: Zloraba te funkcije lahko krši pravila ponudnika storitev in povzroči blokado računa. migrate.github_token_desc = Tu lahko vstavite enega ali več žetonov, ločenih z vejico, da bo selitev hitrejša zaradi omejitve hitrosti GitHub API. OPOZORILO: Zloraba te funkcije lahko krši pravila ponudnika storitev in povzroči blokado računa.

View File

@ -3224,6 +3224,7 @@ config.default_keep_email_private=默认情况下隐藏电子邮件地址
config.default_allow_create_organization=默认情况下允许创建组织 config.default_allow_create_organization=默认情况下允许创建组织
config.enable_timetracking=启用时间跟踪 config.enable_timetracking=启用时间跟踪
config.default_enable_timetracking=默认情况下启用时间跟踪 config.default_enable_timetracking=默认情况下启用时间跟踪
config.allow_dots_in_usernames = 允许用户在用户名中使用英文句号。不影响已有的帐户。
config.default_allow_only_contributors_to_track_time=仅允许成员跟踪时间 config.default_allow_only_contributors_to_track_time=仅允许成员跟踪时间
config.no_reply_address=隐藏电子邮件域 config.no_reply_address=隐藏电子邮件域
config.default_visibility_organization=新组织的默认可见性 config.default_visibility_organization=新组织的默认可见性

View File

@ -311,7 +311,7 @@ func (ar artifactRoutes) comfirmUploadArtifact(ctx *ArtifactContext) {
} }
artifactName := ctx.Req.URL.Query().Get("artifactName") artifactName := ctx.Req.URL.Query().Get("artifactName")
if artifactName == "" { if artifactName == "" {
log.Error("Error artifact name is empty") log.Warn("Error artifact name is empty")
ctx.Error(http.StatusBadRequest, "Error artifact name is empty") ctx.Error(http.StatusBadRequest, "Error artifact name is empty")
return return
} }

View File

@ -61,7 +61,7 @@ func validateArtifactHash(ctx *ArtifactContext, artifactName string) bool {
if paramHash == artifactHash { if paramHash == artifactHash {
return true return true
} }
log.Error("Invalid artifact hash: %s", paramHash) log.Warn("Invalid artifact hash: %s", paramHash)
ctx.Error(http.StatusBadRequest, "Invalid artifact hash") ctx.Error(http.StatusBadRequest, "Invalid artifact hash")
return false return false
} }

View File

@ -359,7 +359,7 @@ func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) {
checksum = req.Hash.Value checksum = req.Hash.Value
} }
if err := mergeChunksForArtifact(ctx, chunks, r.fs, artifact, checksum); err != nil { if err := mergeChunksForArtifact(ctx, chunks, r.fs, artifact, checksum); err != nil {
log.Error("Error merge chunks: %v", err) log.Warn("Error merge chunks: %v", err)
ctx.Error(http.StatusInternalServerError, "Error merge chunks") ctx.Error(http.StatusInternalServerError, "Error merge chunks")
return return
} }

View File

@ -49,7 +49,10 @@ var (
func apiError(ctx *context.Context, status int, obj any) { func apiError(ctx *context.Context, status int, obj any) {
helper.LogAndProcessError(ctx, status, obj, func(message string) { helper.LogAndProcessError(ctx, status, obj, func(message string) {
// The maven client does not present the error message to the user. Log it for users with access to server logs. // The maven client does not present the error message to the user. Log it for users with access to server logs.
if status == http.StatusBadRequest || status == http.StatusInternalServerError { switch status {
case http.StatusBadRequest:
log.Warn(message)
case http.StatusInternalServerError:
log.Error(message) log.Error(message)
} }

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/activitypub" "code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
gitea_context "code.gitea.io/gitea/services/context" gitea_context "code.gitea.io/gitea/services/context"
@ -89,7 +90,8 @@ func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, er
func ReqHTTPSignature() func(ctx *gitea_context.APIContext) { func ReqHTTPSignature() func(ctx *gitea_context.APIContext) {
return func(ctx *gitea_context.APIContext) { return func(ctx *gitea_context.APIContext) {
if authenticated, err := verifyHTTPSignatures(ctx); err != nil { if authenticated, err := verifyHTTPSignatures(ctx); err != nil {
ctx.ServerError("verifyHttpSignatures", err) log.Warn("verifyHttpSignatures failed: %v", err)
ctx.Error(http.StatusBadRequest, "reqSignature", "request signature verification failed")
} else if !authenticated { } else if !authenticated {
ctx.Error(http.StatusForbidden, "reqSignature", "request signature verification failed") ctx.Error(http.StatusForbidden, "reqSignature", "request signature verification failed")
} }

View File

@ -10,7 +10,6 @@ import (
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
@ -201,7 +200,6 @@ func ReadRepoNotifications(ctx *context.APIContext) {
if !ctx.FormBool("all") { if !ctx.FormBool("all") {
statuses := ctx.FormStrings("status-types") statuses := ctx.FormStrings("status-types")
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"}) opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"})
log.Error("%v", opts.Status)
} }
nl, err := db.Find[activities_model.Notification](ctx, opts) nl, err := db.Find[activities_model.Notification](ctx, opts)
if err != nil { if err != nil {

View File

@ -6,6 +6,7 @@ package actions
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"slices" "slices"
"strings" "strings"
@ -25,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
@ -190,6 +192,12 @@ func notify(ctx context.Context, input *notifyInput) error {
baseRef := git.BranchPrefix + input.PullRequest.BaseBranch baseRef := git.BranchPrefix + input.PullRequest.BaseBranch
baseCommit, err := gitRepo.GetCommit(baseRef) baseCommit, err := gitRepo.GetCommit(baseRef)
if err != nil { if err != nil {
if prp, ok := input.Payload.(*api.PullRequestPayload); ok && errors.Is(err, util.ErrNotExist) {
// the baseBranch was deleted and the PR closed: the action can be skipped
if prp.Action == api.HookIssueClosed {
return nil
}
}
return fmt.Errorf("gitRepo.GetCommit: %w", err) return fmt.Errorf("gitRepo.GetCommit: %w", err)
} }
baseWorkflows, _, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload) baseWorkflows, _, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload)

View File

@ -165,11 +165,15 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil { if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil {
if err == packages_model.ErrDuplicatePackageVersion { if err == packages_model.ErrDuplicatePackageVersion {
versionCreated = false versionCreated = false
} } else {
if err != packages_model.ErrDuplicatePackageVersion || !allowDuplicate {
log.Error("Error inserting package: %v", err) log.Error("Error inserting package: %v", err)
return nil, false, err return nil, false, err
} }
if !allowDuplicate {
// no need to log an error
return nil, false, err
}
} }
if versionCreated { if versionCreated {

View File

@ -65,7 +65,7 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus,
if status != nil { if status != nil {
return status.State return status.State
} }
return structs.CommitStatusSuccess return ""
} }
return returnedStatus return returnedStatus

View File

@ -123,7 +123,7 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep
for i, repo := range repos { for i, repo := range repos {
if results[i] == nil { if results[i] == nil {
results[i] = git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID]) results[i] = git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID])
if results[i].State != "" { if results[i] != nil {
if err := updateCommitStatusCache(ctx, repo.ID, repo.DefaultBranch, results[i].State); err != nil { if err := updateCommitStatusCache(ctx, repo.ID, repo.DefaultBranch, results[i].State); err != nil {
log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err) log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
} }

View File

@ -28,10 +28,19 @@ func TestWebhook_GetSlackHook(t *testing.T) {
}) })
} }
func activateWebhook(t *testing.T, hookID int64) {
t.Helper()
updated, err := db.GetEngine(db.DefaultContext).ID(hookID).Cols("is_active").Update(webhook_model.Webhook{IsActive: true})
assert.Equal(t, int64(1), updated)
assert.NoError(t, err)
}
func TestPrepareWebhooks(t *testing.T) { func TestPrepareWebhooks(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
activateWebhook(t, 1)
hookTasks := []*webhook_model.HookTask{ hookTasks := []*webhook_model.HookTask{
{HookID: 1, EventType: webhook_module.HookEventPush}, {HookID: 1, EventType: webhook_module.HookEventPush},
} }
@ -48,6 +57,8 @@ func TestPrepareWebhooksBranchFilterMatch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
activateWebhook(t, 4)
hookTasks := []*webhook_model.HookTask{ hookTasks := []*webhook_model.HookTask{
{HookID: 4, EventType: webhook_module.HookEventPush}, {HookID: 4, EventType: webhook_module.HookEventPush},
} }

View File

@ -59,8 +59,10 @@ func TestMain(m *testing.M) {
exitVal := m.Run() exitVal := m.Run()
testlogger.WriterCloser.Reset() if err := testlogger.WriterCloser.Reset(); err != nil {
fmt.Printf("testlogger.WriterCloser.Reset: %v\n", err)
os.Exit(1)
}
if err = util.RemoveAll(setting.Indexer.IssuePath); err != nil { if err = util.RemoveAll(setting.Indexer.IssuePath); err != nil {
fmt.Printf("util.RemoveAll: %v\n", err) fmt.Printf("util.RemoveAll: %v\n", err)
os.Exit(1) os.Exit(1)

View File

@ -108,6 +108,6 @@ func TestActivityPubPersonInbox(t *testing.T) {
// Unsigned request fails // Unsigned request fails
req := NewRequest(t, "POST", user2inboxurl) req := NewRequest(t, "POST", user2inboxurl)
MakeRequest(t, req, http.StatusInternalServerError) MakeRequest(t, req, http.StatusBadRequest)
}) })
} }

View File

@ -145,7 +145,10 @@ func TestMain(m *testing.M) {
// Instead, "No tests were found", last nonsense log is "According to the configuration, subsequent logs will not be printed to the console" // Instead, "No tests were found", last nonsense log is "According to the configuration, subsequent logs will not be printed to the console"
exitCode := m.Run() exitCode := m.Run()
testlogger.WriterCloser.Reset() if err := testlogger.WriterCloser.Reset(); err != nil {
fmt.Printf("testlogger.WriterCloser.Reset: %v\n", err)
os.Exit(1)
}
if err = util.RemoveAll(setting.Indexer.IssuePath); err != nil { if err = util.RemoveAll(setting.Indexer.IssuePath); err != nil {
fmt.Printf("util.RemoveAll: %v\n", err) fmt.Printf("util.RemoveAll: %v\n", err)

View File

@ -80,10 +80,22 @@ func testPullCleanUp(t *testing.T, session *TestSession, user, repo, pullnum str
return resp return resp
} }
func retrieveHookTasks(t *testing.T, hookID int64, activateWebhook bool) []*webhook.HookTask {
t.Helper()
if activateWebhook {
updated, err := db.GetEngine(db.DefaultContext).ID(hookID).Cols("is_active").Update(webhook.Webhook{IsActive: true})
assert.Equal(t, int64(1), updated)
assert.NoError(t, err)
}
hookTasks, err := webhook.HookTasks(db.DefaultContext, hookID, 1)
assert.NoError(t, err)
return hookTasks
}
func TestPullMerge(t *testing.T) { func TestPullMerge(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
hookTasks, err := webhook.HookTasks(db.DefaultContext, 1, 1) // Retrieve previous hook number hookTasks := retrieveHookTasks(t, 1, true)
assert.NoError(t, err)
hookTasksLenBefore := len(hookTasks) hookTasksLenBefore := len(hookTasks)
session := loginUser(t, "user1") session := loginUser(t, "user1")
@ -96,16 +108,14 @@ func TestPullMerge(t *testing.T) {
assert.EqualValues(t, "pulls", elem[3]) assert.EqualValues(t, "pulls", elem[3])
testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false)
hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) hookTasks = retrieveHookTasks(t, 1, false)
assert.NoError(t, err)
assert.Len(t, hookTasks, hookTasksLenBefore+1) assert.Len(t, hookTasks, hookTasksLenBefore+1)
}) })
} }
func TestPullRebase(t *testing.T) { func TestPullRebase(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
hookTasks, err := webhook.HookTasks(db.DefaultContext, 1, 1) // Retrieve previous hook number hookTasks := retrieveHookTasks(t, 1, true)
assert.NoError(t, err)
hookTasksLenBefore := len(hookTasks) hookTasksLenBefore := len(hookTasks)
session := loginUser(t, "user1") session := loginUser(t, "user1")
@ -118,16 +128,14 @@ func TestPullRebase(t *testing.T) {
assert.EqualValues(t, "pulls", elem[3]) assert.EqualValues(t, "pulls", elem[3])
testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebase, false) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebase, false)
hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) hookTasks = retrieveHookTasks(t, 1, false)
assert.NoError(t, err)
assert.Len(t, hookTasks, hookTasksLenBefore+1) assert.Len(t, hookTasks, hookTasksLenBefore+1)
}) })
} }
func TestPullRebaseMerge(t *testing.T) { func TestPullRebaseMerge(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
hookTasks, err := webhook.HookTasks(db.DefaultContext, 1, 1) // Retrieve previous hook number hookTasks := retrieveHookTasks(t, 1, true)
assert.NoError(t, err)
hookTasksLenBefore := len(hookTasks) hookTasksLenBefore := len(hookTasks)
session := loginUser(t, "user1") session := loginUser(t, "user1")
@ -140,16 +148,14 @@ func TestPullRebaseMerge(t *testing.T) {
assert.EqualValues(t, "pulls", elem[3]) assert.EqualValues(t, "pulls", elem[3])
testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebaseMerge, false) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebaseMerge, false)
hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) hookTasks = retrieveHookTasks(t, 1, false)
assert.NoError(t, err)
assert.Len(t, hookTasks, hookTasksLenBefore+1) assert.Len(t, hookTasks, hookTasksLenBefore+1)
}) })
} }
func TestPullSquash(t *testing.T) { func TestPullSquash(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
hookTasks, err := webhook.HookTasks(db.DefaultContext, 1, 1) // Retrieve previous hook number hookTasks := retrieveHookTasks(t, 1, true)
assert.NoError(t, err)
hookTasksLenBefore := len(hookTasks) hookTasksLenBefore := len(hookTasks)
session := loginUser(t, "user1") session := loginUser(t, "user1")
@ -163,8 +169,7 @@ func TestPullSquash(t *testing.T) {
assert.EqualValues(t, "pulls", elem[3]) assert.EqualValues(t, "pulls", elem[3])
testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleSquash, false) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleSquash, false)
hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) hookTasks = retrieveHookTasks(t, 1, false)
assert.NoError(t, err)
assert.Len(t, hookTasks, hookTasksLenBefore+1) assert.Len(t, hookTasks, hookTasksLenBefore+1)
}) })
} }

View File

@ -251,9 +251,8 @@ const sfc = {
this.repos = json.data.map((webSearchRepo) => { this.repos = json.data.map((webSearchRepo) => {
return { return {
...webSearchRepo.repository, ...webSearchRepo.repository,
latest_commit_status_state: webSearchRepo.latest_commit_status.State, latest_commit_status: webSearchRepo.latest_commit_status,
locale_latest_commit_status_state: webSearchRepo.locale_latest_commit_status, locale_latest_commit_status_state: webSearchRepo.locale_latest_commit_status
latest_commit_status_state_link: webSearchRepo.latest_commit_status.TargetURL
}; };
}); });
const count = response.headers.get('X-Total-Count'); const count = response.headers.get('X-Total-Count');
@ -416,9 +415,9 @@ export default sfc; // activate the IDE's Vue plugin
<svg-icon name="octicon-archive" :size="16"/> <svg-icon name="octicon-archive" :size="16"/>
</div> </div>
</a> </a>
<a class="gt-df gt-ac" v-if="repo.latest_commit_status_state" :href="repo.latest_commit_status_state_link" :data-tooltip-content="repo.locale_latest_commit_status_state"> <a class="gt-df gt-ac" v-if="repo.latest_commit_status" :href="repo.latest_commit_status.TargetLink" :data-tooltip-content="repo.locale_latest_commit_status.State">
<!-- the commit status icon logic is taken from templates/repo/commit_status.tmpl --> <!-- the commit status icon logic is taken from templates/repo/commit_status.tmpl -->
<svg-icon :name="statusIcon(repo.latest_commit_status_state)" :class-name="'gt-ml-3 commit-status icon text ' + statusColor(repo.latest_commit_status_state)" :size="16"/> <svg-icon :name="statusIcon(repo.latest_commit_status.State)" :class-name="'gt-ml-3 commit-status icon text ' + statusColor(repo.latest_commit_status.State)" :size="16"/>
</a> </a>
</li> </li>
</ul> </ul>