Compare commits

..

21 Commits

Author SHA1 Message Date
Matti R
f5845e6497 Changelog for 1.15.4 2021-10-08 14:01:42 -04:00
Norwin
c927ebd119 API: don't allow merged PRs to be reopened (#17271) 2021-10-08 13:54:26 -04:00
Norwin
245596e130 don't try to interpret treepath as hash (#17272)
...when path contains no hash-path-separator ('/')

This is a workaround to #17179.

Entering this case when `path` does not contain a '/' does not really
make sense, as that means the tree path is empty, but this case is only
entered for routes that expect a non-empty tree path.

Treepaths like <40-char-dirname>/<filename> will still fail,
but hopefully don't occur that often. A more complete fix that avoids
this case too is outlined in #17185, but too big of a change to backport
2021-10-08 13:53:54 -04:00
Jimmy Praet
1c3ae6d05e Fix incorrect repository count on organization tab of dashboard (#17266)
Fixes #17249
2021-10-08 17:33:16 +08:00
Jimmy Praet
a1e57ebe6b Fix unwanted team review request deletion (#17257) (#17264)
Add missing issue_id = ? to where clause
Fixes #17251
2021-10-07 23:58:13 +02:00
6543
73ae93b007 CI: migrate from 'plugins/s3:1' to 'woodpeckerci/plugin-s3:latest' (#17234) (#17260)
- this fixes the CI release upload issues, as the docker image for this is freshly built (unlike the mostly unmaintained "official" drone plugins), thus containing current CA certs needed for letsencrypt since 2021-09-31.
- woodpecker is a drone-ci fork maintained partially by @6543. it's API compatible with current drone plugins afaik
2021-10-07 22:02:07 +02:00
6543
dc030f64a7 Remove dead badge on README.md (#17261) 2021-10-07 15:40:11 -04:00
Jimmy Praet
6e0a08d753 Fix broken Activities link in team dashboard (#17255) (#17258)
Remove '/' suffix from organization dashboard link

Fixes #17250
2021-10-07 20:58:59 +02:00
pricly-yellow
7b1153e943 API pull's head/base have correct permission(#17214) (#17245)
* for all pull requests API return permissions of caller
* for all webhook return empty permissions

Signed-off-by: Danila Kryukov <pricly_yellow@dismail.de>

* Fix incorrect error handler

Co-authored-by: delvh <dev.lh@web.de>

* Fix wrong assumption in tests

* Change paramenter name to doer to indicate source

Co-authored-by: 6543 <6543@obermui.de>

Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: 6543 <6543@obermui.de>
2021-10-07 12:39:23 +03:00
pricly-yellow
6995be66e7 Fix stange behavior of DownloadPullDiffOrPatch in incorect index (#17223) (#17227)
Fix GetPullRequestByIndex by validate index > 1

Signed-off-by: Danila Kryukov <pricly_yellow@dismail.de>
Co-authored-by: a1012112796 <1012112796@qq.com>
2021-10-05 20:16:22 +02:00
6543
28971c7c15 Check user instead of organization when creating a repo from a template via API (#16346) (#17195)
* Check user instead of organization

* Enforce that only admins can copy a repo to another user

Co-authored-by: Ion Jaureguialzo Sarasola <ion@jaureguialzo.com>
2021-10-01 10:16:28 +02:00
Lunny Xiao
eb5e6f09eb upgrade xorm to v1.2.5 (#17177) (#17188) 2021-09-30 07:03:42 +01:00
Alexey 〒erentyev
bf6264c1db fix sprintf verbs in locales (#17187)
Signed-off-by: Alexey Terentyev <axifnx@gmail.com>

Co-authored-by: 6543 <6543@obermui.de>
2021-09-30 12:03:21 +08:00
zeripath
5b6b7e79cf Fix missing repo link in issue/pull assigned emails (#17183) (#17184)
Backport #17183

There was a mistake in the template file: `templates/mail/issue/assigned.tmpl`
where the repourl was generated from a non-existent release instead of the issue.

This PR changes this to use the issue but also ensure that the issue repo is loaded.

It also slightly improves the English and the Russian locale string.

Fix #17160

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: 6543 <6543@obermui.de>
2021-09-30 01:21:12 +02:00
Lunny Xiao
766272b154 Fix bug of get context user (#17169) (#17172)
Co-authored-by: 6543 <6543@obermui.de>
2021-09-28 15:42:43 +02:00
zeripath
4707d4b8a9 Nicely handle missing user in collaborations (#17049) (#17166)
Backport #17049

It is possible to have a collaboration in a repository which refers to a no-longer
existing user. This causes the repository transfer to fail with an unusual error.

This PR makes `repo.getCollaborators()` nicely handle the missing user by ghosting
the collaboration but also adds consistency check. It also adds an
Access consistency check.

Fix #17044

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2021-09-28 07:41:12 +01:00
zeripath
4b8b214108 Create doctor command to fix repo_units broken by dumps from 1.14.3-1.14.6 (#17136) (#17137)
Backport #17136

There was a serious issue with the `gitea dump` command in 1.14.3-1.14.6 which led to corruption of the `config` field of the `repo_unit` table. 

This PR adds a doctor command to attempt to fix the broken repo_units. Users affected by #16961 should run:

```
gitea doctor --fix --run fix-broken-repo-units
```

Fix #16961

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-09-27 18:30:11 +01:00
zeripath
ebae7e1512 Add Horizontal scrollbar to inner menu on Chrome (#17086) (#17164) 2021-09-27 12:44:22 -04:00
6543
122917f4d5 Fix wrong i18n keys (#17150) (#17153)
Co-authored-by: 6543 <6543@obermui.de>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-09-26 08:25:12 +08:00
Alexey 〒erentyev
9cf5739c0f correct transaction ending (#17151)
Signed-off-by: Alexey Terentyev <axifnx@gmail.com>
2021-09-25 16:45:39 +01:00
zeripath
4b6556565f Prevent panic in Org mode HighlightCodeBlock (#17140) (#17141)
Backport #17140

When rendering source in org mode there is a mistake in the highlight code that
causes a panic.

This PR fixes this.

Fix #17139

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-09-24 14:29:47 +01:00
51 changed files with 987 additions and 366 deletions
+3 -3
View File
@@ -527,7 +527,7 @@ steps:
- name: release-branch
pull: always
image: plugins/s3:1
image: woodpeckerci/plugin-s3:latest
settings:
acl: public-read
bucket: gitea-artifacts
@@ -548,7 +548,7 @@ steps:
- push
- name: release-main
image: plugins/s3:1
image: woodpeckerci/plugin-s3:latest
settings:
acl: public-read
bucket: gitea-artifacts
@@ -623,7 +623,7 @@ steps:
- name: release-tag
pull: always
image: plugins/s3:1
image: woodpeckerci/plugin-s3:latest
settings:
acl: public-read
bucket: gitea-artifacts
+24
View File
@@ -4,6 +4,30 @@ This changelog goes through all the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.15.4](https://github.com/go-gitea/gitea/releases/tag/v1.15.4) - 2021-10-08
* BUGFIXES
* Raw file API: don't try to interpret 40char filenames as commit SHA (#17185) (#17272)
* Don't allow merged PRs to be reopened (#17192) (#17271)
* Fix incorrect repository count on organization tab of dashboard (#17256) (#17266)
* Fix unwanted team review request deletion (#17257) (#17264)
* Fix broken Activities link in team dashboard (#17255) (#17258)
* API pull's head/base have correct permission(#17214) (#17245)
* Fix stange behavior of DownloadPullDiffOrPatch in incorect index (#17223) (#17227)
* Upgrade xorm to v1.2.5 (#17177) (#17188)
* Fix missing repo link in issue/pull assigned emails (#17183) (#17184)
* Fix bug of get context user (#17169) (#17172)
* Nicely handle missing user in collaborations (#17049) (#17166)
* Add Horizontal scrollbar to inner menu on Chrome (#17086) (#17164)
* Fix wrong i18n keys (#17150) (#17153)
* Fix Archive Creation: correct transaction ending (#17151)
* Prevent panic in Org mode HighlightCodeBlock (#17140) (#17141)
* Create doctor command to fix repo_units broken by dumps from 1.14.3-1.14.6 (#17136) (#17137)
* ENHANCEMENT
* Check user instead of organization when creating a repo from a template via API (#16346) (#17195)
* TRANSLATION
* v1.15 fix Sprintf format 'verbs' in locale files (#17187)
## [1.15.3](https://github.com/go-gitea/gitea/releases/tag/v1.15.3) - 2021-09-19
* ENHANCEMENTS
-3
View File
@@ -12,9 +12,6 @@
<a href="https://discord.gg/Gitea" title="Join the Discord chat at https://discord.gg/Gitea">
<img src="https://img.shields.io/discord/322538954119184384.svg">
</a>
<a href="https://microbadger.com/images/gitea/gitea" title="Get your own image badge on microbadger.com">
<img src="https://images.microbadger.com/badges/image/gitea/gitea.svg">
</a>
<a href="https://codecov.io/gh/go-gitea/gitea" title="Codecov">
<img src="https://codecov.io/gh/go-gitea/gitea/branch/main/graph/badge.svg">
</a>
+1 -1
View File
@@ -139,7 +139,7 @@ require (
mvdan.cc/xurls/v2 v2.2.0
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
xorm.io/builder v0.3.9
xorm.io/xorm v1.2.4
xorm.io/xorm v1.2.5
)
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
+2 -2
View File
@@ -1765,5 +1765,5 @@ xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/builder v0.3.9 h1:Sd65/LdWyO7LR8+Cbd+e7mm3sK/7U9k0jS3999IDHMc=
xorm.io/builder v0.3.9/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.0.6/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
xorm.io/xorm v1.2.4 h1:2MQV4wJt4kY6CYJsLxjCLzBen7+aqVxqDTIwfnR59g4=
xorm.io/xorm v1.2.4/go.mod h1:fTG8tSjk6O1BYxwuohZUK+S1glnRycsCF05L1qQyEU0=
xorm.io/xorm v1.2.5 h1:tqN7OhN8P9xi52qBb76I8m5maAJMz/SSbgK2RGPCPbo=
xorm.io/xorm v1.2.5/go.mod h1:fTG8tSjk6O1BYxwuohZUK+S1glnRycsCF05L1qQyEU0=
+3
View File
@@ -225,6 +225,9 @@ func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int6
return fmt.Errorf("getCollaborations: %v", err)
}
for _, c := range collaborators {
if c.User.IsGhost() {
continue
}
updateUserAccess(accessMap, c.User, c.Collaboration.Mode)
}
return nil
+8 -8
View File
@@ -71,9 +71,9 @@ var (
_ convert.Conversion = &SSPIConfig{}
)
// jsonUnmarshalHandleDoubleEncode - due to a bug in xorm (see https://gitea.com/xorm/xorm/pulls/1957) - it's
// JSONUnmarshalHandleDoubleEncode - due to a bug in xorm (see https://gitea.com/xorm/xorm/pulls/1957) - it's
// possible that a Blob may be double encoded or gain an unwanted prefix of 0xff 0xfe.
func jsonUnmarshalHandleDoubleEncode(bs []byte, v interface{}) error {
func JSONUnmarshalHandleDoubleEncode(bs []byte, v interface{}) error {
json := jsoniter.ConfigCompatibleWithStandardLibrary
err := json.Unmarshal(bs, v)
if err != nil {
@@ -89,7 +89,7 @@ func jsonUnmarshalHandleDoubleEncode(bs []byte, v interface{}) error {
rs = append(rs, temp...)
}
if ok {
if rs[0] == 0xff && rs[1] == 0xfe {
if len(rs) > 1 && rs[0] == 0xff && rs[1] == 0xfe {
rs = rs[2:]
}
err = json.Unmarshal(rs, v)
@@ -108,7 +108,7 @@ type LDAPConfig struct {
// FromDB fills up a LDAPConfig from serialized format.
func (cfg *LDAPConfig) FromDB(bs []byte) error {
err := jsonUnmarshalHandleDoubleEncode(bs, &cfg)
err := JSONUnmarshalHandleDoubleEncode(bs, &cfg)
if err != nil {
return err
}
@@ -149,7 +149,7 @@ type SMTPConfig struct {
// FromDB fills up an SMTPConfig from serialized format.
func (cfg *SMTPConfig) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, cfg)
return JSONUnmarshalHandleDoubleEncode(bs, cfg)
}
// ToDB exports an SMTPConfig to a serialized format.
@@ -166,7 +166,7 @@ type PAMConfig struct {
// FromDB fills up a PAMConfig from serialized format.
func (cfg *PAMConfig) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, cfg)
return JSONUnmarshalHandleDoubleEncode(bs, cfg)
}
// ToDB exports a PAMConfig to a serialized format.
@@ -187,7 +187,7 @@ type OAuth2Config struct {
// FromDB fills up an OAuth2Config from serialized format.
func (cfg *OAuth2Config) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, cfg)
return JSONUnmarshalHandleDoubleEncode(bs, cfg)
}
// ToDB exports an SMTPConfig to a serialized format.
@@ -207,7 +207,7 @@ type SSPIConfig struct {
// FromDB fills up an SSPIConfig from serialized format.
func (cfg *SSPIConfig) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, cfg)
return JSONUnmarshalHandleDoubleEncode(bs, cfg)
}
// ToDB exports an SSPIConfig to a serialized format.
+1 -1
View File
@@ -455,7 +455,7 @@ func GetUserOrgsList(user *User) ([]*MinimalOrg, error) {
groupByStr := groupByCols.String()
groupByStr = groupByStr[0 : len(groupByStr)-1]
sess.Select(groupByStr+", count(repo_id) as org_count").
sess.Select(groupByStr+", count(distinct repo_id) as org_count").
Table("user").
Join("INNER", "team", "`team`.org_id = `user`.id").
Join("INNER", "team_user", "`team`.id = `team_user`.team_id").
+3
View File
@@ -502,6 +502,9 @@ func GetLatestPullRequestByHeadInfo(repoID int64, branch string) (*PullRequest,
// GetPullRequestByIndex returns a pull request by the given index
func GetPullRequestByIndex(repoID, index int64) (*PullRequest, error) {
if index < 1 {
return nil, ErrPullRequestNotExist{}
}
pr := &PullRequest{
BaseRepoID: repoID,
Index: index,
+4
View File
@@ -133,6 +133,10 @@ func TestGetPullRequestByIndex(t *testing.T) {
_, err = GetPullRequestByIndex(9223372036854775807, 9223372036854775807)
assert.Error(t, err)
assert.True(t, IsErrPullRequestNotExist(err))
_, err = GetPullRequestByIndex(1, 0)
assert.Error(t, err)
assert.True(t, IsErrPullRequestNotExist(err))
}
func TestGetPullRequestByID(t *testing.T) {
+11 -5
View File
@@ -8,6 +8,7 @@ package models
import (
"fmt"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
@@ -83,16 +84,21 @@ func (repo *Repository) getCollaborators(e Engine, listOptions ListOptions) ([]*
return nil, fmt.Errorf("getCollaborations: %v", err)
}
collaborators := make([]*Collaborator, len(collaborations))
for i, c := range collaborations {
collaborators := make([]*Collaborator, 0, len(collaborations))
for _, c := range collaborations {
user, err := getUserByID(e, c.UserID)
if err != nil {
return nil, err
if IsErrUserNotExist(err) {
log.Warn("Inconsistent DB: User: %d is listed as collaborator of %-v but does not exist", c.UserID, repo)
user = NewGhostUser()
} else {
return nil, err
}
}
collaborators[i] = &Collaborator{
collaborators = append(collaborators, &Collaborator{
User: user,
Collaboration: c,
}
})
}
return collaborators, nil
}
+9
View File
@@ -269,6 +269,14 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e
// Dummy object.
collaboration := &Collaboration{RepoID: repo.ID}
for _, c := range collaborators {
if c.IsGhost() {
collaboration.ID = c.Collaboration.ID
if _, err := sess.Delete(collaboration); err != nil {
return fmt.Errorf("remove collaborator '%d': %v", c.ID, err)
}
collaboration.ID = 0
}
if c.ID != newOwner.ID {
isMember, err := isOrganizationMember(sess, newOwner.ID, c.ID)
if err != nil {
@@ -281,6 +289,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e
if _, err := sess.Delete(collaboration); err != nil {
return fmt.Errorf("remove collaborator '%d': %v", c.ID, err)
}
collaboration.UserID = 0
}
// Remove old team-repository relations.
+11 -5
View File
@@ -28,7 +28,7 @@ type UnitConfig struct{}
// FromDB fills up a UnitConfig from serialized format.
func (cfg *UnitConfig) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
return JSONUnmarshalHandleDoubleEncode(bs, &cfg)
}
// ToDB exports a UnitConfig to a serialized format.
@@ -44,7 +44,7 @@ type ExternalWikiConfig struct {
// FromDB fills up a ExternalWikiConfig from serialized format.
func (cfg *ExternalWikiConfig) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
return JSONUnmarshalHandleDoubleEncode(bs, &cfg)
}
// ToDB exports a ExternalWikiConfig to a serialized format.
@@ -62,7 +62,7 @@ type ExternalTrackerConfig struct {
// FromDB fills up a ExternalTrackerConfig from serialized format.
func (cfg *ExternalTrackerConfig) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
return JSONUnmarshalHandleDoubleEncode(bs, &cfg)
}
// ToDB exports a ExternalTrackerConfig to a serialized format.
@@ -80,7 +80,7 @@ type IssuesConfig struct {
// FromDB fills up a IssuesConfig from serialized format.
func (cfg *IssuesConfig) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
return JSONUnmarshalHandleDoubleEncode(bs, &cfg)
}
// ToDB exports a IssuesConfig to a serialized format.
@@ -104,7 +104,7 @@ type PullRequestsConfig struct {
// FromDB fills up a PullRequestsConfig from serialized format.
func (cfg *PullRequestsConfig) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
return JSONUnmarshalHandleDoubleEncode(bs, &cfg)
}
// ToDB exports a PullRequestsConfig to a serialized format.
@@ -219,3 +219,9 @@ func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) {
return units, nil
}
// UpdateRepoUnit updates the provided repo unit
func UpdateRepoUnit(unit *RepoUnit) error {
_, err := x.ID(unit.ID).Update(unit)
return err
}
+1 -1
View File
@@ -434,7 +434,7 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, comm
// try to remove team review request if need
if issue.Repo.Owner.IsOrganization() && (reviewType == ReviewTypeApprove || reviewType == ReviewTypeReject) {
teamReviewRequests := make([]*Review, 0, 10)
if err := sess.SQL("SELECT * FROM review WHERE reviewer_team_id > 0 AND type = ?", ReviewTypeRequest).Find(&teamReviewRequests); err != nil {
if err := sess.SQL("SELECT * FROM review WHERE issue_id = ? AND reviewer_team_id > 0 AND type = ?", issue.ID, ReviewTypeRequest).Find(&teamReviewRequests); err != nil {
return nil, nil, err
}
+1 -1
View File
@@ -296,7 +296,7 @@ func (u *User) CanImportLocal() bool {
// DashboardLink returns the user dashboard page link.
func (u *User) DashboardLink() string {
if u.IsOrganization() {
return u.OrganisationLink() + "/dashboard/"
return u.OrganisationLink() + "/dashboard"
}
return setting.AppSubURL + "/"
}
+11
View File
@@ -587,6 +587,17 @@ func GetContext(req *http.Request) *Context {
return req.Context().Value(contextKey).(*Context)
}
// GetContextUser returns context user
func GetContextUser(req *http.Request) *models.User {
if apiContext, ok := req.Context().Value(apiContextKey).(*APIContext); ok {
return apiContext.User
}
if ctx, ok := req.Context().Value(contextKey).(*Context); ok {
return ctx.User
}
return nil
}
// SignedUserName returns signed user's name via context
func SignedUserName(req *http.Request) string {
if middleware.IsInternalPath(req) {
+1 -1
View File
@@ -695,7 +695,7 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
}
// For legacy and API support only full commit sha
parts := strings.Split(path, "/")
if len(parts) > 0 && len(parts[0]) == 40 {
if len(parts) > 1 && len(parts[0]) == 40 {
ctx.Repo.TreePath = strings.Join(parts[1:], "/")
return parts[0]
}
+15 -3
View File
@@ -17,7 +17,7 @@ import (
// ToAPIPullRequest assumes following fields have been assigned with valid values:
// Required - Issue
// Optional - Merger
func ToAPIPullRequest(pr *models.PullRequest) *api.PullRequest {
func ToAPIPullRequest(pr *models.PullRequest, doer *models.User) *api.PullRequest {
var (
baseBranch *git.Branch
headBranch *git.Branch
@@ -41,6 +41,12 @@ func ToAPIPullRequest(pr *models.PullRequest) *api.PullRequest {
return nil
}
perm, err := models.GetUserRepoPermission(pr.BaseRepo, doer)
if err != nil {
log.Error("GetUserRepoPermission[%d]: %v", pr.BaseRepoID, err)
perm.AccessMode = models.AccessModeNone
}
apiPullRequest := &api.PullRequest{
ID: pr.ID,
URL: pr.Issue.HTMLURL(),
@@ -68,7 +74,7 @@ func ToAPIPullRequest(pr *models.PullRequest) *api.PullRequest {
Name: pr.BaseBranch,
Ref: pr.BaseBranch,
RepoID: pr.BaseRepoID,
Repository: ToRepo(pr.BaseRepo, models.AccessModeNone),
Repository: ToRepo(pr.BaseRepo, perm.AccessMode),
},
Head: &api.PRBranchInfo{
Name: pr.HeadBranch,
@@ -96,8 +102,14 @@ func ToAPIPullRequest(pr *models.PullRequest) *api.PullRequest {
}
if pr.HeadRepo != nil {
perm, err := models.GetUserRepoPermission(pr.HeadRepo, doer)
if err != nil {
log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err)
perm.AccessMode = models.AccessModeNone
}
apiPullRequest.Head.RepoID = pr.HeadRepo.ID
apiPullRequest.Head.Repository = ToRepo(pr.HeadRepo, models.AccessModeNone)
apiPullRequest.Head.Repository = ToRepo(pr.HeadRepo, perm.AccessMode)
headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
if err != nil {
+3 -3
View File
@@ -20,14 +20,14 @@ func TestPullRequest_APIFormat(t *testing.T) {
pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 1}).(*models.PullRequest)
assert.NoError(t, pr.LoadAttributes())
assert.NoError(t, pr.LoadIssue())
apiPullRequest := ToAPIPullRequest(pr)
apiPullRequest := ToAPIPullRequest(pr, nil)
assert.NotNil(t, apiPullRequest)
assert.EqualValues(t, &structs.PRBranchInfo{
Name: "branch1",
Ref: "refs/pull/2/head",
Sha: "4a357436d925b5c974181ff12a994538ddc5a269",
RepoID: 1,
Repository: ToRepo(headRepo, models.AccessModeNone),
Repository: ToRepo(headRepo, models.AccessModeRead),
}, apiPullRequest.Head)
//withOut HeadRepo
@@ -37,7 +37,7 @@ func TestPullRequest_APIFormat(t *testing.T) {
// simulate fork deletion
pr.HeadRepo = nil
pr.HeadRepoID = 100000
apiPullRequest = ToAPIPullRequest(pr)
apiPullRequest = ToAPIPullRequest(pr, nil)
assert.NotNil(t, apiPullRequest)
assert.Nil(t, apiPullRequest.Head.Repository)
assert.EqualValues(t, -1, apiPullRequest.Head.RepoID)
+148 -233
View File
@@ -13,6 +13,64 @@ import (
"code.gitea.io/gitea/modules/setting"
)
type consistencyCheck struct {
Name string
Counter func() (int64, error)
Fixer func() (int64, error)
FixedMessage string
}
func (c *consistencyCheck) Run(logger log.Logger, autofix bool) error {
count, err := c.Counter()
if err != nil {
logger.Critical("Error: %v whilst counting %s", err, c.Name)
return err
}
if count > 0 {
if autofix {
var fixed int64
if fixed, err = c.Fixer(); err != nil {
logger.Critical("Error: %v whilst fixing %s", err, c.Name)
return err
}
prompt := "Deleted"
if c.FixedMessage != "" {
prompt = c.FixedMessage
}
if fixed < 0 {
logger.Info(prompt+" %d %s", count, c.Name)
} else {
logger.Info(prompt+" %d/%d %s", fixed, count, c.Name)
}
} else {
logger.Warn("Found %d %s", count, c.Name)
}
}
return nil
}
func asFixer(fn func() error) func() (int64, error) {
return func() (int64, error) {
err := fn()
return -1, err
}
}
func genericOrphanCheck(name, subject, refobject, joincond string) consistencyCheck {
return consistencyCheck{
Name: name,
Counter: func() (int64, error) {
return models.CountOrphanedObjects(subject, refobject, joincond)
},
Fixer: func() (int64, error) {
err := models.DeleteOrphanedObjects(subject, refobject, joincond)
return -1, err
},
}
}
func checkDBConsistency(logger log.Logger, autofix bool) error {
// make sure DB version is uptodate
if err := models.NewEngine(context.Background(), migrations.EnsureUpToDate); err != nil {
@@ -20,246 +78,103 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
return err
}
// find labels without existing repo or org
count, err := models.CountOrphanedLabels()
if err != nil {
logger.Critical("Error: %v whilst counting orphaned labels", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedLabels(); err != nil {
logger.Critical("Error: %v whilst deleting orphaned labels", err)
return err
}
logger.Info("%d labels without existing repository/organisation deleted", count)
} else {
logger.Warn("%d labels without existing repository/organisation", count)
}
}
// find IssueLabels without existing label
count, err = models.CountOrphanedIssueLabels()
if err != nil {
logger.Critical("Error: %v whilst counting orphaned issue_labels", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedIssueLabels(); err != nil {
logger.Critical("Error: %v whilst deleting orphaned issue_labels", err)
return err
}
logger.Info("%d issue_labels without existing label deleted", count)
} else {
logger.Warn("%d issue_labels without existing label", count)
}
}
// find issues without existing repository
count, err = models.CountOrphanedIssues()
if err != nil {
logger.Critical("Error: %v whilst counting orphaned issues", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedIssues(); err != nil {
logger.Critical("Error: %v whilst deleting orphaned issues", err)
return err
}
logger.Info("%d issues without existing repository deleted", count)
} else {
logger.Warn("%d issues without existing repository", count)
}
}
// find pulls without existing issues
count, err = models.CountOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id")
if err != nil {
logger.Critical("Error: %v whilst counting orphaned objects", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id"); err != nil {
logger.Critical("Error: %v whilst deleting orphaned objects", err)
return err
}
logger.Info("%d pull requests without existing issue deleted", count)
} else {
logger.Warn("%d pull requests without existing issue", count)
}
}
// find tracked times without existing issues/pulls
count, err = models.CountOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id")
if err != nil {
logger.Critical("Error: %v whilst counting orphaned objects", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id"); err != nil {
logger.Critical("Error: %v whilst deleting orphaned objects", err)
return err
}
logger.Info("%d tracked times without existing issue deleted", count)
} else {
logger.Warn("%d tracked times without existing issue", count)
}
}
// find null archived repositories
count, err = models.CountNullArchivedRepository()
if err != nil {
logger.Critical("Error: %v whilst counting null archived repositories", err)
return err
}
if count > 0 {
if autofix {
updatedCount, err := models.FixNullArchivedRepository()
if err != nil {
logger.Critical("Error: %v whilst fixing null archived repositories", err)
return err
}
logger.Info("%d repositories with null is_archived updated", updatedCount)
} else {
logger.Warn("%d repositories with null is_archived", count)
}
}
// find label comments with empty labels
count, err = models.CountCommentTypeLabelWithEmptyLabel()
if err != nil {
logger.Critical("Error: %v whilst counting label comments with empty labels", err)
return err
}
if count > 0 {
if autofix {
updatedCount, err := models.FixCommentTypeLabelWithEmptyLabel()
if err != nil {
logger.Critical("Error: %v whilst removing label comments with empty labels", err)
return err
}
logger.Info("%d label comments with empty labels removed", updatedCount)
} else {
logger.Warn("%d label comments with empty labels", count)
}
}
// find label comments with labels from outside the repository
count, err = models.CountCommentTypeLabelWithOutsideLabels()
if err != nil {
logger.Critical("Error: %v whilst counting label comments with outside labels", err)
return err
}
if count > 0 {
if autofix {
updatedCount, err := models.FixCommentTypeLabelWithOutsideLabels()
if err != nil {
logger.Critical("Error: %v whilst removing label comments with outside labels", err)
return err
}
log.Info("%d label comments with outside labels removed", updatedCount)
} else {
log.Warn("%d label comments with outside labels", count)
}
}
// find issue_label with labels from outside the repository
count, err = models.CountIssueLabelWithOutsideLabels()
if err != nil {
logger.Critical("Error: %v whilst counting issue_labels from outside the repository or organisation", err)
return err
}
if count > 0 {
if autofix {
updatedCount, err := models.FixIssueLabelWithOutsideLabels()
if err != nil {
logger.Critical("Error: %v whilst removing issue_labels from outside the repository or organisation", err)
return err
}
logger.Info("%d issue_labels from outside the repository or organisation removed", updatedCount)
} else {
logger.Warn("%d issue_labels from outside the repository or organisation", count)
}
consistencyChecks := []consistencyCheck{
{
// find labels without existing repo or org
Name: "Orphaned Labels without existing repository or organisation",
Counter: models.CountOrphanedLabels,
Fixer: asFixer(models.DeleteOrphanedLabels),
},
{
// find IssueLabels without existing label
Name: "Orphaned Issue Labels without existing label",
Counter: models.CountOrphanedIssueLabels,
Fixer: asFixer(models.DeleteOrphanedIssueLabels),
},
{
// find issues without existing repository
Name: "Orphaned Issues without existing repository",
Counter: models.CountOrphanedIssues,
Fixer: asFixer(models.DeleteOrphanedIssues),
},
// find releases without existing repository
genericOrphanCheck("Orphaned Releases without existing repository",
"release", "repository", "release.repo_id=repository.id"),
// find pulls without existing issues
genericOrphanCheck("Orphaned PullRequests without existing issue",
"pull_request", "issue", "pull_request.issue_id=issue.id"),
// find tracked times without existing issues/pulls
genericOrphanCheck("Orphaned TrackedTimes without existing issue",
"tracked_time", "issue", "tracked_time.issue_id=issue.id"),
// find null archived repositories
{
Name: "Repositories with is_archived IS NULL",
Counter: models.CountNullArchivedRepository,
Fixer: models.FixNullArchivedRepository,
FixedMessage: "Fixed",
},
// find label comments with empty labels
{
Name: "Label comments with empty labels",
Counter: models.CountCommentTypeLabelWithEmptyLabel,
Fixer: models.FixCommentTypeLabelWithEmptyLabel,
FixedMessage: "Fixed",
},
// find label comments with labels from outside the repository
{
Name: "Label comments with labels from outside the repository",
Counter: models.CountCommentTypeLabelWithOutsideLabels,
Fixer: models.FixCommentTypeLabelWithOutsideLabels,
FixedMessage: "Removed",
},
// find issue_label with labels from outside the repository
{
Name: "IssueLabels with Labels from outside the repository",
Counter: models.CountIssueLabelWithOutsideLabels,
Fixer: models.FixIssueLabelWithOutsideLabels,
FixedMessage: "Removed",
},
}
// TODO: function to recalc all counters
if setting.Database.UsePostgreSQL {
count, err = models.CountBadSequences()
if err != nil {
logger.Critical("Error: %v whilst checking sequence values", err)
consistencyChecks = append(consistencyChecks, consistencyCheck{
Name: "Sequence values",
Counter: models.CountBadSequences,
Fixer: asFixer(models.FixBadSequences),
FixedMessage: "Updated",
})
}
consistencyChecks = append(consistencyChecks,
// find protected branches without existing repository
genericOrphanCheck("Protected Branches without existing repository",
"protected_branch", "repository", "protected_branch.repo_id=repository.id"),
// find deleted branches without existing repository
genericOrphanCheck("Deleted Branches without existing repository",
"deleted_branch", "repository", "deleted_branch.repo_id=repository.id"),
// find LFS locks without existing repository
genericOrphanCheck("LFS locks without existing repository",
"lfs_lock", "repository", "lfs_lock.repo_id=repository.id"),
// find collaborations without users
genericOrphanCheck("Collaborations without existing user",
"collaboration", "user", "collaboration.user_id=user.id"),
// find collaborations without repository
genericOrphanCheck("Collaborations without existing repository",
"collaboration", "repository", "collaboration.repo_id=repository.id"),
// find access without users
genericOrphanCheck("Access entries without existing user",
"access", "user", "access.user_id=user.id"),
// find access without repository
genericOrphanCheck("Access entries without existing repository",
"access", "repository", "access.repo_id=repository.id"),
)
for _, c := range consistencyChecks {
if err := c.Run(logger, autofix); err != nil {
return err
}
if count > 0 {
if autofix {
err := models.FixBadSequences()
if err != nil {
logger.Critical("Error: %v whilst attempting to fix sequences", err)
return err
}
logger.Info("%d sequences updated", count)
} else {
logger.Warn("%d sequences with incorrect values", count)
}
}
}
// find protected branches without existing repository
count, err = models.CountOrphanedObjects("protected_branch", "repository", "protected_branch.repo_id=repository.id")
if err != nil {
logger.Critical("Error: %v whilst counting orphaned objects", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedObjects("protected_branch", "repository", "protected_branch.repo_id=repository.id"); err != nil {
logger.Critical("Error: %v whilst deleting orphaned objects", err)
return err
}
logger.Info("%d protected branches without existing repository deleted", count)
} else {
logger.Warn("%d protected branches without existing repository", count)
}
}
// find deleted branches without existing repository
count, err = models.CountOrphanedObjects("deleted_branch", "repository", "deleted_branch.repo_id=repository.id")
if err != nil {
logger.Critical("Error: %v whilst counting orphaned objects", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedObjects("deleted_branch", "repository", "deleted_branch.repo_id=repository.id"); err != nil {
logger.Critical("Error: %v whilst deleting orphaned objects", err)
return err
}
logger.Info("%d deleted branches without existing repository deleted", count)
} else {
logger.Warn("%d deleted branches without existing repository", count)
}
}
// find LFS locks without existing repository
count, err = models.CountOrphanedObjects("lfs_lock", "repository", "lfs_lock.repo_id=repository.id")
if err != nil {
logger.Critical("Error: %v whilst counting orphaned objects", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedObjects("lfs_lock", "repository", "lfs_lock.repo_id=repository.id"); err != nil {
logger.Critical("Error: %v whilst deleting orphaned objects", err)
return err
}
logger.Info("%d LFS locks without existing repository deleted", count)
} else {
logger.Warn("%d LFS locks without existing repository", count)
}
}
return nil
+317
View File
@@ -0,0 +1,317 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package doctor
import (
"bytes"
"fmt"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
)
// #16831 revealed that the dump command that was broken in 1.14.3-1.14.6 and 1.15.0 (#15885).
// This led to repo_unit and login_source cfg not being converted to JSON in the dump
// Unfortunately although it was hoped that there were only a few users affected it
// appears that many users are affected.
// We therefore need to provide a doctor command to fix this repeated issue #16961
func parseBool16961(bs []byte) (bool, error) {
if bytes.EqualFold(bs, []byte("%!s(bool=false)")) {
return false, nil
}
if bytes.EqualFold(bs, []byte("%!s(bool=true)")) {
return true, nil
}
return false, fmt.Errorf("unexpected bool format: %s", string(bs))
}
func fixUnitConfig16961(bs []byte, cfg *models.UnitConfig) (fixed bool, err error) {
err = models.JSONUnmarshalHandleDoubleEncode(bs, &cfg)
if err == nil {
return
}
// Handle #16961
if string(bs) != "&{}" && len(bs) != 0 {
return
}
return true, nil
}
func fixExternalWikiConfig16961(bs []byte, cfg *models.ExternalWikiConfig) (fixed bool, err error) {
err = models.JSONUnmarshalHandleDoubleEncode(bs, &cfg)
if err == nil {
return
}
if len(bs) < 3 {
return
}
if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
return
}
cfg.ExternalWikiURL = string(bs[2 : len(bs)-1])
return true, nil
}
func fixExternalTrackerConfig16961(bs []byte, cfg *models.ExternalTrackerConfig) (fixed bool, err error) {
err = models.JSONUnmarshalHandleDoubleEncode(bs, &cfg)
if err == nil {
return
}
// Handle #16961
if len(bs) < 3 {
return
}
if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
return
}
parts := bytes.Split(bs[2:len(bs)-1], []byte{' '})
if len(parts) != 3 {
return
}
cfg.ExternalTrackerURL = string(bytes.Join(parts[:len(parts)-2], []byte{' '}))
cfg.ExternalTrackerFormat = string(parts[len(parts)-2])
cfg.ExternalTrackerStyle = string(parts[len(parts)-1])
return true, nil
}
func fixPullRequestsConfig16961(bs []byte, cfg *models.PullRequestsConfig) (fixed bool, err error) {
err = models.JSONUnmarshalHandleDoubleEncode(bs, &cfg)
if err == nil {
return
}
// Handle #16961
if len(bs) < 3 {
return
}
if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
return
}
// PullRequestsConfig was the following in 1.14
// type PullRequestsConfig struct {
// IgnoreWhitespaceConflicts bool
// AllowMerge bool
// AllowRebase bool
// AllowRebaseMerge bool
// AllowSquash bool
// AllowManualMerge bool
// AutodetectManualMerge bool
// }
//
// 1.15 added in addition:
// DefaultDeleteBranchAfterMerge bool
// DefaultMergeStyle MergeStyle
parts := bytes.Split(bs[2:len(bs)-1], []byte{' '})
if len(parts) < 7 {
return
}
var parseErr error
cfg.IgnoreWhitespaceConflicts, parseErr = parseBool16961(parts[0])
if parseErr != nil {
return
}
cfg.AllowMerge, parseErr = parseBool16961(parts[1])
if parseErr != nil {
return
}
cfg.AllowRebase, parseErr = parseBool16961(parts[2])
if parseErr != nil {
return
}
cfg.AllowRebaseMerge, parseErr = parseBool16961(parts[3])
if parseErr != nil {
return
}
cfg.AllowSquash, parseErr = parseBool16961(parts[4])
if parseErr != nil {
return
}
cfg.AllowManualMerge, parseErr = parseBool16961(parts[5])
if parseErr != nil {
return
}
cfg.AutodetectManualMerge, parseErr = parseBool16961(parts[6])
if parseErr != nil {
return
}
// 1.14 unit
if len(parts) == 7 {
return true, nil
}
if len(parts) < 9 {
return
}
cfg.DefaultDeleteBranchAfterMerge, parseErr = parseBool16961(parts[7])
if parseErr != nil {
return
}
cfg.DefaultMergeStyle = models.MergeStyle(string(bytes.Join(parts[8:], []byte{' '})))
return true, nil
}
func fixIssuesConfig16961(bs []byte, cfg *models.IssuesConfig) (fixed bool, err error) {
err = models.JSONUnmarshalHandleDoubleEncode(bs, &cfg)
if err == nil {
return
}
// Handle #16961
if len(bs) < 3 {
return
}
if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
return
}
parts := bytes.Split(bs[2:len(bs)-1], []byte{' '})
if len(parts) != 3 {
return
}
var parseErr error
cfg.EnableTimetracker, parseErr = parseBool16961(parts[0])
if parseErr != nil {
return
}
cfg.AllowOnlyContributorsToTrackTime, parseErr = parseBool16961(parts[1])
if parseErr != nil {
return
}
cfg.EnableDependencies, parseErr = parseBool16961(parts[2])
if parseErr != nil {
return
}
return true, nil
}
func fixBrokenRepoUnit16961(repoUnit *models.RepoUnit, bs []byte) (fixed bool, err error) {
// Shortcut empty or null values
if len(bs) == 0 {
return false, nil
}
switch models.UnitType(repoUnit.Type) {
case models.UnitTypeCode, models.UnitTypeReleases, models.UnitTypeWiki, models.UnitTypeProjects:
cfg := &models.UnitConfig{}
repoUnit.Config = cfg
if fixed, err := fixUnitConfig16961(bs, cfg); !fixed {
return false, err
}
case models.UnitTypeExternalWiki:
cfg := &models.ExternalWikiConfig{}
repoUnit.Config = cfg
if fixed, err := fixExternalWikiConfig16961(bs, cfg); !fixed {
return false, err
}
case models.UnitTypeExternalTracker:
cfg := &models.ExternalTrackerConfig{}
repoUnit.Config = cfg
if fixed, err := fixExternalTrackerConfig16961(bs, cfg); !fixed {
return false, err
}
case models.UnitTypePullRequests:
cfg := &models.PullRequestsConfig{}
repoUnit.Config = cfg
if fixed, err := fixPullRequestsConfig16961(bs, cfg); !fixed {
return false, err
}
case models.UnitTypeIssues:
cfg := &models.IssuesConfig{}
repoUnit.Config = cfg
if fixed, err := fixIssuesConfig16961(bs, cfg); !fixed {
return false, err
}
default:
panic(fmt.Sprintf("unrecognized repo unit type: %v", repoUnit.Type))
}
return true, nil
}
func fixBrokenRepoUnits16961(logger log.Logger, autofix bool) error {
// RepoUnit describes all units of a repository
type RepoUnit struct {
ID int64
RepoID int64
Type models.UnitType
Config []byte
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
}
count := 0
err := models.Iterate(
models.DefaultDBContext(),
new(RepoUnit),
builder.Gt{
"id": 0,
},
func(idx int, bean interface{}) error {
unit := bean.(*RepoUnit)
bs := unit.Config
repoUnit := &models.RepoUnit{
ID: unit.ID,
RepoID: unit.RepoID,
Type: unit.Type,
CreatedUnix: unit.CreatedUnix,
}
if fixed, err := fixBrokenRepoUnit16961(repoUnit, bs); !fixed {
return err
}
count++
if !autofix {
return nil
}
return models.UpdateRepoUnit(repoUnit)
},
)
if err != nil {
logger.Critical("Unable to iterate acrosss repounits to fix the broken units: Error %v", err)
return err
}
if !autofix {
logger.Warn("Found %d broken repo_units", count)
return nil
}
logger.Info("Fixed %d broken repo_units", count)
return nil
}
func init() {
Register(&Check{
Title: "Check for incorrectly dumped repo_units (See #16961)",
Name: "fix-broken-repo-units",
IsDefault: false,
Run: fixBrokenRepoUnits16961,
Priority: 7,
})
}
+271
View File
@@ -0,0 +1,271 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package doctor
import (
"testing"
"code.gitea.io/gitea/models"
"github.com/stretchr/testify/assert"
)
func Test_fixUnitConfig_16961(t *testing.T) {
tests := []struct {
name string
bs string
wantFixed bool
wantErr bool
}{
{
name: "empty",
bs: "",
wantFixed: true,
wantErr: false,
},
{
name: "normal: {}",
bs: "{}",
wantFixed: false,
wantErr: false,
},
{
name: "broken but fixable: &{}",
bs: "&{}",
wantFixed: true,
wantErr: false,
},
{
name: "broken but unfixable: &{asdasd}",
bs: "&{asdasd}",
wantFixed: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotFixed, err := fixUnitConfig16961([]byte(tt.bs), &models.UnitConfig{})
if (err != nil) != tt.wantErr {
t.Errorf("fixUnitConfig_16961() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotFixed != tt.wantFixed {
t.Errorf("fixUnitConfig_16961() = %v, want %v", gotFixed, tt.wantFixed)
}
})
}
}
func Test_fixExternalWikiConfig_16961(t *testing.T) {
tests := []struct {
name string
bs string
expected string
wantFixed bool
wantErr bool
}{
{
name: "normal: {\"ExternalWikiURL\":\"http://someurl\"}",
bs: "{\"ExternalWikiURL\":\"http://someurl\"}",
expected: "http://someurl",
wantFixed: false,
wantErr: false,
},
{
name: "broken: &{http://someurl}",
bs: "&{http://someurl}",
expected: "http://someurl",
wantFixed: true,
wantErr: false,
},
{
name: "broken but unfixable: http://someurl",
bs: "http://someurl",
wantFixed: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &models.ExternalWikiConfig{}
gotFixed, err := fixExternalWikiConfig16961([]byte(tt.bs), cfg)
if (err != nil) != tt.wantErr {
t.Errorf("fixExternalWikiConfig_16961() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotFixed != tt.wantFixed {
t.Errorf("fixExternalWikiConfig_16961() = %v, want %v", gotFixed, tt.wantFixed)
}
if cfg.ExternalWikiURL != tt.expected {
t.Errorf("fixExternalWikiConfig_16961().ExternalWikiURL = %v, want %v", cfg.ExternalWikiURL, tt.expected)
}
})
}
}
func Test_fixExternalTrackerConfig_16961(t *testing.T) {
tests := []struct {
name string
bs string
expected models.ExternalTrackerConfig
wantFixed bool
wantErr bool
}{
{
name: "normal",
bs: `{"ExternalTrackerURL":"a","ExternalTrackerFormat":"b","ExternalTrackerStyle":"c"}`,
expected: models.ExternalTrackerConfig{
ExternalTrackerURL: "a",
ExternalTrackerFormat: "b",
ExternalTrackerStyle: "c",
},
wantFixed: false,
wantErr: false,
},
{
name: "broken",
bs: "&{a b c}",
expected: models.ExternalTrackerConfig{
ExternalTrackerURL: "a",
ExternalTrackerFormat: "b",
ExternalTrackerStyle: "c",
},
wantFixed: true,
wantErr: false,
},
{
name: "broken - too many fields",
bs: "&{a b c d}",
wantFixed: false,
wantErr: true,
},
{
name: "broken - wrong format",
bs: "a b c d}",
wantFixed: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &models.ExternalTrackerConfig{}
gotFixed, err := fixExternalTrackerConfig16961([]byte(tt.bs), cfg)
if (err != nil) != tt.wantErr {
t.Errorf("fixExternalTrackerConfig_16961() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotFixed != tt.wantFixed {
t.Errorf("fixExternalTrackerConfig_16961() = %v, want %v", gotFixed, tt.wantFixed)
}
if cfg.ExternalTrackerFormat != tt.expected.ExternalTrackerFormat {
t.Errorf("fixExternalTrackerConfig_16961().ExternalTrackerFormat = %v, want %v", tt.expected.ExternalTrackerFormat, cfg.ExternalTrackerFormat)
}
if cfg.ExternalTrackerStyle != tt.expected.ExternalTrackerStyle {
t.Errorf("fixExternalTrackerConfig_16961().ExternalTrackerStyle = %v, want %v", tt.expected.ExternalTrackerStyle, cfg.ExternalTrackerStyle)
}
if cfg.ExternalTrackerURL != tt.expected.ExternalTrackerURL {
t.Errorf("fixExternalTrackerConfig_16961().ExternalTrackerURL = %v, want %v", tt.expected.ExternalTrackerURL, cfg.ExternalTrackerURL)
}
})
}
}
func Test_fixPullRequestsConfig_16961(t *testing.T) {
tests := []struct {
name string
bs string
expected models.PullRequestsConfig
wantFixed bool
wantErr bool
}{
{
name: "normal",
bs: `{"IgnoreWhitespaceConflicts":false,"AllowMerge":false,"AllowRebase":false,"AllowRebaseMerge":false,"AllowSquash":false,"AllowManualMerge":false,"AutodetectManualMerge":false,"DefaultDeleteBranchAfterMerge":false,"DefaultMergeStyle":""}`,
},
{
name: "broken - 1.14",
bs: `&{%!s(bool=false) %!s(bool=true) %!s(bool=true) %!s(bool=true) %!s(bool=true) %!s(bool=false) %!s(bool=false)}`,
expected: models.PullRequestsConfig{
IgnoreWhitespaceConflicts: false,
AllowMerge: true,
AllowRebase: true,
AllowRebaseMerge: true,
AllowSquash: true,
AllowManualMerge: false,
AutodetectManualMerge: false,
},
wantFixed: true,
},
{
name: "broken - 1.15",
bs: `&{%!s(bool=false) %!s(bool=true) %!s(bool=true) %!s(bool=true) %!s(bool=true) %!s(bool=false) %!s(bool=false) %!s(bool=false) merge}`,
expected: models.PullRequestsConfig{
AllowMerge: true,
AllowRebase: true,
AllowRebaseMerge: true,
AllowSquash: true,
DefaultMergeStyle: models.MergeStyleMerge,
},
wantFixed: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &models.PullRequestsConfig{}
gotFixed, err := fixPullRequestsConfig16961([]byte(tt.bs), cfg)
if (err != nil) != tt.wantErr {
t.Errorf("fixPullRequestsConfig_16961() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotFixed != tt.wantFixed {
t.Errorf("fixPullRequestsConfig_16961() = %v, want %v", gotFixed, tt.wantFixed)
}
assert.EqualValues(t, &tt.expected, cfg)
})
}
}
func Test_fixIssuesConfig_16961(t *testing.T) {
tests := []struct {
name string
bs string
expected models.IssuesConfig
wantFixed bool
wantErr bool
}{
{
name: "normal",
bs: `{"EnableTimetracker":true,"AllowOnlyContributorsToTrackTime":true,"EnableDependencies":true}`,
expected: models.IssuesConfig{
EnableTimetracker: true,
AllowOnlyContributorsToTrackTime: true,
EnableDependencies: true,
},
},
{
name: "broken",
bs: `&{%!s(bool=true) %!s(bool=true) %!s(bool=true)}`,
expected: models.IssuesConfig{
EnableTimetracker: true,
AllowOnlyContributorsToTrackTime: true,
EnableDependencies: true,
},
wantFixed: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &models.IssuesConfig{}
gotFixed, err := fixIssuesConfig16961([]byte(tt.bs), cfg)
if (err != nil) != tt.wantErr {
t.Errorf("fixIssuesConfig_16961() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotFixed != tt.wantFixed {
t.Errorf("fixIssuesConfig_16961() = %v, want %v", gotFixed, tt.wantFixed)
}
assert.EqualValues(t, &tt.expected, cfg)
})
}
}
+12 -11
View File
@@ -66,17 +66,6 @@ func Code(fileName, code string) string {
if len(code) > sizeLimit {
return code
}
formatter := html.New(html.WithClasses(true),
html.WithLineNumbers(false),
html.PreventSurroundingPre(true),
)
if formatter == nil {
log.Error("Couldn't create chroma formatter")
return code
}
htmlbuf := bytes.Buffer{}
htmlw := bufio.NewWriter(&htmlbuf)
var lexer chroma.Lexer
if val, ok := highlightMapping[filepath.Ext(fileName)]; ok {
@@ -97,6 +86,18 @@ func Code(fileName, code string) string {
}
cache.Add(fileName, lexer)
}
return CodeFromLexer(lexer, code)
}
// CodeFromLexer returns a HTML version of code string with chroma syntax highlighting classes
func CodeFromLexer(lexer chroma.Lexer, code string) string {
formatter := html.New(html.WithClasses(true),
html.WithLineNumbers(false),
html.PreventSurroundingPre(true),
)
htmlbuf := bytes.Buffer{}
htmlw := bufio.NewWriter(&htmlbuf)
iterator, err := lexer.Tokenise(nil, string(code))
if err != nil {
+8 -1
View File
@@ -12,6 +12,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -51,6 +52,12 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
htmlWriter := org.NewHTMLWriter()
htmlWriter.HighlightCodeBlock = func(source, lang string, inline bool) string {
defer func() {
if err := recover(); err != nil {
log.Error("Panic in HighlightCodeBlock: %v\n%s", err, log.Stack(2))
panic(err)
}
}()
var w strings.Builder
if _, err := w.WriteString(`<pre>`); err != nil {
return ""
@@ -80,7 +87,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
}
lexer = chroma.Coalesce(lexer)
if _, err := w.WriteString(highlight.Code(lexer.Config().Filenames[0], source)); err != nil {
if _, err := w.WriteString(highlight.CodeFromLexer(lexer, source)); err != nil {
return ""
}
}
+26
View File
@@ -57,3 +57,29 @@ func TestRender_Images(t *testing.T) {
test("[[file:"+url+"]]",
"<p><img src=\""+result+"\" alt=\""+result+"\" title=\""+result+"\" /></p>")
}
func TestRender_Source(t *testing.T) {
setting.AppURL = AppURL
setting.AppSubURL = AppSubURL
test := func(input, expected string) {
buffer, err := RenderString(&markup.RenderContext{
URLPrefix: setting.AppSubURL,
}, input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
}
test(`#+begin_src go
// HelloWorld prints "Hello World"
func HelloWorld() {
fmt.Println("Hello World")
}
#+end_src
`, `<div class="src src-go">
<pre><code class="chroma language-go"><span class="c1">// HelloWorld prints &#34;Hello World&#34;
</span><span class="c1"></span><span class="kd">func</span> <span class="nf">HelloWorld</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Hello World&#34;</span><span class="p">)</span>
<span class="p">}</span></code></pre>
</div>`)
}
+12 -12
View File
@@ -51,7 +51,7 @@ func (m *webhookNotifier) NotifyIssueClearLabels(doer *models.User, issue *model
err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequestLabel, &api.PullRequestPayload{
Action: api.HookIssueLabelCleared,
Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil),
Repository: convert.ToRepo(issue.Repo, mode),
Sender: convert.ToUser(doer, nil),
})
@@ -145,7 +145,7 @@ func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *mo
issue.PullRequest.Issue = issue
apiPullRequest := &api.PullRequestPayload{
Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil),
Repository: convert.ToRepo(issue.Repo, mode),
Sender: convert.ToUser(doer, nil),
}
@@ -197,7 +197,7 @@ func (m *webhookNotifier) NotifyIssueChangeTitle(doer *models.User, issue *model
From: oldTitle,
},
},
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil),
Repository: convert.ToRepo(issue.Repo, mode),
Sender: convert.ToUser(doer, nil),
})
@@ -232,7 +232,7 @@ func (m *webhookNotifier) NotifyIssueChangeStatus(doer *models.User, issue *mode
// Merge pull request calls issue.changeStatus so we need to handle separately.
apiPullRequest := &api.PullRequestPayload{
Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil),
Repository: convert.ToRepo(issue.Repo, mode),
Sender: convert.ToUser(doer, nil),
}
@@ -301,7 +301,7 @@ func (m *webhookNotifier) NotifyNewPullRequest(pull *models.PullRequest, mention
if err := webhook_services.PrepareWebhooks(pull.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
Action: api.HookIssueOpened,
Index: pull.Issue.Index,
PullRequest: convert.ToAPIPullRequest(pull),
PullRequest: convert.ToAPIPullRequest(pull, nil),
Repository: convert.ToRepo(pull.Issue.Repo, mode),
Sender: convert.ToUser(pull.Issue.Poster, nil),
}); err != nil {
@@ -322,7 +322,7 @@ func (m *webhookNotifier) NotifyIssueChangeContent(doer *models.User, issue *mod
From: oldContent,
},
},
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil),
Repository: convert.ToRepo(issue.Repo, mode),
Sender: convert.ToUser(doer, nil),
})
@@ -500,7 +500,7 @@ func (m *webhookNotifier) NotifyIssueChangeLabels(doer *models.User, issue *mode
err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequestLabel, &api.PullRequestPayload{
Action: api.HookIssueLabelUpdated,
Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil),
Repository: convert.ToRepo(issue.Repo, models.AccessModeNone),
Sender: convert.ToUser(doer, nil),
})
@@ -542,7 +542,7 @@ func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *m
err = webhook_services.PrepareWebhooks(issue.Repo, models.HookEventPullRequestMilestone, &api.PullRequestPayload{
Action: hookAction,
Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil),
Repository: convert.ToRepo(issue.Repo, mode),
Sender: convert.ToUser(doer, nil),
})
@@ -609,7 +609,7 @@ func (*webhookNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *mod
// Merge pull request calls issue.changeStatus so we need to handle separately.
apiPullRequest := &api.PullRequestPayload{
Index: pr.Issue.Index,
PullRequest: convert.ToAPIPullRequest(pr),
PullRequest: convert.ToAPIPullRequest(pr, nil),
Repository: convert.ToRepo(pr.Issue.Repo, mode),
Sender: convert.ToUser(doer, nil),
Action: api.HookIssueClosed,
@@ -642,7 +642,7 @@ func (m *webhookNotifier) NotifyPullRequestChangeTargetBranch(doer *models.User,
From: oldBranch,
},
},
PullRequest: convert.ToAPIPullRequest(issue.PullRequest),
PullRequest: convert.ToAPIPullRequest(issue.PullRequest, nil),
Repository: convert.ToRepo(issue.Repo, mode),
Sender: convert.ToUser(doer, nil),
})
@@ -681,7 +681,7 @@ func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review
if err := webhook_services.PrepareWebhooks(review.Issue.Repo, reviewHookType, &api.PullRequestPayload{
Action: api.HookIssueReviewed,
Index: review.Issue.Index,
PullRequest: convert.ToAPIPullRequest(pr),
PullRequest: convert.ToAPIPullRequest(pr, nil),
Repository: convert.ToRepo(review.Issue.Repo, mode),
Sender: convert.ToUser(review.Reviewer, nil),
Review: &api.ReviewPayload{
@@ -736,7 +736,7 @@ func (m *webhookNotifier) NotifyPullRequestSynchronized(doer *models.User, pr *m
if err := webhook_services.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequestSync, &api.PullRequestPayload{
Action: api.HookIssueSynchronized,
Index: pr.Issue.Index,
PullRequest: convert.ToAPIPullRequest(pr),
PullRequest: convert.ToAPIPullRequest(pr, nil),
Repository: convert.ToRepo(pr.Issue.Repo, models.AccessModeNone),
Sender: convert.ToUser(doer, nil),
}); err != nil {
+1 -1
View File
@@ -534,7 +534,7 @@ migrate.clone_address_desc=HTTP(S) или Git URL за клониране на
migrate.clone_local_path=или път към локален сървър
migrate.permission_denied=Недостатъчни права за импорт на локални хранилища.
migrate.failed=Грешка при миграция: %v
migrated_from_fake=Мигриран от %[1]с
migrated_from_fake=Мигриран от %[1]s
migrate.migrating=Мигриране от <b>%s</b>...
migrate.migrating_failed=Мигрирането от <b>%s</b> беше неуспешно.
+1 -1
View File
@@ -326,7 +326,7 @@ hi_user_x=Hallo <b>%s</b>,
activate_account=Bitte aktiviere dein Konto
activate_account.title=%s, bitte aktiviere dein Konto
activate_account.text_1=Hallo <b>%[1]s</b>, danke für deine Registrierung bei %[2]!
activate_account.text_1=Hallo <b>%[1]s</b>, danke für deine Registrierung bei %[2]s!
activate_account.text_2=Bitte klicke innerhalb von <b>%s</b> auf folgenden Link, um dein Konto zu aktivieren:
activate_email=Bestätige deine E-Mail-Adresse
+2 -2
View File
@@ -345,8 +345,8 @@ reset_password.text = Please click the following link to recover your account wi
register_success = Registration successful
issue_assigned.pull = @%[1]s assigned you to the pull request %[2]s in repository %[3]s.
issue_assigned.issue = @%[1]s assigned you to the issue %[2]s in repository %[3]s.
issue_assigned.pull = @%[1]s assigned you to pull request %[2]s in repository %[3]s.
issue_assigned.issue = @%[1]s assigned you to issue %[2]s in repository %[3]s.
issue.x_mentioned_you = <b>@%s</b> mentioned you:
issue.action.force_push = <b>%[1]s</b> force-pushed the <b>%[2]s</b> from %[3]s to %[4]s.
+1 -1
View File
@@ -326,7 +326,7 @@ hi_user_x=Hola <b>%s</b>,
activate_account=Por favor, active su cuenta
activate_account.title=%s, por favor activa tu cuenta
activate_account.text_1=¡Hola <b>%[1]s</b>, gracias por registrarse en %[2]!
activate_account.text_1=¡Hola <b>%[1]s</b>, gracias por registrarse en %[2]s!
activate_account.text_2=Por favor, haga clic en el siguiente enlace para activar su cuenta dentro de <b>%s</b>:
activate_email=Verifique su correo electrónico
+1 -1
View File
@@ -1048,7 +1048,7 @@ issues.action_assignee=Toegewezene
issues.action_assignee_no_select=Geen verantwoordelijke
issues.opened_by=%[1]s geopend door <a href="%[2]s">%[3]s</a>
issues.closed_by=door <a href="%[2]s">%[3]s</a> gesloten %[1]s
issues.closed_by_fake=met %[2]gesloten %[1]s
issues.closed_by_fake=met %[2]s gesloten %[1]s
issues.previous=Vorige
issues.next=Volgende
issues.open_title=Open
+3 -3
View File
@@ -2245,7 +2245,7 @@ dashboard.task.cancelled=Tarefa: %[1]s cancelada: %[3]s
dashboard.task.error=Erro na tarefa: %[1]s: %[3]s
dashboard.task.finished=Tarefa: %[1]s iniciada por %[2]s foi concluída
dashboard.task.unknown=Tarefa desconhecida: %[1]s
dashboard.cron.started=Cron iniciado: %[1]
dashboard.cron.started=Cron iniciado: %[1]s
dashboard.cron.process=Cron: %[1]s
dashboard.cron.cancelled=Cron: %s cancelado: %[3]s
dashboard.cron.error=Erro no cron: %s: %[3]s
@@ -2698,7 +2698,7 @@ notices.delete_success=As notificações do sistema foram eliminadas.
[action]
create_repo=criou o repositório <a href="%s">%s</a>
rename_repo=renomeou o repositório de <code>%[1]s</code> para <a href="%[2]s">%[3]</a>
rename_repo=renomeou o repositório de <code>%[1]s</code> para <a href="%[2]s">%[3]s</a>
commit_repo=enviou para <a href="%[1]s/src/branch/%[2]s">%[3]s</a> em <a href="%[1]s">%[4]s</a>
create_issue=`abriu a questão <a href="%s/issues/%s">%s#%[2]s</a>`
close_issue=`fechou a questão <a href="%s/issues/%s">%s#%[2]s</a>`
@@ -2724,7 +2724,7 @@ reject_pull_request=`sugeriu modificações para <a href="%s/pulls/%s">%s#%[2]s<
publish_release=`lançou <a href="%s/releases/tag/%s"> "%[4]s" </a> à <a href="%[1]s">%[3]s</a>`
review_dismissed=`descartou a revisão de <b>%[4]s</b> para <a href="%[1]s/pulls/%[2]s">%[3]s#%[2]s</a>`
review_dismissed_reason=Motivo:
create_branch=criou o ramo <a href="%[1]s/src/branch/%[2]s">%[3]s</a> em <a href="%[1]s">%[4]</a>
create_branch=criou o ramo <a href="%[1]s/src/branch/%[2]s">%[3]s</a> em <a href="%[1]s">%[4]s</a>
[tool]
ago=há %s
+5 -5
View File
@@ -334,7 +334,7 @@ activate_email.title=%s, пожалуйста, подтвердите ваш а
activate_email.text=Пожалуйста, перейдите по ссылке, чтобы подтвердить ваш адрес электронной почты в течение <b>%s</b>:
register_notify=Добро пожаловать на Gitea
register_notify.title=%[1], добро пожаловать в %[2]
register_notify.title=%[1]s, добро пожаловать в %[2]s
register_notify.text_1=это письмо с вашим подтверждением регистрации в %s!
register_notify.text_2=Теперь вы можете войти через логин: %s.
register_notify.text_3=Если эта учетная запись была создана для вас, пожалуйста, сначала <a href="%s">установите пароль</a>.
@@ -345,7 +345,7 @@ reset_password.text=Пожалуйста, перейдите по ссылке,
register_success=Регистрация прошла успешно
issue_assigned.pull=@%[1] назначил вам запрос на слияние %[2] в репозитории %[3].
issue_assigned.pull=@%[1]s назначил вам запрос на слияние %[2]s в репозитории %[3]s.
issue_assigned.issue=@%[1]s назначил вам задачу %[2]s в репозитории %[3]s.
issue.x_mentioned_you=<b>@%s</b> упомянул вас:
@@ -1219,8 +1219,8 @@ issues.reopened_at=`переоткрыл(а) эту проблему <a id="%[1]
issues.commit_ref_at=`упомянул эту задачу в коммите <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_issue_from=`<a href="%[3]s">ссылка на эту проблему %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_pull_from=`<a href="%[3]s">ссылается на этот Pull Request %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closing_from=`<a href="%[3]s">ссылается на Pull Request %[4], который закроет эту задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopening_from=`<a href="%[3]s">ссылается на Pull Request %[4], который вновь откроет эту задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closing_from=`<a href="%[3]s">ссылается на Pull Request %[4]s, который закроет эту задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopening_from=`<a href="%[3]s">ссылается на Pull Request %[4]s, который вновь откроет эту задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closed_from=`<a href="%[3]s">закрыл этот запрос %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopened_from=`<a href="%[3]s">переоткрыл эту задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_from=`из %[1]s`
@@ -1303,7 +1303,7 @@ issues.error_modifying_due_date=Не удалось изменить срок в
issues.error_removing_due_date=Не удалось убрать срок выполнения.
issues.push_commit_1=добавил(а) %d коммит %s
issues.push_commits_n=добавил(а) %d коммитов %s
issues.force_push_codes=`принудительно залито %[1]s от <a class="ui sha" href="%[3]s"><code>%[2]</code></a> к <a class="ui sha" href="%[5]s"><code>%[4]s</code></a> %[6]s`
issues.force_push_codes=`принудительно залито %[1]s от <a class="ui sha" href="%[3]s"><code>%[2]s</code></a> к <a class="ui sha" href="%[5]s"><code>%[4]s</code></a> %[6]s`
issues.due_date_form=гггг-мм-дд
issues.due_date_form_add=Добавить срок выполнения
issues.due_date_form_edit=Редактировать
+1 -1
View File
@@ -1147,7 +1147,7 @@ issues.reopened_at=`<a id="%[1]s" href="#%[1]s">%[2]s</a> konusunu yeniden açt
issues.commit_ref_at=`<a id="%[1]s" href="#%[1]s">%[2]s</a> işlemesinde bu konuyu işaret etti`
issues.ref_issue_from=`<a href="%[3]s">bu konuya referansta bulundu %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_pull_from=`<a href="%[3]s">bu değişiklik isteğine referansta bulundu %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closing_from=`<a href="%[3]s">bir değişiklik isteğine referansta bulundu %[4] bu konu kapatılacak </a><a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closing_from=`<a href="%[3]s">bir değişiklik isteğine referansta bulundu %[4]s bu konu kapatılacak </a><a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopening_from=`<a href="%[3]s">bir değişiklik isteğine referansta bulundu %[4]s bu konu yeniden açılacak</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closed_from=`<a href="%[3]s">bu konuyu kapat%[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopened_from=`<a href="%[3]s">konuyu yeniden aç%[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
+6 -6
View File
@@ -348,7 +348,7 @@ issue.x_mentioned_you=<b>@%s</b> згадав вас:
issue.action.force_push=<b>%[1]s</b> force-pushed <b>%[2]s</b> з %[3]s в %[4]s.
issue.action.push_n=<b>@%[1]s</b> відправив %[3]d коміти до %[2]s
issue.action.close=<b>@%[1]s</b> закрито #%[2]d.
issue.action.reopen=<b>@%[1]</b> заново відкрив #%[2]d.
issue.action.reopen=<b>@%[1]s</b> заново відкрив #%[2]d.
issue.action.merge=<b>@%[1]s</b> об'єднав #%[2]d до %[3]s.
issue.action.approve=<b>@%[1]s</b> затвердили цей запит на злиття.
issue.action.reject=<b>@%[1]s</b> запитують зміни на цей запит на злиття.
@@ -555,7 +555,7 @@ delete_email=Видалити
email_deletion=Видалити адресу електронної пошти
email_deletion_desc=Електронна адреса та пов'язана з нею інформація буде видалена з вашого облікового запису. Git коміти, здійснені через цю електронну адресу, залишиться без змін. Продовжити?
email_deletion_success=Адресу електронної пошти було видалено.
theme_update_success=Тему оновлено.
theme_update_success=Тему оновлено.
theme_update_error=Вибрана тема не існує.
openid_deletion=Видалити адресу OpenID
openid_deletion_desc=Видалення цієї OpenID-адреси з вашого облікового запису забороняє вам входити з ним. Продовжити?
@@ -1171,7 +1171,7 @@ issues.action_milestone_no_select=Етап відсутній
issues.action_assignee=Виконавець
issues.action_assignee_no_select=Немає виконавеця
issues.opened_by=%[1]s відкрито <a href="%[2]s">%[3]s</a>
pulls.merged_by=до <a href="%[2]s">%[3]</a> злито %[1]s
pulls.merged_by=до <a href="%[2]s">%[3]s</a> злито %[1]s
pulls.merged_by_fake=%[2]s об'єднаний %[1]s
issues.closed_by=закрито <a href="%[2]s">%[3]s</a> %[1]s
issues.opened_by_fake=%[2]s відкрив(ла) %[1]s
@@ -1285,7 +1285,7 @@ issues.error_modifying_due_date=Не вдалося змінити дату за
issues.error_removing_due_date=Не вдалося видалити дату завершення.
issues.push_commit_1=додав %d коміт %s
issues.push_commits_n=додав %d коміти(-ів) %s
issues.force_push_codes=`примусово залито %[1]s з <a class="ui sha" href="%[3]s"><code>%[2]</code></a> до <a class="ui sha" href="%[5]s"><code>%[4]s</code></a> %[6]s`
issues.force_push_codes=`примусово залито %[1]s з <a class="ui sha" href="%[3]s"><code>%[2]s</code></a> до <a class="ui sha" href="%[5]s"><code>%[4]s</code></a> %[6]s`
issues.due_date_form=рррр-мм-дд
issues.due_date_form_add=Додати дату завершення
issues.due_date_form_edit=Редагувати
@@ -2221,7 +2221,7 @@ dashboard.clean_unbind_oauth_success=Всі незавершені зв'язки
dashboard.task.started=Запущено завдання: %[1]s
dashboard.task.process=Завдання: %[1]s
dashboard.task.cancelled=Завдання: %[1]s скасовано: %[3]s
dashboard.task.error=Помилка у завданні: %[1]:%[3]s
dashboard.task.error=Помилка у завданні: %[1]s :%[3]s
dashboard.task.finished=Завершилося завдання, яке запустив %[2]s: %[1]s
dashboard.task.unknown=Невідоме завдання: %[1]s
dashboard.cron.started=Запущено Cron: %[1]s
@@ -2701,7 +2701,7 @@ mirror_sync_delete=синхронізовано й видалено посила
approve_pull_request=`схвалив <a href="%s/pulls/%s">%s#%[2]s</a>`
reject_pull_request=`запропонував зміни до <a href="%s/pulls/%s">%s#%[2]s</a>`
publish_release=`опублікував випуск <a href="%s/releases/tag/%s"> "%[4]s" </a> з <a href="%[1]s">%[3]s</a>`
review_dismissed=`відхилений відгук від <b>%[4]</b> у <a href="%[1]s/pulls/%[2]s">%[3]s#%[2]s</a>`
review_dismissed=`відхилений відгук від <b>%[4]s</b> у <a href="%[1]s/pulls/%[2]s">%[3]s#%[2]s</a>`
review_dismissed_reason=Причина:
create_branch=створено гілку <a href="%[1]s/src/branch/%[2]s">%[3]s</a> у <a href="%[1]s">%[4]s</a>
+1 -1
View File
@@ -1380,7 +1380,7 @@ pulls.filter_branch=过滤分支
pulls.no_results=未找到结果
pulls.nothing_to_compare=分支内容相同,无需创建合并请求。
pulls.nothing_to_compare_and_allow_empty_pr=这些分支是相等的,此合并请求将为空。
pulls.has_pull_request="在这些分支之间的合并请求已存在: <a href="%[1]s/pulls/%[3]d">%[2]s%#[3]d</a>"
pulls.has_pull_request="在这些分支之间的合并请求已存在: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>"
pulls.create=创建合并请求
pulls.title_desc=请求将 %[1]d 次代码提交从 <code>%[2]s</code> 合并至 <code id="branch_target">%[3]s</code>
pulls.merged_title_desc=于 %[4]s 将 %[1]d 次代码提交从 <code>%[2]s</code>合并至 <code>%[3]s</code>
+9
View File
@@ -752,6 +752,15 @@ func EditIssue(ctx *context.APIContext) {
}
}
if form.State != nil {
if issue.IsPull {
if pr, err := issue.GetPullRequest(); err != nil {
ctx.Error(http.StatusInternalServerError, "GetPullRequest", err)
return
} else if pr.HasMerged {
ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
return
}
}
issue.IsClosed = api.StateClosed == api.StateType(*form.State)
}
statusChangeComment, titleChanged, err := models.UpdateIssueByAPI(issue, ctx.User)
+9 -5
View File
@@ -115,7 +115,7 @@ func ListPullRequests(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
return
}
apiPrs[i] = convert.ToAPIPullRequest(prs[i])
apiPrs[i] = convert.ToAPIPullRequest(prs[i], ctx.User)
}
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
@@ -172,7 +172,7 @@ func GetPullRequest(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
return
}
ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(pr))
ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(pr, ctx.User))
}
// DownloadPullDiff render a pull's raw diff
@@ -437,7 +437,7 @@ func CreatePullRequest(ctx *context.APIContext) {
}
log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
ctx.JSON(http.StatusCreated, convert.ToAPIPullRequest(pr))
ctx.JSON(http.StatusCreated, convert.ToAPIPullRequest(pr, ctx.User))
}
// EditPullRequest does what it says
@@ -584,6 +584,10 @@ func EditPullRequest(ctx *context.APIContext) {
}
if form.State != nil {
if pr.HasMerged {
ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
return
}
issue.IsClosed = api.StateClosed == api.StateType(*form.State)
}
statusChangeComment, titleChanged, err := models.UpdateIssueByAPI(issue, ctx.User)
@@ -605,7 +609,7 @@ func EditPullRequest(ctx *context.APIContext) {
}
// change pull target branch
if len(form.Base) != 0 && form.Base != pr.BaseBranch {
if !pr.HasMerged && len(form.Base) != 0 && form.Base != pr.BaseBranch {
if !ctx.Repo.GitRepo.IsBranchExist(form.Base) {
ctx.Error(http.StatusNotFound, "NewBaseBranchNotExist", fmt.Errorf("new base '%s' not exist", form.Base))
return
@@ -640,7 +644,7 @@ func EditPullRequest(ctx *context.APIContext) {
}
// TODO this should be 200, not 201
ctx.JSON(http.StatusCreated, convert.ToAPIPullRequest(pr))
ctx.JSON(http.StatusCreated, convert.ToAPIPullRequest(pr, ctx.User))
}
// IsPullRequestMerged checks if a PR exists given an index
+9 -4
View File
@@ -374,16 +374,21 @@ func Generate(ctx *context.APIContext) {
ctxUser := ctx.User
var err error
if form.Owner != ctxUser.Name {
ctxUser, err = models.GetOrgByName(form.Owner)
ctxUser, err = models.GetUserByName(form.Owner)
if err != nil {
if models.IsErrOrgNotExist(err) {
if models.IsErrUserNotExist(err) {
ctx.JSON(http.StatusNotFound, map[string]interface{}{
"error": "request owner `" + form.Name + "` is not exist",
"error": "request owner `" + form.Owner + "` does not exist",
})
return
}
ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
return
}
if !ctx.User.IsAdmin && !ctxUser.IsOrganization() {
ctx.Error(http.StatusForbidden, "", "Only admin can generate repository for other user.")
return
}
+1 -10
View File
@@ -14,7 +14,6 @@ import (
"path/filepath"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
@@ -147,15 +146,7 @@ func Recovery() func(next http.Handler) http.Handler {
"i18n": lc,
}
var user *models.User
if apiContext := context.GetAPIContext(req); apiContext != nil {
user = apiContext.User
}
if user == nil {
if ctx := context.GetContext(req); ctx != nil {
user = ctx.User
}
}
var user = context.GetContextUser(req)
if user == nil {
// Get user from session if logged in - do not attempt to sign-in
user = auth.SessionUser(sessionStore)
+4 -18
View File
@@ -1307,30 +1307,16 @@ func DownloadPullPatch(ctx *context.Context) {
// DownloadPullDiffOrPatch render a pull's raw diff or patch
func DownloadPullDiffOrPatch(ctx *context.Context, patch bool) {
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrIssueNotExist(err) {
ctx.NotFound("GetIssueByIndex", err)
if models.IsErrPullRequestNotExist(err) {
ctx.NotFound("GetPullRequestByIndex", err)
} else {
ctx.ServerError("GetIssueByIndex", err)
ctx.ServerError("GetPullRequestByIndex", err)
}
return
}
// Return not found if it's not a pull request
if !issue.IsPull {
ctx.NotFound("DownloadPullDiff",
fmt.Errorf("Issue is not a pull request"))
return
}
if err = issue.LoadPullRequest(); err != nil {
ctx.ServerError("LoadPullRequest", err)
return
}
pr := issue.PullRequest
if err := pull_service.DownloadDiffOrPatch(pr, ctx, patch); err != nil {
ctx.ServerError("DownloadDiffOrPatch", err)
return
+1 -1
View File
@@ -208,7 +208,7 @@ func DeleteKey(ctx *context.Context) {
return
}
if external {
ctx.Flash.Error(ctx.Tr("setting.ssh_externally_managed"))
ctx.Flash.Error(ctx.Tr("settings.ssh_externally_managed"))
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
return
}
+4 -4
View File
@@ -32,7 +32,7 @@ func RegenerateScratchTwoFactor(ctx *context.Context) {
t, err := models.GetTwoFactorByUID(ctx.User.ID)
if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) {
ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled"))
ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
}
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
@@ -62,7 +62,7 @@ func DisableTwoFactor(ctx *context.Context) {
t, err := models.GetTwoFactorByUID(ctx.User.ID)
if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) {
ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled"))
ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
}
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
@@ -150,7 +150,7 @@ func EnrollTwoFactor(ctx *context.Context) {
if t != nil {
// already enrolled - we should redirect back!
log.Warn("Trying to re-enroll %-v in twofa when already enrolled", ctx.User)
ctx.Flash.Error(ctx.Tr("setting.twofa_is_enrolled"))
ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
return
}
@@ -175,7 +175,7 @@ func EnrollTwoFactorPost(ctx *context.Context) {
t, err := models.GetTwoFactorByUID(ctx.User.ID)
if t != nil {
// already enrolled
ctx.Flash.Error(ctx.Tr("setting.twofa_is_enrolled"))
ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
return
}
+4 -2
View File
@@ -132,9 +132,11 @@ func doArchive(r *ArchiveRequest) (*models.RepoArchiver, error) {
if err == nil {
if archiver.Status == models.RepoArchiverGenerating {
archiver.Status = models.RepoArchiverReady
return archiver, models.UpdateRepoArchiverStatus(ctx, archiver)
if err = models.UpdateRepoArchiverStatus(ctx, archiver); err != nil {
return nil, err
}
}
return archiver, nil
return archiver, commiter.Commit()
}
if !errors.Is(err, os.ErrNotExist) {
+10
View File
@@ -344,6 +344,16 @@ func sanitizeSubject(subject string) string {
// SendIssueAssignedMail composes and sends issue assigned email
func SendIssueAssignedMail(issue *models.Issue, doer *models.User, content string, comment *models.Comment, recipients []*models.User) error {
if setting.MailService == nil {
// No mail service configured
return nil
}
if err := issue.LoadRepo(); err != nil {
log.Error("Unable to load repo [%d] for issue #%d [%d]. Error: %v", issue.RepoID, issue.Index, issue.ID, err)
return err
}
langMap := make(map[string][]*models.User)
for _, user := range recipients {
langMap[user.Language] = append(langMap[user.Language], user)
+1 -1
View File
@@ -8,7 +8,7 @@
<title>{{.Subject}}</title>
</head>
{{$repo_url := printf "<a href='%s'>%s</a>" .Release.Repo.HTMLURL .Release.Repo.FullName}}
{{$repo_url := printf "<a href='%s'>%s</a>" .Issue.Repo.HTMLURL .Issue.Repo.FullName}}
{{$link := printf "<a href='%s'>#%d</a>" .Link .Issue.Index}}
<body>
<p>
+1 -1
View File
@@ -1047,7 +1047,7 @@ strk.kbt.io/projects/go/libravatar
# xorm.io/builder v0.3.9
## explicit
xorm.io/builder
# xorm.io/xorm v1.2.4
# xorm.io/xorm v1.2.5
## explicit
xorm.io/xorm
xorm.io/xorm/caches
+1 -1
View File
@@ -206,7 +206,7 @@ func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string {
// ModifyColumnSQL returns a SQL to modify SQL
func (db *Base) ModifyColumnSQL(tableName string, col *schemas.Column) string {
s, _ := ColumnString(db.dialect, col, false)
return fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s", tableName, s)
return fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s", db.quoter.Quote(tableName), s)
}
// ForUpdateSQL returns for updateSQL
+1 -1
View File
@@ -423,7 +423,7 @@ func (db *mssql) DropTableSQL(tableName string) (string, bool) {
func (db *mssql) ModifyColumnSQL(tableName string, col *schemas.Column) string {
s, _ := ColumnString(db.dialect, col, false)
return fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s", tableName, s)
return fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s", db.quoter.Quote(tableName), s)
}
func (db *mssql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) {
+2 -2
View File
@@ -988,10 +988,10 @@ func (db *postgres) IsTableExist(queryer core.Queryer, ctx context.Context, tabl
func (db *postgres) ModifyColumnSQL(tableName string, col *schemas.Column) string {
if len(db.getSchema()) == 0 || strings.Contains(tableName, ".") {
return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s",
tableName, col.Name, db.SQLType(col))
db.quoter.Quote(tableName), db.quoter.Quote(col.Name), db.SQLType(col))
}
return fmt.Sprintf("alter table %s.%s ALTER COLUMN %s TYPE %s",
db.getSchema(), tableName, col.Name, db.SQLType(col))
db.quoter.Quote(db.getSchema()), db.quoter.Quote(tableName), db.quoter.Quote(col.Name), db.SQLType(col))
}
func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string {
+2
View File
@@ -181,6 +181,7 @@ details summary > * {
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-thumb {
box-shadow: inset 0 0 0 6px var(--color-primary);
@@ -1285,6 +1286,7 @@ footer {
display: flex;
margin-left: auto;
margin-right: auto;
overflow-x: auto;
}
@media @mediaSm {