mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-13 19:45:47 +02:00
Merge branch 'main' into fix-24635
This commit is contained in:
commit
e20d12e8a7
@ -111,9 +111,6 @@ linters:
|
|||||||
- require-error
|
- require-error
|
||||||
usetesting:
|
usetesting:
|
||||||
os-temp-dir: true
|
os-temp-dir: true
|
||||||
modernize:
|
|
||||||
disable:
|
|
||||||
- stringsbuilder
|
|
||||||
perfsprint:
|
perfsprint:
|
||||||
concat-loop: false
|
concat-loop: false
|
||||||
govet:
|
govet:
|
||||||
|
|||||||
6
Makefile
6
Makefile
@ -32,7 +32,7 @@ XGO_VERSION := go-1.25.x
|
|||||||
AIR_PACKAGE ?= github.com/air-verse/air@v1
|
AIR_PACKAGE ?= github.com/air-verse/air@v1
|
||||||
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3
|
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3
|
||||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2
|
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.7.0
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.7.2
|
||||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15
|
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15
|
||||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0
|
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0
|
||||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1
|
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1
|
||||||
@ -339,12 +339,12 @@ lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backen
|
|||||||
|
|
||||||
.PHONY: lint-js
|
.PHONY: lint-js
|
||||||
lint-js: node_modules ## lint js files
|
lint-js: node_modules ## lint js files
|
||||||
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --flag unstable_native_nodejs_ts_config $(ESLINT_FILES)
|
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 $(ESLINT_FILES)
|
||||||
$(NODE_VARS) pnpm exec vue-tsc
|
$(NODE_VARS) pnpm exec vue-tsc
|
||||||
|
|
||||||
.PHONY: lint-js-fix
|
.PHONY: lint-js-fix
|
||||||
lint-js-fix: node_modules ## lint js files and fix issues
|
lint-js-fix: node_modules ## lint js files and fix issues
|
||||||
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --flag unstable_native_nodejs_ts_config $(ESLINT_FILES) --fix
|
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 $(ESLINT_FILES) --fix
|
||||||
$(NODE_VARS) pnpm exec vue-tsc
|
$(NODE_VARS) pnpm exec vue-tsc
|
||||||
|
|
||||||
.PHONY: lint-css
|
.PHONY: lint-css
|
||||||
|
|||||||
@ -215,7 +215,7 @@ export default defineConfig([
|
|||||||
'@typescript-eslint/no-unnecessary-condition': [0],
|
'@typescript-eslint/no-unnecessary-condition': [0],
|
||||||
'@typescript-eslint/no-unnecessary-qualifier': [0],
|
'@typescript-eslint/no-unnecessary-qualifier': [0],
|
||||||
'@typescript-eslint/no-unnecessary-template-expression': [0],
|
'@typescript-eslint/no-unnecessary-template-expression': [0],
|
||||||
'@typescript-eslint/no-unnecessary-type-arguments': [0],
|
'@typescript-eslint/no-unnecessary-type-arguments': [2],
|
||||||
'@typescript-eslint/no-unnecessary-type-assertion': [2],
|
'@typescript-eslint/no-unnecessary-type-assertion': [2],
|
||||||
'@typescript-eslint/no-unnecessary-type-constraint': [2],
|
'@typescript-eslint/no-unnecessary-type-constraint': [2],
|
||||||
'@typescript-eslint/no-unnecessary-type-conversion': [2],
|
'@typescript-eslint/no-unnecessary-type-conversion': [2],
|
||||||
@ -228,11 +228,12 @@ export default defineConfig([
|
|||||||
'@typescript-eslint/no-unsafe-member-access': [0],
|
'@typescript-eslint/no-unsafe-member-access': [0],
|
||||||
'@typescript-eslint/no-unsafe-return': [0],
|
'@typescript-eslint/no-unsafe-return': [0],
|
||||||
'@typescript-eslint/no-unsafe-unary-minus': [2],
|
'@typescript-eslint/no-unsafe-unary-minus': [2],
|
||||||
'@typescript-eslint/no-unused-expressions': [0],
|
'@typescript-eslint/no-unused-expressions': [2],
|
||||||
'@typescript-eslint/no-unused-private-class-members': [2],
|
'@typescript-eslint/no-unused-private-class-members': [2],
|
||||||
'@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'all', caughtErrors: 'all', ignoreRestSiblings: false, argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'}],
|
'@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'all', caughtErrors: 'all', ignoreRestSiblings: false, argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'}],
|
||||||
'@typescript-eslint/no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true, typedefs: false, enums: false, ignoreTypeReferences: true}],
|
'@typescript-eslint/no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true, typedefs: false, enums: false, ignoreTypeReferences: true}],
|
||||||
'@typescript-eslint/no-useless-constructor': [0],
|
'@typescript-eslint/no-useless-constructor': [0],
|
||||||
|
'@typescript-eslint/no-useless-default-assignment': [0], // https://github.com/typescript-eslint/typescript-eslint/issues/11847
|
||||||
'@typescript-eslint/no-useless-empty-export': [0],
|
'@typescript-eslint/no-useless-empty-export': [0],
|
||||||
'@typescript-eslint/no-wrapper-object-types': [2],
|
'@typescript-eslint/no-wrapper-object-types': [2],
|
||||||
'@typescript-eslint/non-nullable-type-assertion-style': [0],
|
'@typescript-eslint/non-nullable-type-assertion-style': [0],
|
||||||
@ -584,7 +585,7 @@ export default defineConfig([
|
|||||||
'no-unreachable': [2],
|
'no-unreachable': [2],
|
||||||
'no-unsafe-finally': [2],
|
'no-unsafe-finally': [2],
|
||||||
'no-unsafe-negation': [2],
|
'no-unsafe-negation': [2],
|
||||||
'no-unused-expressions': [2],
|
'no-unused-expressions': [0], // handled by @typescript-eslint/no-unused-expressions
|
||||||
'no-unused-labels': [2],
|
'no-unused-labels': [2],
|
||||||
'no-unused-private-class-members': [0], // handled by @typescript-eslint/no-unused-private-class-members
|
'no-unused-private-class-members': [0], // handled by @typescript-eslint/no-unused-private-class-members
|
||||||
'no-unused-vars': [0], // handled by @typescript-eslint/no-unused-vars
|
'no-unused-vars': [0], // handled by @typescript-eslint/no-unused-vars
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
@ -169,7 +170,8 @@ func (p *Permission) ReadableUnitTypes() []unit.Type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Permission) LogString() string {
|
func (p *Permission) LogString() string {
|
||||||
format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): ["
|
var format strings.Builder
|
||||||
|
format.WriteString("<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [")
|
||||||
args := []any{p.AccessMode.ToString(), len(p.units), len(p.unitsMode)}
|
args := []any{p.AccessMode.ToString(), len(p.units), len(p.unitsMode)}
|
||||||
|
|
||||||
for i, u := range p.units {
|
for i, u := range p.units {
|
||||||
@ -181,19 +183,19 @@ func (p *Permission) LogString() string {
|
|||||||
config = err.Error()
|
config = err.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
format += "\n\tunits[%d]: ID=%d RepoID=%d Type=%s Config=%s"
|
format.WriteString("\n\tunits[%d]: ID=%d RepoID=%d Type=%s Config=%s")
|
||||||
args = append(args, i, u.ID, u.RepoID, u.Type.LogString(), config)
|
args = append(args, i, u.ID, u.RepoID, u.Type.LogString(), config)
|
||||||
}
|
}
|
||||||
for key, value := range p.unitsMode {
|
for key, value := range p.unitsMode {
|
||||||
format += "\n\tunitsMode[%-v]: %-v"
|
format.WriteString("\n\tunitsMode[%-v]: %-v")
|
||||||
args = append(args, key.LogString(), value.LogString())
|
args = append(args, key.LogString(), value.LogString())
|
||||||
}
|
}
|
||||||
format += "\n\tanonymousAccessMode: %-v"
|
format.WriteString("\n\tanonymousAccessMode: %-v")
|
||||||
args = append(args, p.anonymousAccessMode)
|
args = append(args, p.anonymousAccessMode)
|
||||||
format += "\n\teveryoneAccessMode: %-v"
|
format.WriteString("\n\teveryoneAccessMode: %-v")
|
||||||
args = append(args, p.everyoneAccessMode)
|
args = append(args, p.everyoneAccessMode)
|
||||||
format += "\n\t]>"
|
format.WriteString("\n\t]>")
|
||||||
return fmt.Sprintf(format, args...)
|
return fmt.Sprintf(format.String(), args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyPublicAccessPermission(unitType unit.Type, accessMode perm_model.AccessMode, modeMap *map[unit.Type]perm_model.AccessMode) {
|
func applyPublicAccessPermission(unitType unit.Type, accessMode perm_model.AccessMode, modeMap *map[unit.Type]perm_model.AccessMode) {
|
||||||
|
|||||||
@ -1461,3 +1461,15 @@ func GetUserOrOrgIDByName(ctx context.Context, name string) (int64, error) {
|
|||||||
}
|
}
|
||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserOrOrgByName returns the user or org by name
|
||||||
|
func GetUserOrOrgByName(ctx context.Context, name string) (*User, error) {
|
||||||
|
var u User
|
||||||
|
has, err := db.GetEngine(ctx).Where("lower_name = ?", strings.ToLower(name)).Get(&u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !has {
|
||||||
|
return nil, ErrUserNotExist{Name: name}
|
||||||
|
}
|
||||||
|
return &u, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -75,9 +75,9 @@ func (f Format) Parser(r io.Reader) *Parser {
|
|||||||
// hexEscaped produces hex-escpaed characters from a string. For example, "\n\0"
|
// hexEscaped produces hex-escpaed characters from a string. For example, "\n\0"
|
||||||
// would turn into "%0a%00".
|
// would turn into "%0a%00".
|
||||||
func (f Format) hexEscaped(delim []byte) string {
|
func (f Format) hexEscaped(delim []byte) string {
|
||||||
escaped := ""
|
var escaped strings.Builder
|
||||||
for i := range delim {
|
for i := range delim {
|
||||||
escaped += "%" + hex.EncodeToString([]byte{delim[i]})
|
escaped.WriteString("%" + hex.EncodeToString([]byte{delim[i]}))
|
||||||
}
|
}
|
||||||
return escaped
|
return escaped.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ type Uploader interface {
|
|||||||
CreateMilestones(ctx context.Context, milestones ...*Milestone) error
|
CreateMilestones(ctx context.Context, milestones ...*Milestone) error
|
||||||
CreateReleases(ctx context.Context, releases ...*Release) error
|
CreateReleases(ctx context.Context, releases ...*Release) error
|
||||||
SyncTags(ctx context.Context) error
|
SyncTags(ctx context.Context) error
|
||||||
|
SyncBranches(ctx context.Context) error
|
||||||
CreateLabels(ctx context.Context, labels ...*Label) error
|
CreateLabels(ctx context.Context, labels ...*Label) error
|
||||||
CreateIssues(ctx context.Context, issues ...*Issue) error
|
CreateIssues(ctx context.Context, issues ...*Issue) error
|
||||||
CreateComments(ctx context.Context, comments ...*Comment) error
|
CreateComments(ctx context.Context, comments ...*Comment) error
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
package setting
|
package setting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@ -23,11 +24,11 @@ type OpenWithEditorApp struct {
|
|||||||
type OpenWithEditorAppsType []OpenWithEditorApp
|
type OpenWithEditorAppsType []OpenWithEditorApp
|
||||||
|
|
||||||
func (t OpenWithEditorAppsType) ToTextareaString() string {
|
func (t OpenWithEditorAppsType) ToTextareaString() string {
|
||||||
ret := ""
|
var ret strings.Builder
|
||||||
for _, app := range t {
|
for _, app := range t {
|
||||||
ret += app.DisplayName + " = " + app.OpenURL + "\n"
|
ret.WriteString(app.DisplayName + " = " + app.OpenURL + "\n")
|
||||||
}
|
}
|
||||||
return ret
|
return ret.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultOpenWithEditorApps() OpenWithEditorAppsType {
|
func DefaultOpenWithEditorApps() OpenWithEditorAppsType {
|
||||||
|
|||||||
@ -249,17 +249,18 @@ func (ut *RenderUtils) MarkdownToHtml(input string) template.HTML { //nolint:rev
|
|||||||
func (ut *RenderUtils) RenderLabels(labels []*issues_model.Label, repoLink string, issue *issues_model.Issue) template.HTML {
|
func (ut *RenderUtils) RenderLabels(labels []*issues_model.Label, repoLink string, issue *issues_model.Issue) template.HTML {
|
||||||
isPullRequest := issue != nil && issue.IsPull
|
isPullRequest := issue != nil && issue.IsPull
|
||||||
baseLink := fmt.Sprintf("%s/%s", repoLink, util.Iif(isPullRequest, "pulls", "issues"))
|
baseLink := fmt.Sprintf("%s/%s", repoLink, util.Iif(isPullRequest, "pulls", "issues"))
|
||||||
htmlCode := `<span class="labels-list">`
|
var htmlCode strings.Builder
|
||||||
|
htmlCode.WriteString(`<span class="labels-list">`)
|
||||||
for _, label := range labels {
|
for _, label := range labels {
|
||||||
// Protect against nil value in labels - shouldn't happen but would cause a panic if so
|
// Protect against nil value in labels - shouldn't happen but would cause a panic if so
|
||||||
if label == nil {
|
if label == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
link := fmt.Sprintf("%s?labels=%d", baseLink, label.ID)
|
link := fmt.Sprintf("%s?labels=%d", baseLink, label.ID)
|
||||||
htmlCode += string(ut.RenderLabelWithLink(label, template.URL(link)))
|
htmlCode.WriteString(string(ut.RenderLabelWithLink(label, template.URL(link))))
|
||||||
}
|
}
|
||||||
htmlCode += "</span>"
|
htmlCode.WriteString("</span>")
|
||||||
return template.HTML(htmlCode)
|
return template.HTML(htmlCode.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ut *RenderUtils) RenderThemeItem(info *webtheme.ThemeMetaInfo, iconSize int) template.HTML {
|
func (ut *RenderUtils) RenderThemeItem(info *webtheme.ThemeMetaInfo, iconSize int) template.HTML {
|
||||||
|
|||||||
@ -1863,6 +1863,7 @@ pulls.desc = Enable pull requests and code reviews.
|
|||||||
pulls.new = New Pull Request
|
pulls.new = New Pull Request
|
||||||
pulls.new.blocked_user = Cannot create pull request because you are blocked by the repository owner.
|
pulls.new.blocked_user = Cannot create pull request because you are blocked by the repository owner.
|
||||||
pulls.new.must_collaborator = You must be a collaborator to create pull request.
|
pulls.new.must_collaborator = You must be a collaborator to create pull request.
|
||||||
|
pulls.new.already_existed = A pull request between these branches already exists
|
||||||
pulls.edit.already_changed = Unable to save changes to the pull request. It appears the content has already been changed by another user. Please refresh the page and try editing again to avoid overwriting their changes.
|
pulls.edit.already_changed = Unable to save changes to the pull request. It appears the content has already been changed by another user. Please refresh the page and try editing again to avoid overwriting their changes.
|
||||||
pulls.view = View Pull Request
|
pulls.view = View Pull Request
|
||||||
pulls.compare_changes = New Pull Request
|
pulls.compare_changes = New Pull Request
|
||||||
|
|||||||
35
package.json
35
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@10.24.0",
|
"packageManager": "pnpm@10.26.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 22.6.0",
|
"node": ">= 22.6.0",
|
||||||
"pnpm": ">= 10.0.0"
|
"pnpm": ">= 10.0.0"
|
||||||
@ -12,7 +12,7 @@
|
|||||||
"@citation-js/plugin-software-formats": "0.6.1",
|
"@citation-js/plugin-software-formats": "0.6.1",
|
||||||
"@github/markdown-toolbar-element": "2.2.3",
|
"@github/markdown-toolbar-element": "2.2.3",
|
||||||
"@github/paste-markdown": "1.5.3",
|
"@github/paste-markdown": "1.5.3",
|
||||||
"@github/relative-time-element": "4.5.1",
|
"@github/relative-time-element": "5.0.0",
|
||||||
"@github/text-expander-element": "2.9.2",
|
"@github/text-expander-element": "2.9.2",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
"@primer/octicons": "19.21.1",
|
"@primer/octicons": "19.21.1",
|
||||||
@ -21,7 +21,7 @@
|
|||||||
"@techknowlogick/license-checker-webpack-plugin": "0.3.0",
|
"@techknowlogick/license-checker-webpack-plugin": "0.3.0",
|
||||||
"add-asset-webpack-plugin": "3.1.1",
|
"add-asset-webpack-plugin": "3.1.1",
|
||||||
"ansi_up": "6.0.6",
|
"ansi_up": "6.0.6",
|
||||||
"asciinema-player": "3.12.1",
|
"asciinema-player": "3.13.5",
|
||||||
"chart.js": "4.5.1",
|
"chart.js": "4.5.1",
|
||||||
"chartjs-adapter-dayjs-4": "1.0.4",
|
"chartjs-adapter-dayjs-4": "1.0.4",
|
||||||
"chartjs-plugin-zoom": "2.2.0",
|
"chartjs-plugin-zoom": "2.2.0",
|
||||||
@ -36,32 +36,31 @@
|
|||||||
"htmx.org": "2.0.8",
|
"htmx.org": "2.0.8",
|
||||||
"idiomorph": "0.7.4",
|
"idiomorph": "0.7.4",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"katex": "0.16.25",
|
"katex": "0.16.27",
|
||||||
"mermaid": "11.12.2",
|
"mermaid": "11.12.2",
|
||||||
"mini-css-extract-plugin": "2.9.4",
|
"mini-css-extract-plugin": "2.9.4",
|
||||||
"monaco-editor": "0.55.1",
|
"monaco-editor": "0.55.1",
|
||||||
"monaco-editor-webpack-plugin": "7.1.1",
|
"monaco-editor-webpack-plugin": "7.1.1",
|
||||||
"online-3d-viewer": "0.16.0",
|
"online-3d-viewer": "0.17.0",
|
||||||
"pdfobject": "2.3.1",
|
"pdfobject": "2.3.1",
|
||||||
"perfect-debounce": "2.0.0",
|
"perfect-debounce": "2.0.0",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
"postcss-loader": "8.2.0",
|
"postcss-loader": "8.2.0",
|
||||||
"sortablejs": "1.15.6",
|
"sortablejs": "1.15.6",
|
||||||
"swagger-ui-dist": "5.30.3",
|
"swagger-ui-dist": "5.31.0",
|
||||||
"tailwindcss": "3.4.17",
|
"tailwindcss": "3.4.17",
|
||||||
"throttle-debounce": "5.0.2",
|
"throttle-debounce": "5.0.2",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tippy.js": "6.3.7",
|
"tippy.js": "6.3.7",
|
||||||
"toastify-js": "1.12.0",
|
"toastify-js": "1.12.0",
|
||||||
"tributejs": "5.1.3",
|
"tributejs": "5.1.3",
|
||||||
"typescript": "5.9.3",
|
|
||||||
"uint8-to-base64": "0.2.1",
|
"uint8-to-base64": "0.2.1",
|
||||||
"vanilla-colorful": "0.7.2",
|
"vanilla-colorful": "0.7.2",
|
||||||
"vue": "3.5.25",
|
"vue": "3.5.25",
|
||||||
"vue-bar-graph": "2.2.0",
|
"vue-bar-graph": "2.2.0",
|
||||||
"vue-chartjs": "5.3.3",
|
"vue-chartjs": "5.3.3",
|
||||||
"vue-loader": "17.4.2",
|
"vue-loader": "17.4.2",
|
||||||
"webpack": "5.103.0",
|
"webpack": "5.104.0",
|
||||||
"webpack-cli": "6.0.1",
|
"webpack-cli": "6.0.1",
|
||||||
"wrap-ansi": "9.0.2"
|
"wrap-ansi": "9.0.2"
|
||||||
},
|
},
|
||||||
@ -80,10 +79,10 @@
|
|||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/toastify-js": "1.12.4",
|
"@types/toastify-js": "1.12.4",
|
||||||
"@typescript-eslint/parser": "8.48.1",
|
"@typescript-eslint/parser": "8.50.0",
|
||||||
"@vitejs/plugin-vue": "6.0.2",
|
"@vitejs/plugin-vue": "6.0.3",
|
||||||
"@vitest/eslint-plugin": "1.5.1",
|
"@vitest/eslint-plugin": "1.5.2",
|
||||||
"eslint": "9.39.1",
|
"eslint": "9.39.2",
|
||||||
"eslint-import-resolver-typescript": "4.4.4",
|
"eslint-import-resolver-typescript": "4.4.4",
|
||||||
"eslint-plugin-array-func": "5.1.0",
|
"eslint-plugin-array-func": "5.1.0",
|
||||||
"eslint-plugin-github": "6.0.0",
|
"eslint-plugin-github": "6.0.0",
|
||||||
@ -97,7 +96,8 @@
|
|||||||
"eslint-plugin-wc": "3.0.2",
|
"eslint-plugin-wc": "3.0.2",
|
||||||
"globals": "16.5.0",
|
"globals": "16.5.0",
|
||||||
"happy-dom": "20.0.11",
|
"happy-dom": "20.0.11",
|
||||||
"markdownlint-cli": "0.46.0",
|
"jiti": "2.6.1",
|
||||||
|
"markdownlint-cli": "0.47.0",
|
||||||
"material-icon-theme": "5.29.0",
|
"material-icon-theme": "5.29.0",
|
||||||
"nolyfill": "1.0.44",
|
"nolyfill": "1.0.44",
|
||||||
"postcss-html": "1.8.0",
|
"postcss-html": "1.8.0",
|
||||||
@ -108,11 +108,12 @@
|
|||||||
"stylelint-declaration-strict-value": "1.10.11",
|
"stylelint-declaration-strict-value": "1.10.11",
|
||||||
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
||||||
"svgo": "4.0.0",
|
"svgo": "4.0.0",
|
||||||
"typescript-eslint": "8.48.1",
|
"typescript": "5.9.3",
|
||||||
"updates": "17.0.4",
|
"typescript-eslint": "8.50.0",
|
||||||
|
"updates": "17.0.7",
|
||||||
"vite-string-plugin": "1.4.9",
|
"vite-string-plugin": "1.4.9",
|
||||||
"vitest": "4.0.15",
|
"vitest": "4.0.16",
|
||||||
"vue-tsc": "3.1.5"
|
"vue-tsc": "3.1.8"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"defaults"
|
"defaults"
|
||||||
|
|||||||
1345
pnpm-lock.yaml
generated
1345
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -433,15 +433,16 @@ func makePackageVersionDependency(ctx *context.Context, version *packages_model.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion, c *cache.EphemeralCache) (string, error) {
|
func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion, c *cache.EphemeralCache) (string, error) {
|
||||||
ret := "---\n"
|
var ret strings.Builder
|
||||||
|
ret.WriteString("---\n")
|
||||||
for _, v := range versions {
|
for _, v := range versions {
|
||||||
dep, err := makePackageVersionDependency(ctx, v, c)
|
dep, err := makePackageVersionDependency(ctx, v, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
ret += dep + "\n"
|
ret.WriteString(dep + "\n")
|
||||||
}
|
}
|
||||||
return ret, nil
|
return ret.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeGemFullFileName(gemName, version, platform string) string {
|
func makeGemFullFileName(gemName, version, platform string) string {
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
|
"code.gitea.io/gitea/routers/common"
|
||||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||||
"code.gitea.io/gitea/services/automerge"
|
"code.gitea.io/gitea/services/automerge"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
@ -1082,7 +1083,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||||||
} else if len(headInfos) == 2 {
|
} else if len(headInfos) == 2 {
|
||||||
// There is a head repository (the head repository could also be the same base repo)
|
// There is a head repository (the head repository could also be the same base repo)
|
||||||
headRefToGuess = headInfos[1]
|
headRefToGuess = headInfos[1]
|
||||||
headUser, err = user_model.GetUserByName(ctx, headInfos[0])
|
headUser, err = user_model.GetUserOrOrgByName(ctx, headInfos[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if user_model.IsErrUserNotExist(err) {
|
if user_model.IsErrUserNotExist(err) {
|
||||||
ctx.APIErrorNotFound("GetUserByName")
|
ctx.APIErrorNotFound("GetUserByName")
|
||||||
@ -1098,28 +1099,23 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||||||
|
|
||||||
isSameRepo := ctx.Repo.Owner.ID == headUser.ID
|
isSameRepo := ctx.Repo.Owner.ID == headUser.ID
|
||||||
|
|
||||||
// Check if current user has fork of repository or in the same repository.
|
var headRepo *repo_model.Repository
|
||||||
headRepo := repo_model.GetForkedRepo(ctx, headUser.ID, baseRepo.ID)
|
if isSameRepo {
|
||||||
if headRepo == nil && !isSameRepo {
|
headRepo = baseRepo
|
||||||
err = baseRepo.GetBaseRepo(ctx)
|
} else {
|
||||||
|
headRepo, err = common.FindHeadRepo(ctx, baseRepo, headUser.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
if headRepo == nil {
|
||||||
// Check if baseRepo's base repository is the same as headUser's repository.
|
ctx.APIErrorNotFound("head repository not found")
|
||||||
if baseRepo.BaseRepo == nil || baseRepo.BaseRepo.OwnerID != headUser.ID {
|
|
||||||
log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
|
|
||||||
ctx.APIErrorNotFound("GetBaseRepo")
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
// Assign headRepo so it can be used below.
|
|
||||||
headRepo = baseRepo.BaseRepo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var headGitRepo *git.Repository
|
var headGitRepo *git.Repository
|
||||||
if isSameRepo {
|
if isSameRepo {
|
||||||
headRepo = ctx.Repo.Repository
|
|
||||||
headGitRepo = ctx.Repo.GitRepo
|
headGitRepo = ctx.Repo.GitRepo
|
||||||
closer = func() {} // no need to close the head repo because it shares the base repo
|
closer = func() {} // no need to close the head repo because it shares the base repo
|
||||||
} else {
|
} else {
|
||||||
@ -1143,7 +1139,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(unit.TypeCode) {
|
if !permBase.CanRead(unit.TypeCode) {
|
||||||
log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v", ctx.Doer, baseRepo, permBase)
|
log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v", ctx.Doer, baseRepo, permBase)
|
||||||
ctx.APIErrorNotFound("Can't read pulls or can't read UnitTypeCode")
|
ctx.APIErrorNotFound("Can't read pulls or can't read UnitTypeCode")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
@ -20,3 +22,54 @@ type CompareInfo struct {
|
|||||||
HeadBranch string
|
HeadBranch string
|
||||||
DirectComparison bool
|
DirectComparison bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maxForkTraverseLevel defines the maximum levels to traverse when searching for the head repository.
|
||||||
|
const maxForkTraverseLevel = 10
|
||||||
|
|
||||||
|
// FindHeadRepo tries to find the head repository based on the base repository and head user ID.
|
||||||
|
func FindHeadRepo(ctx context.Context, baseRepo *repo_model.Repository, headUserID int64) (*repo_model.Repository, error) {
|
||||||
|
if baseRepo.IsFork {
|
||||||
|
curRepo := baseRepo
|
||||||
|
for curRepo.OwnerID != headUserID { // We assume the fork deepth is not too deep.
|
||||||
|
if err := curRepo.GetBaseRepo(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if curRepo.BaseRepo == nil {
|
||||||
|
return findHeadRepoFromRootBase(ctx, curRepo, headUserID, maxForkTraverseLevel)
|
||||||
|
}
|
||||||
|
curRepo = curRepo.BaseRepo
|
||||||
|
}
|
||||||
|
return curRepo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return findHeadRepoFromRootBase(ctx, baseRepo, headUserID, maxForkTraverseLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findHeadRepoFromRootBase(ctx context.Context, baseRepo *repo_model.Repository, headUserID int64, traverseLevel int) (*repo_model.Repository, error) {
|
||||||
|
if traverseLevel == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// test if we are lucky
|
||||||
|
repo, err := repo_model.GetUserFork(ctx, baseRepo.ID, headUserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if repo != nil {
|
||||||
|
return repo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
firstLevelForkedRepos, err := repo_model.GetRepositoriesByForkID(ctx, baseRepo.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, repo := range firstLevelForkedRepos {
|
||||||
|
forked, err := findHeadRepoFromRootBase(ctx, repo, headUserID, traverseLevel-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if forked != nil {
|
||||||
|
return forked, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -179,11 +179,11 @@ func AuthorizeOAuth(ctx *context.Context) {
|
|||||||
errs := binding.Errors{}
|
errs := binding.Errors{}
|
||||||
errs = form.Validate(ctx.Req, errs)
|
errs = form.Validate(ctx.Req, errs)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
errstring := ""
|
var errstring strings.Builder
|
||||||
for _, e := range errs {
|
for _, e := range errs {
|
||||||
errstring += e.Error() + "\n"
|
errstring.WriteString(e.Error() + "\n")
|
||||||
}
|
}
|
||||||
ctx.ServerError("AuthorizeOAuth: Validate: ", fmt.Errorf("errors occurred during validation: %s", errstring))
|
ctx.ServerError("AuthorizeOAuth: Validate: ", fmt.Errorf("errors occurred during validation: %s", errstring.String()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -259,7 +259,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo {
|
|||||||
} else if len(headInfos) == 2 {
|
} else if len(headInfos) == 2 {
|
||||||
headInfosSplit := strings.Split(headInfos[0], "/")
|
headInfosSplit := strings.Split(headInfos[0], "/")
|
||||||
if len(headInfosSplit) == 1 {
|
if len(headInfosSplit) == 1 {
|
||||||
ci.HeadUser, err = user_model.GetUserByName(ctx, headInfos[0])
|
ci.HeadUser, err = user_model.GetUserOrOrgByName(ctx, headInfos[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if user_model.IsErrUserNotExist(err) {
|
if user_model.IsErrUserNotExist(err) {
|
||||||
ctx.NotFound(nil)
|
ctx.NotFound(nil)
|
||||||
|
|||||||
@ -495,7 +495,7 @@ func preparePullViewSigning(ctx *context.Context, issue *issues_model.Issue) {
|
|||||||
pull := issue.PullRequest
|
pull := issue.PullRequest
|
||||||
ctx.Data["WillSign"] = false
|
ctx.Data["WillSign"] = false
|
||||||
if ctx.Doer != nil {
|
if ctx.Doer != nil {
|
||||||
sign, key, _, err := asymkey_service.SignMerge(ctx, pull, ctx.Doer, pull.BaseRepo.RepoPath(), pull.BaseBranch, pull.GetGitHeadRefName())
|
sign, key, _, err := asymkey_service.SignMerge(ctx, pull, ctx.Doer, ctx.Repo.GitRepo, pull.BaseBranch, pull.GetGitHeadRefName())
|
||||||
ctx.Data["WillSign"] = sign
|
ctx.Data["WillSign"] = sign
|
||||||
ctx.Data["SigningKeyMergeDisplay"] = asymkey_model.GetDisplaySigningKey(key)
|
ctx.Data["SigningKeyMergeDisplay"] = asymkey_model.GetDisplaySigningKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -1340,6 +1340,17 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if a pull request already exists with the same head and base branch.
|
||||||
|
pr, err := issues_model.GetUnmergedPullRequest(ctx, ci.HeadRepo.ID, repo.ID, ci.HeadBranch, ci.BaseBranch, issues_model.PullRequestFlowGithub)
|
||||||
|
if err != nil && !issues_model.IsErrPullRequestNotExist(err) {
|
||||||
|
ctx.ServerError("GetUnmergedPullRequest", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pr != nil {
|
||||||
|
ctx.JSONError(ctx.Tr("repo.pulls.new.already_existed"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
content := form.Content
|
content := form.Content
|
||||||
if filename := ctx.Req.Form.Get("template-file"); filename != "" {
|
if filename := ctx.Req.Form.Get("template-file"); filename != "" {
|
||||||
if template, err := issue_template.UnmarshalFromRepo(ctx.Repo.GitRepo, ctx.Repo.Repository.DefaultBranch, filename); err == nil {
|
if template, err := issue_template.UnmarshalFromRepo(ctx.Repo.GitRepo, ctx.Repo.Repository.DefaultBranch, filename); err == nil {
|
||||||
|
|||||||
@ -169,7 +169,7 @@ Loop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SignWikiCommit determines if we should sign the commits to this repository wiki
|
// SignWikiCommit determines if we should sign the commits to this repository wiki
|
||||||
func SignWikiCommit(ctx context.Context, repo *repo_model.Repository, u *user_model.User) (bool, *git.SigningKey, *git.Signature, error) {
|
func SignWikiCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, u *user_model.User) (bool, *git.SigningKey, *git.Signature, error) {
|
||||||
rules := signingModeFromStrings(setting.Repository.Signing.Wiki)
|
rules := signingModeFromStrings(setting.Repository.Signing.Wiki)
|
||||||
signingKey, sig := gitrepo.GetSigningKey(ctx)
|
signingKey, sig := gitrepo.GetSigningKey(ctx)
|
||||||
if signingKey == nil {
|
if signingKey == nil {
|
||||||
@ -200,11 +200,6 @@ Loop:
|
|||||||
return false, nil, nil, &ErrWontSign{twofa}
|
return false, nil, nil, &ErrWontSign{twofa}
|
||||||
}
|
}
|
||||||
case parentSigned:
|
case parentSigned:
|
||||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo.WikiStorageRepo())
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, nil, err
|
|
||||||
}
|
|
||||||
defer gitRepo.Close()
|
|
||||||
commit, err := gitRepo.GetCommit("HEAD")
|
commit, err := gitRepo.GetCommit("HEAD")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, nil, err
|
return false, nil, nil, err
|
||||||
@ -222,7 +217,7 @@ Loop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SignCRUDAction determines if we should sign a CRUD commit to this repository
|
// SignCRUDAction determines if we should sign a CRUD commit to this repository
|
||||||
func SignCRUDAction(ctx context.Context, u *user_model.User, tmpBasePath, parentCommit string) (bool, *git.SigningKey, *git.Signature, error) {
|
func SignCRUDAction(ctx context.Context, u *user_model.User, gitRepo *git.Repository, parentCommit string) (bool, *git.SigningKey, *git.Signature, error) {
|
||||||
rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions)
|
rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions)
|
||||||
signingKey, sig := git.GetSigningKey(ctx)
|
signingKey, sig := git.GetSigningKey(ctx)
|
||||||
if signingKey == nil {
|
if signingKey == nil {
|
||||||
@ -253,11 +248,6 @@ Loop:
|
|||||||
return false, nil, nil, &ErrWontSign{twofa}
|
return false, nil, nil, &ErrWontSign{twofa}
|
||||||
}
|
}
|
||||||
case parentSigned:
|
case parentSigned:
|
||||||
gitRepo, err := git.OpenRepository(ctx, tmpBasePath)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, nil, err
|
|
||||||
}
|
|
||||||
defer gitRepo.Close()
|
|
||||||
isEmpty, err := gitRepo.IsEmpty()
|
isEmpty, err := gitRepo.IsEmpty()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, nil, err
|
return false, nil, nil, err
|
||||||
@ -281,7 +271,7 @@ Loop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SignMerge determines if we should sign a PR merge commit to the base repository
|
// SignMerge determines if we should sign a PR merge commit to the base repository
|
||||||
func SignMerge(ctx context.Context, pr *issues_model.PullRequest, u *user_model.User, tmpBasePath, baseCommit, headCommit string) (bool, *git.SigningKey, *git.Signature, error) {
|
func SignMerge(ctx context.Context, pr *issues_model.PullRequest, u *user_model.User, gitRepo *git.Repository, baseCommit, headCommit string) (bool, *git.SigningKey, *git.Signature, error) {
|
||||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
if err := pr.LoadBaseRepo(ctx); err != nil {
|
||||||
log.Error("Unable to get Base Repo for pull request")
|
log.Error("Unable to get Base Repo for pull request")
|
||||||
return false, nil, nil, err
|
return false, nil, nil, err
|
||||||
@ -294,9 +284,6 @@ func SignMerge(ctx context.Context, pr *issues_model.PullRequest, u *user_model.
|
|||||||
}
|
}
|
||||||
rules := signingModeFromStrings(setting.Repository.Signing.Merges)
|
rules := signingModeFromStrings(setting.Repository.Signing.Merges)
|
||||||
|
|
||||||
var gitRepo *git.Repository
|
|
||||||
var err error
|
|
||||||
|
|
||||||
Loop:
|
Loop:
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
switch rule {
|
switch rule {
|
||||||
@ -332,13 +319,6 @@ Loop:
|
|||||||
return false, nil, nil, &ErrWontSign{approved}
|
return false, nil, nil, &ErrWontSign{approved}
|
||||||
}
|
}
|
||||||
case baseSigned:
|
case baseSigned:
|
||||||
if gitRepo == nil {
|
|
||||||
gitRepo, err = git.OpenRepository(ctx, tmpBasePath)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, nil, err
|
|
||||||
}
|
|
||||||
defer gitRepo.Close()
|
|
||||||
}
|
|
||||||
commit, err := gitRepo.GetCommit(baseCommit)
|
commit, err := gitRepo.GetCommit(baseCommit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, nil, err
|
return false, nil, nil, err
|
||||||
@ -348,13 +328,6 @@ Loop:
|
|||||||
return false, nil, nil, &ErrWontSign{baseSigned}
|
return false, nil, nil, &ErrWontSign{baseSigned}
|
||||||
}
|
}
|
||||||
case headSigned:
|
case headSigned:
|
||||||
if gitRepo == nil {
|
|
||||||
gitRepo, err = git.OpenRepository(ctx, tmpBasePath)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, nil, err
|
|
||||||
}
|
|
||||||
defer gitRepo.Close()
|
|
||||||
}
|
|
||||||
commit, err := gitRepo.GetCommit(headCommit)
|
commit, err := gitRepo.GetCommit(headCommit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, nil, err
|
return false, nil, nil, err
|
||||||
@ -364,13 +337,6 @@ Loop:
|
|||||||
return false, nil, nil, &ErrWontSign{headSigned}
|
return false, nil, nil, &ErrWontSign{headSigned}
|
||||||
}
|
}
|
||||||
case commitsSigned:
|
case commitsSigned:
|
||||||
if gitRepo == nil {
|
|
||||||
gitRepo, err = git.OpenRepository(ctx, tmpBasePath)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, nil, err
|
|
||||||
}
|
|
||||||
defer gitRepo.Close()
|
|
||||||
}
|
|
||||||
commit, err := gitRepo.GetCommit(headCommit)
|
commit, err := gitRepo.GetCommit(headCommit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, nil, err
|
return false, nil, nil, err
|
||||||
|
|||||||
@ -140,7 +140,13 @@ func PrepareCommitFormOptions(ctx *Context, doer *user_model.User, targetRepo *r
|
|||||||
protectionRequireSigned = protectedBranch.RequireSignedCommits
|
protectionRequireSigned = protectedBranch.RequireSignedCommits
|
||||||
}
|
}
|
||||||
|
|
||||||
willSign, signKey, _, err := asymkey_service.SignCRUDAction(ctx, doer, targetRepo.RepoPath(), refName.String())
|
targetGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, targetRepo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
willSign, signKey, _, err := asymkey_service.SignCRUDAction(ctx, doer, targetGitRepo, refName.String())
|
||||||
wontSignReason := ""
|
wontSignReason := ""
|
||||||
if asymkey_service.IsErrWontSign(err) {
|
if asymkey_service.IsErrWontSign(err) {
|
||||||
wontSignReason = string(err.(*asymkey_service.ErrWontSign).Reason)
|
wontSignReason = string(err.(*asymkey_service.ErrWontSign).Reason)
|
||||||
|
|||||||
@ -358,6 +358,11 @@ func (g *RepositoryDumper) SyncTags(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SyncBranches syncs branches in the database
|
||||||
|
func (g *RepositoryDumper) SyncBranches(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateIssues creates issues
|
// CreateIssues creates issues
|
||||||
func (g *RepositoryDumper) CreateIssues(_ context.Context, issues ...*base.Issue) error {
|
func (g *RepositoryDumper) CreateIssues(_ context.Context, issues ...*base.Issue) error {
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@ -368,6 +368,11 @@ func (g *GiteaLocalUploader) SyncTags(ctx context.Context) error {
|
|||||||
return repo_module.SyncReleasesWithTags(ctx, g.repo, g.gitRepo)
|
return repo_module.SyncReleasesWithTags(ctx, g.repo, g.gitRepo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *GiteaLocalUploader) SyncBranches(ctx context.Context) error {
|
||||||
|
_, err := repo_module.SyncRepoBranchesWithRepo(ctx, g.repo, g.gitRepo, g.doer.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// CreateIssues creates issues
|
// CreateIssues creates issues
|
||||||
func (g *GiteaLocalUploader) CreateIssues(ctx context.Context, issues ...*base.Issue) error {
|
func (g *GiteaLocalUploader) CreateIssues(ctx context.Context, issues ...*base.Issue) error {
|
||||||
iss := make([]*issues_model.Issue, 0, len(issues))
|
iss := make([]*issues_model.Issue, 0, len(issues))
|
||||||
|
|||||||
@ -5,9 +5,6 @@
|
|||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -17,15 +14,10 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
base "code.gitea.io/gitea/modules/migration"
|
base "code.gitea.io/gitea/modules/migration"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/test"
|
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -228,297 +220,3 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, linkedUser.ID, target.GetUserID())
|
assert.Equal(t, linkedUser.ID, target.GetUserID())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
|
|
||||||
unittest.PrepareTestEnv(t)
|
|
||||||
|
|
||||||
//
|
|
||||||
// fromRepo master
|
|
||||||
//
|
|
||||||
fromRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
||||||
baseRef := "master"
|
|
||||||
// this is very different from the real situation. It should be a bare repository for all the Gitea managed repositories
|
|
||||||
assert.NoError(t, git.InitRepository(t.Context(), fromRepo.RepoPath(), false, fromRepo.ObjectFormatName))
|
|
||||||
err := gitrepo.RunCmd(t.Context(), fromRepo, gitcmd.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte("# Testing Repository\n\nOriginally created in: "+fromRepo.RepoPath()), 0o644))
|
|
||||||
assert.NoError(t, git.AddChanges(t.Context(), fromRepo.RepoPath(), true))
|
|
||||||
signature := git.Signature{
|
|
||||||
Email: "test@example.com",
|
|
||||||
Name: "test",
|
|
||||||
When: time.Now(),
|
|
||||||
}
|
|
||||||
assert.NoError(t, git.CommitChanges(t.Context(), fromRepo.RepoPath(), git.CommitChangesOptions{
|
|
||||||
Committer: &signature,
|
|
||||||
Author: &signature,
|
|
||||||
Message: "Initial Commit",
|
|
||||||
}))
|
|
||||||
fromGitRepo, err := gitrepo.OpenRepository(t.Context(), fromRepo)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer fromGitRepo.Close()
|
|
||||||
baseSHA, err := fromGitRepo.GetBranchCommitID(baseRef)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
//
|
|
||||||
// fromRepo branch1
|
|
||||||
//
|
|
||||||
headRef := "branch1"
|
|
||||||
_, err = gitrepo.RunCmdString(t.Context(), fromRepo, gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(headRef))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte("SOMETHING"), 0o644))
|
|
||||||
assert.NoError(t, git.AddChanges(t.Context(), fromRepo.RepoPath(), true))
|
|
||||||
signature.When = time.Now()
|
|
||||||
assert.NoError(t, git.CommitChanges(t.Context(), fromRepo.RepoPath(), git.CommitChangesOptions{
|
|
||||||
Committer: &signature,
|
|
||||||
Author: &signature,
|
|
||||||
Message: "Pull request",
|
|
||||||
}))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
headSHA, err := fromGitRepo.GetBranchCommitID(headRef)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
fromRepoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: fromRepo.OwnerID})
|
|
||||||
|
|
||||||
//
|
|
||||||
// forkRepo branch2
|
|
||||||
//
|
|
||||||
forkHeadRef := "branch2"
|
|
||||||
forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 8})
|
|
||||||
assert.NoError(t, git.Clone(t.Context(), fromRepo.RepoPath(), forkRepo.RepoPath(), git.CloneRepoOptions{
|
|
||||||
Branch: headRef,
|
|
||||||
}))
|
|
||||||
_, err = gitrepo.RunCmdString(t.Context(), forkRepo, gitcmd.NewCommand("checkout", "-b").AddDynamicArguments(forkHeadRef))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NoError(t, os.WriteFile(filepath.Join(forkRepo.RepoPath(), "README.md"), []byte("# branch2 "+forkRepo.RepoPath()), 0o644))
|
|
||||||
assert.NoError(t, git.AddChanges(t.Context(), forkRepo.RepoPath(), true))
|
|
||||||
assert.NoError(t, git.CommitChanges(t.Context(), forkRepo.RepoPath(), git.CommitChangesOptions{
|
|
||||||
Committer: &signature,
|
|
||||||
Author: &signature,
|
|
||||||
Message: "branch2 commit",
|
|
||||||
}))
|
|
||||||
forkGitRepo, err := gitrepo.OpenRepository(t.Context(), forkRepo)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer forkGitRepo.Close()
|
|
||||||
forkHeadSHA, err := forkGitRepo.GetBranchCommitID(forkHeadRef)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
toRepoName := "migrated"
|
|
||||||
ctx := t.Context()
|
|
||||||
uploader := NewGiteaLocalUploader(ctx, fromRepoOwner, fromRepoOwner.Name, toRepoName)
|
|
||||||
uploader.gitServiceType = structs.GiteaService
|
|
||||||
|
|
||||||
assert.NoError(t, repo_service.Init(t.Context()))
|
|
||||||
assert.NoError(t, uploader.CreateRepo(ctx, &base.Repository{
|
|
||||||
Description: "description",
|
|
||||||
OriginalURL: fromRepo.RepoPath(),
|
|
||||||
CloneURL: fromRepo.RepoPath(),
|
|
||||||
IsPrivate: false,
|
|
||||||
IsMirror: true,
|
|
||||||
}, base.MigrateOptions{
|
|
||||||
GitServiceType: structs.GiteaService,
|
|
||||||
Private: false,
|
|
||||||
Mirror: true,
|
|
||||||
}))
|
|
||||||
|
|
||||||
for _, testCase := range []struct {
|
|
||||||
name string
|
|
||||||
head string
|
|
||||||
logFilter []string
|
|
||||||
logFiltered []bool
|
|
||||||
pr base.PullRequest
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "fork, good Head.SHA",
|
|
||||||
head: fmt.Sprintf("%s/%s", forkRepo.OwnerName, forkHeadRef),
|
|
||||||
pr: base.PullRequest{
|
|
||||||
PatchURL: "",
|
|
||||||
Number: 1,
|
|
||||||
State: "open",
|
|
||||||
Base: base.PullRequestBranch{
|
|
||||||
CloneURL: fromRepo.RepoPath(),
|
|
||||||
Ref: baseRef,
|
|
||||||
SHA: baseSHA,
|
|
||||||
RepoName: fromRepo.Name,
|
|
||||||
OwnerName: fromRepo.OwnerName,
|
|
||||||
},
|
|
||||||
Head: base.PullRequestBranch{
|
|
||||||
CloneURL: forkRepo.RepoPath(),
|
|
||||||
Ref: forkHeadRef,
|
|
||||||
SHA: forkHeadSHA,
|
|
||||||
RepoName: forkRepo.Name,
|
|
||||||
OwnerName: forkRepo.OwnerName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "fork, invalid Head.Ref",
|
|
||||||
head: "unknown repository",
|
|
||||||
pr: base.PullRequest{
|
|
||||||
PatchURL: "",
|
|
||||||
Number: 1,
|
|
||||||
State: "open",
|
|
||||||
Base: base.PullRequestBranch{
|
|
||||||
CloneURL: fromRepo.RepoPath(),
|
|
||||||
Ref: baseRef,
|
|
||||||
SHA: baseSHA,
|
|
||||||
RepoName: fromRepo.Name,
|
|
||||||
OwnerName: fromRepo.OwnerName,
|
|
||||||
},
|
|
||||||
Head: base.PullRequestBranch{
|
|
||||||
CloneURL: forkRepo.RepoPath(),
|
|
||||||
Ref: "INVALID",
|
|
||||||
SHA: forkHeadSHA,
|
|
||||||
RepoName: forkRepo.Name,
|
|
||||||
OwnerName: forkRepo.OwnerName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logFilter: []string{"Fetch branch from"},
|
|
||||||
logFiltered: []bool{true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid fork CloneURL",
|
|
||||||
head: "unknown repository",
|
|
||||||
pr: base.PullRequest{
|
|
||||||
PatchURL: "",
|
|
||||||
Number: 1,
|
|
||||||
State: "open",
|
|
||||||
Base: base.PullRequestBranch{
|
|
||||||
CloneURL: fromRepo.RepoPath(),
|
|
||||||
Ref: baseRef,
|
|
||||||
SHA: baseSHA,
|
|
||||||
RepoName: fromRepo.Name,
|
|
||||||
OwnerName: fromRepo.OwnerName,
|
|
||||||
},
|
|
||||||
Head: base.PullRequestBranch{
|
|
||||||
CloneURL: "UNLIKELY",
|
|
||||||
Ref: forkHeadRef,
|
|
||||||
SHA: forkHeadSHA,
|
|
||||||
RepoName: forkRepo.Name,
|
|
||||||
OwnerName: "WRONG",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logFilter: []string{"AddRemote"},
|
|
||||||
logFiltered: []bool{true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no fork, good Head.SHA",
|
|
||||||
head: headRef,
|
|
||||||
pr: base.PullRequest{
|
|
||||||
PatchURL: "",
|
|
||||||
Number: 1,
|
|
||||||
State: "open",
|
|
||||||
Base: base.PullRequestBranch{
|
|
||||||
CloneURL: fromRepo.RepoPath(),
|
|
||||||
Ref: baseRef,
|
|
||||||
SHA: baseSHA,
|
|
||||||
RepoName: fromRepo.Name,
|
|
||||||
OwnerName: fromRepo.OwnerName,
|
|
||||||
},
|
|
||||||
Head: base.PullRequestBranch{
|
|
||||||
CloneURL: fromRepo.RepoPath(),
|
|
||||||
Ref: headRef,
|
|
||||||
SHA: headSHA,
|
|
||||||
RepoName: fromRepo.Name,
|
|
||||||
OwnerName: fromRepo.OwnerName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no fork, empty Head.SHA",
|
|
||||||
head: headRef,
|
|
||||||
pr: base.PullRequest{
|
|
||||||
PatchURL: "",
|
|
||||||
Number: 1,
|
|
||||||
State: "open",
|
|
||||||
Base: base.PullRequestBranch{
|
|
||||||
CloneURL: fromRepo.RepoPath(),
|
|
||||||
Ref: baseRef,
|
|
||||||
SHA: baseSHA,
|
|
||||||
RepoName: fromRepo.Name,
|
|
||||||
OwnerName: fromRepo.OwnerName,
|
|
||||||
},
|
|
||||||
Head: base.PullRequestBranch{
|
|
||||||
CloneURL: fromRepo.RepoPath(),
|
|
||||||
Ref: headRef,
|
|
||||||
SHA: "",
|
|
||||||
RepoName: fromRepo.Name,
|
|
||||||
OwnerName: fromRepo.OwnerName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logFilter: []string{"Empty reference", "Cannot remove local head"},
|
|
||||||
logFiltered: []bool{true, false},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no fork, invalid Head.SHA",
|
|
||||||
head: headRef,
|
|
||||||
pr: base.PullRequest{
|
|
||||||
PatchURL: "",
|
|
||||||
Number: 1,
|
|
||||||
State: "open",
|
|
||||||
Base: base.PullRequestBranch{
|
|
||||||
CloneURL: fromRepo.RepoPath(),
|
|
||||||
Ref: baseRef,
|
|
||||||
SHA: baseSHA,
|
|
||||||
RepoName: fromRepo.Name,
|
|
||||||
OwnerName: fromRepo.OwnerName,
|
|
||||||
},
|
|
||||||
Head: base.PullRequestBranch{
|
|
||||||
CloneURL: fromRepo.RepoPath(),
|
|
||||||
Ref: headRef,
|
|
||||||
SHA: "brokenSHA",
|
|
||||||
RepoName: fromRepo.Name,
|
|
||||||
OwnerName: fromRepo.OwnerName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logFilter: []string{"Deprecated local head"},
|
|
||||||
logFiltered: []bool{true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no fork, not found Head.SHA",
|
|
||||||
head: headRef,
|
|
||||||
pr: base.PullRequest{
|
|
||||||
PatchURL: "",
|
|
||||||
Number: 1,
|
|
||||||
State: "open",
|
|
||||||
Base: base.PullRequestBranch{
|
|
||||||
CloneURL: fromRepo.RepoPath(),
|
|
||||||
Ref: baseRef,
|
|
||||||
SHA: baseSHA,
|
|
||||||
RepoName: fromRepo.Name,
|
|
||||||
OwnerName: fromRepo.OwnerName,
|
|
||||||
},
|
|
||||||
Head: base.PullRequestBranch{
|
|
||||||
CloneURL: fromRepo.RepoPath(),
|
|
||||||
Ref: headRef,
|
|
||||||
SHA: "2697b352310fcd01cbd1f3dbd43b894080027f68",
|
|
||||||
RepoName: fromRepo.Name,
|
|
||||||
OwnerName: fromRepo.OwnerName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logFilter: []string{"Deprecated local head", "Cannot remove local head"},
|
|
||||||
logFiltered: []bool{true, false},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
stopMark := fmt.Sprintf(">>>>>>>>>>>>>STOP: %s<<<<<<<<<<<<<<<", testCase.name)
|
|
||||||
|
|
||||||
logChecker, cleanup := test.NewLogChecker(log.DEFAULT)
|
|
||||||
logChecker.Filter(testCase.logFilter...).StopMark(stopMark)
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
testCase.pr.EnsuredSafe = true
|
|
||||||
|
|
||||||
head, err := uploader.updateGitForPullRequest(ctx, &testCase.pr)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, testCase.head, head)
|
|
||||||
|
|
||||||
log.Info(stopMark)
|
|
||||||
|
|
||||||
logFiltered, logStopped := logChecker.Check(5 * time.Second)
|
|
||||||
assert.True(t, logStopped)
|
|
||||||
if len(testCase.logFilter) > 0 {
|
|
||||||
assert.Equal(t, testCase.logFiltered, logFiltered, "for log message filters: %v", testCase.logFilter)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -478,6 +478,15 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(mapInsertedPRIndexes) > 0 {
|
||||||
|
// The pull requests migrating process may created head branches in the base repository
|
||||||
|
// because head repository maybe a fork one which will not be migrated. So that we need
|
||||||
|
// to sync branches again.
|
||||||
|
log.Trace("syncing branches after migrating pull requests")
|
||||||
|
if err = uploader.SyncBranches(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Comments && supportAllComments {
|
if opts.Comments && supportAllComments {
|
||||||
|
|||||||
@ -232,7 +232,13 @@ func isSignedIfRequired(ctx context.Context, pr *issues_model.PullRequest, doer
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sign, _, _, err := asymkey_service.SignMerge(ctx, pr, doer, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitHeadRefName())
|
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.BaseRepo)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
sign, _, _, err := asymkey_service.SignMerge(ctx, pr, doer, gitRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
||||||
|
|
||||||
return sign, err
|
return sign, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,7 +84,7 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
log.Error("failed to get sha of head branch in %-v: show-ref[%s] --hash refs/heads/tracking: %v", mergeCtx.pr, mergeCtx.tmpBasePath, err)
|
log.Error("failed to get sha of head branch in %-v: show-ref[%s] --hash refs/heads/tracking: %v", mergeCtx.pr, mergeCtx.tmpBasePath, err)
|
||||||
return nil, nil, fmt.Errorf("unable to get sha of head branch in %v %w", pr, err)
|
return nil, nil, fmt.Errorf("unable to get sha of head branch in pr[%d]: %w", pr.ID, err)
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(trackingCommitID) != expectedHeadCommitID {
|
if strings.TrimSpace(trackingCommitID) != expectedHeadCommitID {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -105,8 +105,15 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque
|
|||||||
mergeCtx.sig = doer.NewGitSig()
|
mergeCtx.sig = doer.NewGitSig()
|
||||||
mergeCtx.committer = mergeCtx.sig
|
mergeCtx.committer = mergeCtx.sig
|
||||||
|
|
||||||
|
gitRepo, err := git.OpenRepository(ctx, mergeCtx.tmpBasePath)
|
||||||
|
if err != nil {
|
||||||
|
defer cancel()
|
||||||
|
return nil, nil, fmt.Errorf("failed to open temp git repo for pr[%d]: %w", mergeCtx.pr.ID, err)
|
||||||
|
}
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
// Determine if we should sign
|
// Determine if we should sign
|
||||||
sign, key, signer, _ := asymkey_service.SignMerge(ctx, mergeCtx.pr, mergeCtx.doer, mergeCtx.tmpBasePath, "HEAD", trackingBranch)
|
sign, key, signer, _ := asymkey_service.SignMerge(ctx, mergeCtx.pr, mergeCtx.doer, gitRepo, "HEAD", trackingBranch)
|
||||||
if sign {
|
if sign {
|
||||||
mergeCtx.signKey = key
|
mergeCtx.signKey = key
|
||||||
if pr.BaseRepo.GetTrustModel() == repo_model.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
|
if pr.BaseRepo.GetTrustModel() == repo_model.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/services/pull"
|
"code.gitea.io/gitea/services/pull"
|
||||||
@ -35,7 +36,13 @@ func (err ErrCommitIDDoesNotMatch) Error() string {
|
|||||||
|
|
||||||
// CherryPick cherry-picks or reverts a commit to the given repository
|
// CherryPick cherry-picks or reverts a commit to the given repository
|
||||||
func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, revert bool, opts *ApplyDiffPatchOptions) (*structs.FileResponse, error) {
|
func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, revert bool, opts *ApplyDiffPatchOptions) (*structs.FileResponse, error) {
|
||||||
if err := opts.Validate(ctx, repo, doer); err != nil {
|
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
if err := opts.Validate(ctx, repo, gitRepo, doer); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
message := strings.TrimSpace(opts.Message)
|
message := strings.TrimSpace(opts.Message)
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import (
|
|||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@ -52,7 +53,7 @@ type ApplyDiffPatchOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the provided options
|
// Validate validates the provided options
|
||||||
func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_model.Repository, doer *user_model.User) error {
|
func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doer *user_model.User) error {
|
||||||
// If no branch name is set, assume master
|
// If no branch name is set, assume master
|
||||||
if opts.OldBranch == "" {
|
if opts.OldBranch == "" {
|
||||||
opts.OldBranch = repo.DefaultBranch
|
opts.OldBranch = repo.DefaultBranch
|
||||||
@ -95,7 +96,7 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if protectedBranch != nil && protectedBranch.RequireSignedCommits {
|
if protectedBranch != nil && protectedBranch.RequireSignedCommits {
|
||||||
_, _, _, err := asymkey_service.SignCRUDAction(ctx, doer, repo.RepoPath(), opts.OldBranch)
|
_, _, _, err := asymkey_service.SignCRUDAction(ctx, doer, gitRepo, opts.OldBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !asymkey_service.IsErrWontSign(err) {
|
if !asymkey_service.IsErrWontSign(err) {
|
||||||
return err
|
return err
|
||||||
@ -116,7 +117,13 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := opts.Validate(ctx, repo, doer); err != nil {
|
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
if err := opts.Validate(ctx, repo, gitRepo, doer); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -303,7 +303,7 @@ func (t *TemporaryUploadRepository) CommitTree(ctx context.Context, opts *Commit
|
|||||||
var key *git.SigningKey
|
var key *git.SigningKey
|
||||||
var signer *git.Signature
|
var signer *git.Signature
|
||||||
if opts.ParentCommitID != "" {
|
if opts.ParentCommitID != "" {
|
||||||
sign, key, signer, _ = asymkey_service.SignCRUDAction(ctx, opts.DoerUser, t.basePath, opts.ParentCommitID)
|
sign, key, signer, _ = asymkey_service.SignCRUDAction(ctx, opts.DoerUser, t.gitRepo, opts.ParentCommitID)
|
||||||
} else {
|
} else {
|
||||||
sign, key, signer, _ = asymkey_service.SignInitialCommit(ctx, opts.DoerUser)
|
sign, key, signer, _ = asymkey_service.SignInitialCommit(ctx, opts.DoerUser)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -167,7 +167,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if err := VerifyBranchProtection(ctx, repo, doer, opts.OldBranch, treePaths); err != nil {
|
} else if err := VerifyBranchProtection(ctx, repo, gitRepo, doer, opts.OldBranch, treePaths); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -659,7 +659,7 @@ func writeRepoObjectForRename(ctx context.Context, t *TemporaryUploadRepository,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
|
// VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
|
||||||
func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, branchName string, treePaths []string) error {
|
func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doer *user_model.User, branchName string, treePaths []string) error {
|
||||||
protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branchName)
|
protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -686,7 +686,7 @@ func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, do
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if protectedBranch.RequireSignedCommits {
|
if protectedBranch.RequireSignedCommits {
|
||||||
_, _, _, err := asymkey_service.SignCRUDAction(ctx, doer, repo.RepoPath(), branchName)
|
_, _, _, err := asymkey_service.SignCRUDAction(ctx, doer, gitRepo, branchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !asymkey_service.IsErrWontSign(err) {
|
if !asymkey_service.IsErrWontSign(err) {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -72,22 +72,22 @@ func (dc dingtalkConvertor) Push(p *api.PushPayload) (DingtalkPayload, error) {
|
|||||||
|
|
||||||
title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)
|
title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)
|
||||||
|
|
||||||
var text string
|
var text strings.Builder
|
||||||
// for each commit, generate attachment text
|
// for each commit, generate attachment text
|
||||||
for i, commit := range p.Commits {
|
for i, commit := range p.Commits {
|
||||||
var authorName string
|
var authorName string
|
||||||
if commit.Author != nil {
|
if commit.Author != nil {
|
||||||
authorName = " - " + commit.Author.Name
|
authorName = " - " + commit.Author.Name
|
||||||
}
|
}
|
||||||
text += fmt.Sprintf("[%s](%s) %s", commit.ID[:7], commit.URL,
|
text.WriteString(fmt.Sprintf("[%s](%s) %s", commit.ID[:7], commit.URL,
|
||||||
strings.TrimRight(commit.Message, "\r\n")) + authorName
|
strings.TrimRight(commit.Message, "\r\n")) + authorName)
|
||||||
// add linebreak to each commit but the last
|
// add linebreak to each commit but the last
|
||||||
if i < len(p.Commits)-1 {
|
if i < len(p.Commits)-1 {
|
||||||
text += "\r\n"
|
text.WriteString("\r\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return createDingtalkPayload(title, text, linkText, titleLink), nil
|
return createDingtalkPayload(title, text.String(), linkText, titleLink), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue implements PayloadConvertor Issue method
|
// Issue implements PayloadConvertor Issue method
|
||||||
|
|||||||
@ -159,7 +159,7 @@ func (d discordConvertor) Push(p *api.PushPayload) (DiscordPayload, error) {
|
|||||||
|
|
||||||
title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)
|
title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)
|
||||||
|
|
||||||
var text string
|
var text strings.Builder
|
||||||
// for each commit, generate attachment text
|
// for each commit, generate attachment text
|
||||||
for i, commit := range p.Commits {
|
for i, commit := range p.Commits {
|
||||||
// limit the commit message display to just the summary, otherwise it would be hard to read
|
// limit the commit message display to just the summary, otherwise it would be hard to read
|
||||||
@ -169,14 +169,14 @@ func (d discordConvertor) Push(p *api.PushPayload) (DiscordPayload, error) {
|
|||||||
if utf8.RuneCountInString(message) > 50 {
|
if utf8.RuneCountInString(message) > 50 {
|
||||||
message = fmt.Sprintf("%.47s...", message)
|
message = fmt.Sprintf("%.47s...", message)
|
||||||
}
|
}
|
||||||
text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL, message, commit.Author.Name)
|
text.WriteString(fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL, message, commit.Author.Name))
|
||||||
// add linebreak to each commit but the last
|
// add linebreak to each commit but the last
|
||||||
if i < len(p.Commits)-1 {
|
if i < len(p.Commits)-1 {
|
||||||
text += "\n"
|
text.WriteString("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.createPayload(p.Sender, title, text, titleLink, greenColor), nil
|
return d.createPayload(p.Sender, title, text.String(), titleLink, greenColor), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue implements PayloadConvertor Issue method
|
// Issue implements PayloadConvertor Issue method
|
||||||
|
|||||||
@ -76,22 +76,23 @@ func (fc feishuConvertor) Push(p *api.PushPayload) (FeishuPayload, error) {
|
|||||||
commitDesc string
|
commitDesc string
|
||||||
)
|
)
|
||||||
|
|
||||||
text := fmt.Sprintf("[%s:%s] %s\r\n", p.Repo.FullName, branchName, commitDesc)
|
var text strings.Builder
|
||||||
|
text.WriteString(fmt.Sprintf("[%s:%s] %s\r\n", p.Repo.FullName, branchName, commitDesc))
|
||||||
// for each commit, generate attachment text
|
// for each commit, generate attachment text
|
||||||
for i, commit := range p.Commits {
|
for i, commit := range p.Commits {
|
||||||
var authorName string
|
var authorName string
|
||||||
if commit.Author != nil {
|
if commit.Author != nil {
|
||||||
authorName = " - " + commit.Author.Name
|
authorName = " - " + commit.Author.Name
|
||||||
}
|
}
|
||||||
text += fmt.Sprintf("[%s](%s) %s", commit.ID[:7], commit.URL,
|
text.WriteString(fmt.Sprintf("[%s](%s) %s", commit.ID[:7], commit.URL,
|
||||||
strings.TrimRight(commit.Message, "\r\n")) + authorName
|
strings.TrimRight(commit.Message, "\r\n")) + authorName)
|
||||||
// add linebreak to each commit but the last
|
// add linebreak to each commit but the last
|
||||||
if i < len(p.Commits)-1 {
|
if i < len(p.Commits)-1 {
|
||||||
text += "\r\n"
|
text.WriteString("\r\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newFeishuTextPayload(text), nil
|
return newFeishuTextPayload(text.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue implements PayloadConvertor Issue method
|
// Issue implements PayloadConvertor Issue method
|
||||||
|
|||||||
@ -173,18 +173,19 @@ func (m matrixConvertor) Push(p *api.PushPayload) (MatrixPayload, error) {
|
|||||||
|
|
||||||
repoLink := htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
|
repoLink := htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
|
||||||
branchLink := MatrixLinkToRef(p.Repo.HTMLURL, p.Ref)
|
branchLink := MatrixLinkToRef(p.Repo.HTMLURL, p.Ref)
|
||||||
text := fmt.Sprintf("[%s] %s pushed %s to %s:<br>", repoLink, p.Pusher.UserName, commitDesc, branchLink)
|
var text strings.Builder
|
||||||
|
text.WriteString(fmt.Sprintf("[%s] %s pushed %s to %s:<br>", repoLink, p.Pusher.UserName, commitDesc, branchLink))
|
||||||
|
|
||||||
// for each commit, generate a new line text
|
// for each commit, generate a new line text
|
||||||
for i, commit := range p.Commits {
|
for i, commit := range p.Commits {
|
||||||
text += fmt.Sprintf("%s: %s - %s", htmlLinkFormatter(commit.URL, commit.ID[:7]), commit.Message, commit.Author.Name)
|
text.WriteString(fmt.Sprintf("%s: %s - %s", htmlLinkFormatter(commit.URL, commit.ID[:7]), commit.Message, commit.Author.Name))
|
||||||
// add linebreak to each commit but the last
|
// add linebreak to each commit but the last
|
||||||
if i < len(p.Commits)-1 {
|
if i < len(p.Commits)-1 {
|
||||||
text += "<br>"
|
text.WriteString("<br>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.newPayload(text, p.Commits...)
|
return m.newPayload(text.String(), p.Commits...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PullRequest implements payloadConvertor PullRequest method
|
// PullRequest implements payloadConvertor PullRequest method
|
||||||
|
|||||||
@ -131,14 +131,14 @@ func (m msteamsConvertor) Push(p *api.PushPayload) (MSTeamsPayload, error) {
|
|||||||
|
|
||||||
title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)
|
title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)
|
||||||
|
|
||||||
var text string
|
var text strings.Builder
|
||||||
// for each commit, generate attachment text
|
// for each commit, generate attachment text
|
||||||
for i, commit := range p.Commits {
|
for i, commit := range p.Commits {
|
||||||
text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL,
|
text.WriteString(fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL,
|
||||||
strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name)
|
strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name))
|
||||||
// add linebreak to each commit but the last
|
// add linebreak to each commit but the last
|
||||||
if i < len(p.Commits)-1 {
|
if i < len(p.Commits)-1 {
|
||||||
text += "\n\n"
|
text.WriteString("\n\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ func (m msteamsConvertor) Push(p *api.PushPayload) (MSTeamsPayload, error) {
|
|||||||
p.Repo,
|
p.Repo,
|
||||||
p.Sender,
|
p.Sender,
|
||||||
title,
|
title,
|
||||||
text,
|
text.String(),
|
||||||
titleLink,
|
titleLink,
|
||||||
greenColor,
|
greenColor,
|
||||||
&MSTeamsFact{"Commit count:", strconv.Itoa(p.TotalCommits)},
|
&MSTeamsFact{"Commit count:", strconv.Itoa(p.TotalCommits)},
|
||||||
|
|||||||
@ -208,13 +208,13 @@ func (s slackConvertor) Push(p *api.PushPayload) (SlackPayload, error) {
|
|||||||
branchLink := SlackLinkToRef(p.Repo.HTMLURL, p.Ref)
|
branchLink := SlackLinkToRef(p.Repo.HTMLURL, p.Ref)
|
||||||
text := fmt.Sprintf("[%s:%s] %s pushed by %s", repoLink, branchLink, commitString, p.Pusher.UserName)
|
text := fmt.Sprintf("[%s:%s] %s pushed by %s", repoLink, branchLink, commitString, p.Pusher.UserName)
|
||||||
|
|
||||||
var attachmentText string
|
var attachmentText strings.Builder
|
||||||
// for each commit, generate attachment text
|
// for each commit, generate attachment text
|
||||||
for i, commit := range p.Commits {
|
for i, commit := range p.Commits {
|
||||||
attachmentText += fmt.Sprintf("%s: %s - %s", SlackLinkFormatter(commit.URL, commit.ID[:7]), SlackShortTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name))
|
attachmentText.WriteString(fmt.Sprintf("%s: %s - %s", SlackLinkFormatter(commit.URL, commit.ID[:7]), SlackShortTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name)))
|
||||||
// add linebreak to each commit but the last
|
// add linebreak to each commit but the last
|
||||||
if i < len(p.Commits)-1 {
|
if i < len(p.Commits)-1 {
|
||||||
attachmentText += "\n"
|
attachmentText.WriteString("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,7 +222,7 @@ func (s slackConvertor) Push(p *api.PushPayload) (SlackPayload, error) {
|
|||||||
Color: s.Color,
|
Color: s.Color,
|
||||||
Title: p.Repo.HTMLURL,
|
Title: p.Repo.HTMLURL,
|
||||||
TitleLink: p.Repo.HTMLURL,
|
TitleLink: p.Repo.HTMLURL,
|
||||||
Text: attachmentText,
|
Text: attachmentText.String(),
|
||||||
}}), nil
|
}}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -94,14 +94,14 @@ func (t telegramConvertor) Push(p *api.PushPayload) (TelegramPayload, error) {
|
|||||||
}
|
}
|
||||||
title := fmt.Sprintf(`[%s:%s] %s`, htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName), htmlLinkFormatter(titleLink, branchName), html.EscapeString(commitDesc))
|
title := fmt.Sprintf(`[%s:%s] %s`, htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName), htmlLinkFormatter(titleLink, branchName), html.EscapeString(commitDesc))
|
||||||
|
|
||||||
var htmlCommits string
|
var htmlCommits strings.Builder
|
||||||
for _, commit := range p.Commits {
|
for _, commit := range p.Commits {
|
||||||
htmlCommits += fmt.Sprintf("\n[%s] %s", htmlLinkFormatter(commit.URL, commit.ID[:7]), html.EscapeString(strings.TrimRight(commit.Message, "\r\n")))
|
htmlCommits.WriteString(fmt.Sprintf("\n[%s] %s", htmlLinkFormatter(commit.URL, commit.ID[:7]), html.EscapeString(strings.TrimRight(commit.Message, "\r\n"))))
|
||||||
if commit.Author != nil {
|
if commit.Author != nil {
|
||||||
htmlCommits += " - " + html.EscapeString(commit.Author.Name)
|
htmlCommits.WriteString(" - " + html.EscapeString(commit.Author.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return createTelegramPayloadHTML(title + htmlCommits), nil
|
return createTelegramPayloadHTML(title + htmlCommits.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue implements PayloadConvertor Issue method
|
// Issue implements PayloadConvertor Issue method
|
||||||
|
|||||||
@ -77,7 +77,7 @@ func (wc wechatworkConvertor) Push(p *api.PushPayload) (WechatworkPayload, error
|
|||||||
|
|
||||||
title := fmt.Sprintf("# %s:%s <font color=\"warning\"> %s </font>", p.Repo.FullName, branchName, commitDesc)
|
title := fmt.Sprintf("# %s:%s <font color=\"warning\"> %s </font>", p.Repo.FullName, branchName, commitDesc)
|
||||||
|
|
||||||
var text string
|
var text strings.Builder
|
||||||
// for each commit, generate attachment text
|
// for each commit, generate attachment text
|
||||||
for i, commit := range p.Commits {
|
for i, commit := range p.Commits {
|
||||||
var authorName string
|
var authorName string
|
||||||
@ -86,15 +86,15 @@ func (wc wechatworkConvertor) Push(p *api.PushPayload) (WechatworkPayload, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
message := strings.ReplaceAll(commit.Message, "\n\n", "\r\n")
|
message := strings.ReplaceAll(commit.Message, "\n\n", "\r\n")
|
||||||
text += fmt.Sprintf(" > [%s](%s) \r\n ><font color=\"info\">%s</font> \n ><font color=\"warning\">%s</font>", commit.ID[:7], commit.URL,
|
text.WriteString(fmt.Sprintf(" > [%s](%s) \r\n ><font color=\"info\">%s</font> \n ><font color=\"warning\">%s</font>", commit.ID[:7], commit.URL,
|
||||||
message, authorName)
|
message, authorName))
|
||||||
|
|
||||||
// add linebreak to each commit but the last
|
// add linebreak to each commit but the last
|
||||||
if i < len(p.Commits)-1 {
|
if i < len(p.Commits)-1 {
|
||||||
text += "\n"
|
text.WriteString("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newWechatworkMarkdownPayload(title + "\r\n\r\n" + text), nil
|
return newWechatworkMarkdownPayload(title + "\r\n\r\n" + text.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue implements PayloadConvertor Issue method
|
// Issue implements PayloadConvertor Issue method
|
||||||
|
|||||||
@ -193,7 +193,13 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
|
|||||||
|
|
||||||
committer := doer.NewGitSig()
|
committer := doer.NewGitSig()
|
||||||
|
|
||||||
sign, signingKey, signer, _ := asymkey_service.SignWikiCommit(ctx, repo, doer)
|
originalGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo.WikiStorageRepo())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to open wiki repository: %w", err)
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
sign, signingKey, signer, _ := asymkey_service.SignWikiCommit(ctx, repo, originalGitRepo, doer)
|
||||||
if sign {
|
if sign {
|
||||||
commitTreeOpts.Key = signingKey
|
commitTreeOpts.Key = signingKey
|
||||||
if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
|
if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
|
||||||
@ -212,7 +218,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gitrepo.PushFromLocal(gitRepo.Ctx, basePath, repo.WikiStorageRepo(), git.PushOptions{
|
if err := gitrepo.PushFromLocal(ctx, basePath, repo.WikiStorageRepo(), git.PushOptions{
|
||||||
Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, repo.DefaultWikiBranch),
|
Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, repo.DefaultWikiBranch),
|
||||||
Env: repo_module.FullPushingEnvironment(
|
Env: repo_module.FullPushingEnvironment(
|
||||||
doer,
|
doer,
|
||||||
@ -315,7 +321,13 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
|
|||||||
|
|
||||||
committer := doer.NewGitSig()
|
committer := doer.NewGitSig()
|
||||||
|
|
||||||
sign, signingKey, signer, _ := asymkey_service.SignWikiCommit(ctx, repo, doer)
|
originalGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo.WikiStorageRepo())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to open wiki repository: %w", err)
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
sign, signingKey, signer, _ := asymkey_service.SignWikiCommit(ctx, repo, originalGitRepo, doer)
|
||||||
if sign {
|
if sign {
|
||||||
commitTreeOpts.Key = signingKey
|
commitTreeOpts.Key = signingKey
|
||||||
if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
|
if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
|
||||||
|
|||||||
@ -10,18 +10,25 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/services/migrations"
|
"code.gitea.io/gitea/services/migrations"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMigrateLocalPath(t *testing.T) {
|
func TestMigrateLocalPath(t *testing.T) {
|
||||||
@ -112,3 +119,176 @@ func Test_UpdateCommentsMigrationsByType(t *testing.T) {
|
|||||||
err := issues_model.UpdateCommentsMigrationsByType(t.Context(), structs.GithubService, "1", 1)
|
err := issues_model.UpdateCommentsMigrationsByType(t.Context(), structs.GithubService, "1", 1)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_MigrateFromGiteaToGitea(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
|
||||||
|
session := loginUser(t, owner.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
|
||||||
|
|
||||||
|
resp, err := http.Get("https://gitea.com/gitea")
|
||||||
|
if err != nil || resp.StatusCode != http.StatusOK {
|
||||||
|
if resp != nil {
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
t.Skipf("Can't reach https://gitea.com, skipping %s", t.Name())
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
repoName := fmt.Sprintf("gitea-to-gitea-%d", time.Now().UnixNano())
|
||||||
|
cloneAddr := "https://gitea.com/gitea/test_repo.git"
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate", &structs.MigrateRepoOptions{
|
||||||
|
CloneAddr: cloneAddr,
|
||||||
|
RepoOwnerID: owner.ID,
|
||||||
|
RepoName: repoName,
|
||||||
|
Service: structs.GiteaService.Name(),
|
||||||
|
Wiki: true,
|
||||||
|
Milestones: true,
|
||||||
|
Labels: true,
|
||||||
|
Issues: true,
|
||||||
|
PullRequests: true,
|
||||||
|
Releases: true,
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
migratedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: repoName})
|
||||||
|
assert.Equal(t, owner.ID, migratedRepo.OwnerID)
|
||||||
|
assert.Equal(t, structs.GiteaService, migratedRepo.OriginalServiceType)
|
||||||
|
assert.Equal(t, cloneAddr, migratedRepo.OriginalURL)
|
||||||
|
|
||||||
|
issueCount := unittest.GetCount(t,
|
||||||
|
&issues_model.Issue{RepoID: migratedRepo.ID},
|
||||||
|
unittest.Cond("is_pull = ?", false),
|
||||||
|
)
|
||||||
|
assert.Equal(t, 7, issueCount)
|
||||||
|
pullCount := unittest.GetCount(t,
|
||||||
|
&issues_model.Issue{RepoID: migratedRepo.ID},
|
||||||
|
unittest.Cond("is_pull = ?", true),
|
||||||
|
)
|
||||||
|
assert.Equal(t, 6, pullCount)
|
||||||
|
|
||||||
|
issue4, err := issues_model.GetIssueWithAttrsByIndex(t.Context(), migratedRepo.ID, 4)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, owner.ID, issue4.PosterID)
|
||||||
|
assert.Equal(t, "Ghost", issue4.OriginalAuthor)
|
||||||
|
assert.Equal(t, int64(-1), issue4.OriginalAuthorID)
|
||||||
|
assert.Equal(t, "what is this repo about?", issue4.Title)
|
||||||
|
assert.True(t, issue4.IsClosed)
|
||||||
|
assert.True(t, issue4.IsLocked)
|
||||||
|
if assert.NotNil(t, issue4.Milestone) {
|
||||||
|
assert.Equal(t, "V1", issue4.Milestone.Name)
|
||||||
|
}
|
||||||
|
labelNames := make([]string, 0, len(issue4.Labels))
|
||||||
|
for _, label := range issue4.Labels {
|
||||||
|
labelNames = append(labelNames, label.Name)
|
||||||
|
}
|
||||||
|
assert.Contains(t, labelNames, "Question")
|
||||||
|
reactionTypes := make([]string, 0, len(issue4.Reactions))
|
||||||
|
for _, reaction := range issue4.Reactions {
|
||||||
|
reactionTypes = append(reactionTypes, reaction.Type)
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(t, []string{"laugh"}, reactionTypes) // gitea's author is ghost which will be ignored when migrating reactions
|
||||||
|
|
||||||
|
comments, err := issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{
|
||||||
|
IssueID: issue4.ID,
|
||||||
|
Type: issues_model.CommentTypeComment,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, comments, 2)
|
||||||
|
assert.Equal(t, owner.ID, comments[0].PosterID)
|
||||||
|
assert.Equal(t, int64(689), comments[0].OriginalAuthorID)
|
||||||
|
assert.Equal(t, "6543", comments[0].OriginalAuthor)
|
||||||
|
assert.Contains(t, comments[0].Content, "TESTSET for gitea2gitea")
|
||||||
|
assert.Equal(t, owner.ID, comments[1].PosterID)
|
||||||
|
assert.Equal(t, "Ghost", comments[1].OriginalAuthor)
|
||||||
|
assert.Equal(t, int64(-1), comments[1].OriginalAuthorID)
|
||||||
|
assert.Equal(t, "Oh!", strings.TrimSpace(comments[1].Content))
|
||||||
|
|
||||||
|
pr12, err := issues_model.GetPullRequestByIndex(t.Context(), migratedRepo.ID, 12)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, owner.ID, pr12.Issue.PosterID)
|
||||||
|
assert.Equal(t, "6543", pr12.Issue.OriginalAuthor)
|
||||||
|
assert.Equal(t, int64(689), pr12.Issue.OriginalAuthorID)
|
||||||
|
assert.Equal(t, "Dont Touch", pr12.Issue.Title)
|
||||||
|
assert.True(t, pr12.Issue.IsClosed)
|
||||||
|
assert.True(t, pr12.HasMerged)
|
||||||
|
assert.Equal(t, "827aa28a907853e5ddfa40c8f9bc52471a2685fd", pr12.MergedCommitID)
|
||||||
|
assert.NoError(t, pr12.Issue.LoadMilestone(t.Context()))
|
||||||
|
if assert.NotNil(t, pr12.Issue.Milestone) {
|
||||||
|
assert.Equal(t, "V2 Finalize", pr12.Issue.Milestone.Name)
|
||||||
|
}
|
||||||
|
assert.Contains(t, pr12.Issue.Content, "dont touch")
|
||||||
|
|
||||||
|
pr8, err := issues_model.GetPullRequestByIndex(t.Context(), migratedRepo.ID, 8)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, owner.ID, pr8.Issue.PosterID)
|
||||||
|
assert.Equal(t, "6543", pr8.Issue.OriginalAuthor)
|
||||||
|
assert.Equal(t, int64(689), pr8.Issue.OriginalAuthorID)
|
||||||
|
assert.Equal(t, "add garbage for close pull", pr8.Issue.Title)
|
||||||
|
assert.True(t, pr8.Issue.IsClosed)
|
||||||
|
assert.False(t, pr8.HasMerged)
|
||||||
|
assert.Contains(t, pr8.Issue.Content, "well you'll see")
|
||||||
|
|
||||||
|
pr13, err := issues_model.GetPullRequestByIndex(t.Context(), migratedRepo.ID, 13)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, owner.ID, pr13.Issue.PosterID)
|
||||||
|
assert.Equal(t, "6543", pr13.Issue.OriginalAuthor)
|
||||||
|
assert.Equal(t, int64(689), pr13.Issue.OriginalAuthorID)
|
||||||
|
assert.Equal(t, "extend", pr13.Issue.Title)
|
||||||
|
assert.False(t, pr13.Issue.IsClosed)
|
||||||
|
assert.False(t, pr13.HasMerged)
|
||||||
|
assert.True(t, pr13.Issue.IsLocked)
|
||||||
|
|
||||||
|
gitRepo, err := gitrepo.OpenRepository(t.Context(), migratedRepo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
branches, _, err := gitRepo.GetBranchNames(0, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.ElementsMatch(t, []string{"6543-patch-1", "master", "6543-forks/add-xkcd-2199"}, branches) // last branch comes from the pull request
|
||||||
|
|
||||||
|
branchNames, err := git_model.FindBranchNames(t.Context(), git_model.FindBranchOptions{
|
||||||
|
RepoID: migratedRepo.ID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.ElementsMatch(t, []string{"6543-patch-1", "master", "6543-forks/add-xkcd-2199"}, branchNames)
|
||||||
|
|
||||||
|
tags, _, err := gitRepo.GetTagInfos(0, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
tagNames := make([]string, 0, len(tags))
|
||||||
|
for _, tag := range tags {
|
||||||
|
tagNames = append(tagNames, tag.Name)
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(t, []string{"V1", "v2-rc1"}, tagNames)
|
||||||
|
|
||||||
|
releases, err := db.Find[repo_model.Release](t.Context(), repo_model.FindReleasesOptions{
|
||||||
|
RepoID: migratedRepo.ID,
|
||||||
|
IncludeDrafts: true,
|
||||||
|
IncludeTags: false,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, releases, 2)
|
||||||
|
|
||||||
|
releaseMap := make(map[string]*repo_model.Release, len(releases))
|
||||||
|
for _, rel := range releases {
|
||||||
|
releaseMap[rel.TagName] = rel
|
||||||
|
assert.Equal(t, owner.ID, rel.PublisherID)
|
||||||
|
assert.Equal(t, "6543", rel.OriginalAuthor)
|
||||||
|
assert.Equal(t, int64(689), rel.OriginalAuthorID)
|
||||||
|
assert.False(t, rel.IsDraft)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Contains(t, releaseMap, "v2-rc1")
|
||||||
|
v2Release := releaseMap["v2-rc1"]
|
||||||
|
assert.Equal(t, "Second Release", v2Release.Title)
|
||||||
|
assert.True(t, v2Release.IsPrerelease)
|
||||||
|
assert.Contains(t, v2Release.Note, "this repo has:")
|
||||||
|
|
||||||
|
require.Contains(t, releaseMap, "V1")
|
||||||
|
v1Release := releaseMap["V1"]
|
||||||
|
assert.Equal(t, "First Release", v1Release.Title)
|
||||||
|
assert.False(t, v1Release.IsPrerelease)
|
||||||
|
assert.Equal(t, "as title", strings.TrimSpace(v1Release.Note))
|
||||||
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -17,7 +18,9 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -153,8 +156,16 @@ func TestPullCreate(t *testing.T) {
|
|||||||
url := test.RedirectURL(resp)
|
url := test.RedirectURL(resp)
|
||||||
assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)
|
assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)
|
||||||
|
|
||||||
|
// test create the pull request again and it should fail now
|
||||||
|
link := "/user2/repo1/compare/master...user1/repo1:master"
|
||||||
|
req := NewRequestWithValues(t, "POST", link, map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, session),
|
||||||
|
"title": "This is a pull title",
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusBadRequest)
|
||||||
|
|
||||||
// check .diff can be accessed and matches performed change
|
// check .diff can be accessed and matches performed change
|
||||||
req := NewRequest(t, "GET", url+".diff")
|
req = NewRequest(t, "GET", url+".diff")
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body)
|
assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body)
|
||||||
assert.Regexp(t, "^diff", resp.Body)
|
assert.Regexp(t, "^diff", resp.Body)
|
||||||
@ -295,6 +306,95 @@ func TestPullCreatePrFromBaseToFork(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreatePullRequestFromNestedOrgForks(t *testing.T) {
|
||||||
|
onGiteaRun(t, func(t *testing.T, _ *url.URL) {
|
||||||
|
session := loginUser(t, "user1")
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization)
|
||||||
|
|
||||||
|
const (
|
||||||
|
baseOrg = "test-fork-org1"
|
||||||
|
midForkOrg = "test-fork-org2"
|
||||||
|
leafForkOrg = "test-fork-org3"
|
||||||
|
repoName = "test-fork-repo"
|
||||||
|
patchBranch = "teabot-patch-1"
|
||||||
|
)
|
||||||
|
|
||||||
|
createOrg := func(name string) {
|
||||||
|
req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &api.CreateOrgOption{
|
||||||
|
UserName: name,
|
||||||
|
Visibility: "public",
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
}
|
||||||
|
|
||||||
|
createOrg(baseOrg)
|
||||||
|
createOrg(midForkOrg)
|
||||||
|
createOrg(leafForkOrg)
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/repos", baseOrg), &api.CreateRepoOption{
|
||||||
|
Name: repoName,
|
||||||
|
AutoInit: true,
|
||||||
|
DefaultBranch: "main",
|
||||||
|
Private: false,
|
||||||
|
Readme: "Default",
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
var baseRepo api.Repository
|
||||||
|
DecodeJSON(t, resp, &baseRepo)
|
||||||
|
assert.Equal(t, "main", baseRepo.DefaultBranch)
|
||||||
|
|
||||||
|
forkIntoOrg := func(srcOrg, dstOrg string) api.Repository {
|
||||||
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", srcOrg, repoName), &api.CreateForkOption{
|
||||||
|
Organization: util.ToPointer(dstOrg),
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusAccepted)
|
||||||
|
var forkRepo api.Repository
|
||||||
|
DecodeJSON(t, resp, &forkRepo)
|
||||||
|
assert.NotNil(t, forkRepo.Owner)
|
||||||
|
if forkRepo.Owner != nil {
|
||||||
|
assert.Equal(t, dstOrg, forkRepo.Owner.UserName)
|
||||||
|
}
|
||||||
|
return forkRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
forkIntoOrg(baseOrg, midForkOrg)
|
||||||
|
forkIntoOrg(midForkOrg, leafForkOrg)
|
||||||
|
|
||||||
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", leafForkOrg, repoName, "patch-from-org3.txt"), &api.CreateFileOptions{
|
||||||
|
FileOptions: api.FileOptions{
|
||||||
|
BranchName: "main",
|
||||||
|
NewBranchName: patchBranch,
|
||||||
|
Message: "create patch from org3",
|
||||||
|
},
|
||||||
|
ContentBase64: base64.StdEncoding.EncodeToString([]byte("patch content")),
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
prPayload := map[string]string{
|
||||||
|
"head": fmt.Sprintf("%s:%s", leafForkOrg, patchBranch),
|
||||||
|
"base": "main",
|
||||||
|
"title": "test creating pull from test-fork-org3 to test-fork-org1",
|
||||||
|
}
|
||||||
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/pulls", baseOrg, repoName), prPayload).AddTokenAuth(token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusCreated)
|
||||||
|
var pr api.PullRequest
|
||||||
|
DecodeJSON(t, resp, &pr)
|
||||||
|
assert.Equal(t, prPayload["title"], pr.Title)
|
||||||
|
if assert.NotNil(t, pr.Head) {
|
||||||
|
assert.Equal(t, patchBranch, pr.Head.Ref)
|
||||||
|
if assert.NotNil(t, pr.Head.Repository) {
|
||||||
|
assert.Equal(t, fmt.Sprintf("%s/%s", leafForkOrg, repoName), pr.Head.Repository.FullName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if assert.NotNil(t, pr.Base) {
|
||||||
|
assert.Equal(t, "main", pr.Base.Ref)
|
||||||
|
if assert.NotNil(t, pr.Base.Repository) {
|
||||||
|
assert.Equal(t, fmt.Sprintf("%s/%s", baseOrg, repoName), pr.Base.Repository.FullName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestPullCreateParallel(t *testing.T) {
|
func TestPullCreateParallel(t *testing.T) {
|
||||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||||
sessionFork := loginUser(t, "user1")
|
sessionFork := loginUser(t, "user1")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user