mirror of
https://github.com/go-gitea/gitea
synced 2026-02-09 23:28:09 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5845e6497 | ||
|
|
c927ebd119 | ||
|
|
245596e130 | ||
|
|
1c3ae6d05e | ||
|
|
a1e57ebe6b | ||
|
|
73ae93b007 | ||
|
|
dc030f64a7 | ||
|
|
6e0a08d753 | ||
|
|
7b1153e943 | ||
|
|
6995be66e7 | ||
|
|
28971c7c15 | ||
|
|
eb5e6f09eb | ||
|
|
bf6264c1db | ||
|
|
5b6b7e79cf | ||
|
|
766272b154 | ||
|
|
4707d4b8a9 | ||
|
|
4b8b214108 | ||
|
|
ebae7e1512 | ||
|
|
122917f4d5 | ||
|
|
9cf5739c0f | ||
|
|
4b6556565f |
+3
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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").
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 + "/"
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 "Hello World"
|
||||
</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">"Hello World"</span><span class="p">)</span>
|
||||
<span class="p">}</span></code></pre>
|
||||
</div>`)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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> беше неуспешно.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=Редактировать
|
||||
|
||||
@@ -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>`
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
Vendored
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user