0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-04-04 12:26:12 +02:00

Merge branch 'main' into fix-24635

This commit is contained in:
Excellencedev 2025-12-18 02:39:42 +01:00 committed by GitHub
commit e20d12e8a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 1359 additions and 986 deletions

View File

@ -111,9 +111,6 @@ linters:
- require-error
usetesting:
os-temp-dir: true
modernize:
disable:
- stringsbuilder
perfsprint:
concat-loop: false
govet:

View File

@ -32,7 +32,7 @@ XGO_VERSION := go-1.25.x
AIR_PACKAGE ?= github.com/air-verse/air@v1
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3
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
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0
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
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
.PHONY: lint-js-fix
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
.PHONY: lint-css

View File

@ -215,7 +215,7 @@ export default defineConfig([
'@typescript-eslint/no-unnecessary-condition': [0],
'@typescript-eslint/no-unnecessary-qualifier': [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-constraint': [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-return': [0],
'@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-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-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-wrapper-object-types': [2],
'@typescript-eslint/non-nullable-type-assertion-style': [0],
@ -584,7 +585,7 @@ export default defineConfig([
'no-unreachable': [2],
'no-unsafe-finally': [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-private-class-members': [0], // handled by @typescript-eslint/no-unused-private-class-members
'no-unused-vars': [0], // handled by @typescript-eslint/no-unused-vars

View File

@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"slices"
"strings"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
@ -169,7 +170,8 @@ func (p *Permission) ReadableUnitTypes() []unit.Type {
}
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)}
for i, u := range p.units {
@ -181,19 +183,19 @@ func (p *Permission) LogString() string {
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)
}
for key, value := range p.unitsMode {
format += "\n\tunitsMode[%-v]: %-v"
format.WriteString("\n\tunitsMode[%-v]: %-v")
args = append(args, key.LogString(), value.LogString())
}
format += "\n\tanonymousAccessMode: %-v"
format.WriteString("\n\tanonymousAccessMode: %-v")
args = append(args, p.anonymousAccessMode)
format += "\n\teveryoneAccessMode: %-v"
format.WriteString("\n\teveryoneAccessMode: %-v")
args = append(args, p.everyoneAccessMode)
format += "\n\t]>"
return fmt.Sprintf(format, args...)
format.WriteString("\n\t]>")
return fmt.Sprintf(format.String(), args...)
}
func applyPublicAccessPermission(unitType unit.Type, accessMode perm_model.AccessMode, modeMap *map[unit.Type]perm_model.AccessMode) {

View File

@ -1461,3 +1461,15 @@ func GetUserOrOrgIDByName(ctx context.Context, name string) (int64, error) {
}
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
}

View File

@ -75,9 +75,9 @@ func (f Format) Parser(r io.Reader) *Parser {
// hexEscaped produces hex-escpaed characters from a string. For example, "\n\0"
// would turn into "%0a%00".
func (f Format) hexEscaped(delim []byte) string {
escaped := ""
var escaped strings.Builder
for i := range delim {
escaped += "%" + hex.EncodeToString([]byte{delim[i]})
escaped.WriteString("%" + hex.EncodeToString([]byte{delim[i]}))
}
return escaped
return escaped.String()
}

View File

@ -14,6 +14,7 @@ type Uploader interface {
CreateMilestones(ctx context.Context, milestones ...*Milestone) error
CreateReleases(ctx context.Context, releases ...*Release) error
SyncTags(ctx context.Context) error
SyncBranches(ctx context.Context) error
CreateLabels(ctx context.Context, labels ...*Label) error
CreateIssues(ctx context.Context, issues ...*Issue) error
CreateComments(ctx context.Context, comments ...*Comment) error

View File

@ -4,6 +4,7 @@
package setting
import (
"strings"
"sync"
"code.gitea.io/gitea/modules/log"
@ -23,11 +24,11 @@ type OpenWithEditorApp struct {
type OpenWithEditorAppsType []OpenWithEditorApp
func (t OpenWithEditorAppsType) ToTextareaString() string {
ret := ""
var ret strings.Builder
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 {

View File

@ -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 {
isPullRequest := issue != nil && issue.IsPull
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 {
// Protect against nil value in labels - shouldn't happen but would cause a panic if so
if label == nil {
continue
}
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>"
return template.HTML(htmlCode)
htmlCode.WriteString("</span>")
return template.HTML(htmlCode.String())
}
func (ut *RenderUtils) RenderThemeItem(info *webtheme.ThemeMetaInfo, iconSize int) template.HTML {

View File

@ -1863,6 +1863,7 @@ pulls.desc = Enable pull requests and code reviews.
pulls.new = New Pull Request
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.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.view = View Pull Request
pulls.compare_changes = New Pull Request

View File

@ -1,6 +1,6 @@
{
"type": "module",
"packageManager": "pnpm@10.24.0",
"packageManager": "pnpm@10.26.0",
"engines": {
"node": ">= 22.6.0",
"pnpm": ">= 10.0.0"
@ -12,7 +12,7 @@
"@citation-js/plugin-software-formats": "0.6.1",
"@github/markdown-toolbar-element": "2.2.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",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@primer/octicons": "19.21.1",
@ -21,7 +21,7 @@
"@techknowlogick/license-checker-webpack-plugin": "0.3.0",
"add-asset-webpack-plugin": "3.1.1",
"ansi_up": "6.0.6",
"asciinema-player": "3.12.1",
"asciinema-player": "3.13.5",
"chart.js": "4.5.1",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.2.0",
@ -36,32 +36,31 @@
"htmx.org": "2.0.8",
"idiomorph": "0.7.4",
"jquery": "3.7.1",
"katex": "0.16.25",
"katex": "0.16.27",
"mermaid": "11.12.2",
"mini-css-extract-plugin": "2.9.4",
"monaco-editor": "0.55.1",
"monaco-editor-webpack-plugin": "7.1.1",
"online-3d-viewer": "0.16.0",
"online-3d-viewer": "0.17.0",
"pdfobject": "2.3.1",
"perfect-debounce": "2.0.0",
"postcss": "8.5.6",
"postcss-loader": "8.2.0",
"sortablejs": "1.15.6",
"swagger-ui-dist": "5.30.3",
"swagger-ui-dist": "5.31.0",
"tailwindcss": "3.4.17",
"throttle-debounce": "5.0.2",
"tinycolor2": "1.6.0",
"tippy.js": "6.3.7",
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"typescript": "5.9.3",
"uint8-to-base64": "0.2.1",
"vanilla-colorful": "0.7.2",
"vue": "3.5.25",
"vue-bar-graph": "2.2.0",
"vue-chartjs": "5.3.3",
"vue-loader": "17.4.2",
"webpack": "5.103.0",
"webpack": "5.104.0",
"webpack-cli": "6.0.1",
"wrap-ansi": "9.0.2"
},
@ -80,10 +79,10 @@
"@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6",
"@types/toastify-js": "1.12.4",
"@typescript-eslint/parser": "8.48.1",
"@vitejs/plugin-vue": "6.0.2",
"@vitest/eslint-plugin": "1.5.1",
"eslint": "9.39.1",
"@typescript-eslint/parser": "8.50.0",
"@vitejs/plugin-vue": "6.0.3",
"@vitest/eslint-plugin": "1.5.2",
"eslint": "9.39.2",
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-array-func": "5.1.0",
"eslint-plugin-github": "6.0.0",
@ -97,7 +96,8 @@
"eslint-plugin-wc": "3.0.2",
"globals": "16.5.0",
"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",
"nolyfill": "1.0.44",
"postcss-html": "1.8.0",
@ -108,11 +108,12 @@
"stylelint-declaration-strict-value": "1.10.11",
"stylelint-value-no-unknown-custom-properties": "6.0.1",
"svgo": "4.0.0",
"typescript-eslint": "8.48.1",
"updates": "17.0.4",
"typescript": "5.9.3",
"typescript-eslint": "8.50.0",
"updates": "17.0.7",
"vite-string-plugin": "1.4.9",
"vitest": "4.0.15",
"vue-tsc": "3.1.5"
"vitest": "4.0.16",
"vue-tsc": "3.1.8"
},
"browserslist": [
"defaults"

1345
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -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) {
ret := "---\n"
var ret strings.Builder
ret.WriteString("---\n")
for _, v := range versions {
dep, err := makePackageVersionDependency(ctx, v, c)
if err != nil {
return "", err
}
ret += dep + "\n"
ret.WriteString(dep + "\n")
}
return ret, nil
return ret.String(), nil
}
func makeGemFullFileName(gemName, version, platform string) string {

View File

@ -30,6 +30,7 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/routers/common"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/automerge"
"code.gitea.io/gitea/services/context"
@ -1082,7 +1083,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
} else if len(headInfos) == 2 {
// There is a head repository (the head repository could also be the same base repo)
headRefToGuess = headInfos[1]
headUser, err = user_model.GetUserByName(ctx, headInfos[0])
headUser, err = user_model.GetUserOrOrgByName(ctx, headInfos[0])
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.APIErrorNotFound("GetUserByName")
@ -1098,28 +1099,23 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
isSameRepo := ctx.Repo.Owner.ID == headUser.ID
// Check if current user has fork of repository or in the same repository.
headRepo := repo_model.GetForkedRepo(ctx, headUser.ID, baseRepo.ID)
if headRepo == nil && !isSameRepo {
err = baseRepo.GetBaseRepo(ctx)
var headRepo *repo_model.Repository
if isSameRepo {
headRepo = baseRepo
} else {
headRepo, err = common.FindHeadRepo(ctx, baseRepo, headUser.ID)
if err != nil {
ctx.APIErrorInternal(err)
return nil, nil
}
// Check if baseRepo's base repository is the same as headUser's repository.
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")
if headRepo == nil {
ctx.APIErrorNotFound("head repository not found")
return nil, nil
}
// Assign headRepo so it can be used below.
headRepo = baseRepo.BaseRepo
}
var headGitRepo *git.Repository
if isSameRepo {
headRepo = ctx.Repo.Repository
headGitRepo = ctx.Repo.GitRepo
closer = func() {} // no need to close the head repo because it shares the base repo
} else {
@ -1143,7 +1139,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
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)
ctx.APIErrorNotFound("Can't read pulls or can't read UnitTypeCode")
return nil, nil

View File

@ -4,6 +4,8 @@
package common
import (
"context"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
@ -20,3 +22,54 @@ type CompareInfo struct {
HeadBranch string
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
}

View File

@ -179,11 +179,11 @@ func AuthorizeOAuth(ctx *context.Context) {
errs := binding.Errors{}
errs = form.Validate(ctx.Req, errs)
if len(errs) > 0 {
errstring := ""
var errstring strings.Builder
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
}

View File

@ -259,7 +259,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo {
} else if len(headInfos) == 2 {
headInfosSplit := strings.Split(headInfos[0], "/")
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 user_model.IsErrUserNotExist(err) {
ctx.NotFound(nil)

View File

@ -495,7 +495,7 @@ func preparePullViewSigning(ctx *context.Context, issue *issues_model.Issue) {
pull := issue.PullRequest
ctx.Data["WillSign"] = false
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["SigningKeyMergeDisplay"] = asymkey_model.GetDisplaySigningKey(key)
if err != nil {

View File

@ -1340,6 +1340,17 @@ func CompareAndPullRequestPost(ctx *context.Context) {
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
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 {

View File

@ -169,7 +169,7 @@ Loop:
}
// 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)
signingKey, sig := gitrepo.GetSigningKey(ctx)
if signingKey == nil {
@ -200,11 +200,6 @@ Loop:
return false, nil, nil, &ErrWontSign{twofa}
}
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")
if err != nil {
return false, nil, nil, err
@ -222,7 +217,7 @@ Loop:
}
// 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)
signingKey, sig := git.GetSigningKey(ctx)
if signingKey == nil {
@ -253,11 +248,6 @@ Loop:
return false, nil, nil, &ErrWontSign{twofa}
}
case parentSigned:
gitRepo, err := git.OpenRepository(ctx, tmpBasePath)
if err != nil {
return false, nil, nil, err
}
defer gitRepo.Close()
isEmpty, err := gitRepo.IsEmpty()
if err != nil {
return false, nil, nil, err
@ -281,7 +271,7 @@ Loop:
}
// 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 {
log.Error("Unable to get Base Repo for pull request")
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)
var gitRepo *git.Repository
var err error
Loop:
for _, rule := range rules {
switch rule {
@ -332,13 +319,6 @@ Loop:
return false, nil, nil, &ErrWontSign{approved}
}
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)
if err != nil {
return false, nil, nil, err
@ -348,13 +328,6 @@ Loop:
return false, nil, nil, &ErrWontSign{baseSigned}
}
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)
if err != nil {
return false, nil, nil, err
@ -364,13 +337,6 @@ Loop:
return false, nil, nil, &ErrWontSign{headSigned}
}
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)
if err != nil {
return false, nil, nil, err

View File

@ -140,7 +140,13 @@ func PrepareCommitFormOptions(ctx *Context, doer *user_model.User, targetRepo *r
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 := ""
if asymkey_service.IsErrWontSign(err) {
wontSignReason = string(err.(*asymkey_service.ErrWontSign).Reason)

View File

@ -358,6 +358,11 @@ func (g *RepositoryDumper) SyncTags(ctx context.Context) error {
return nil
}
// SyncBranches syncs branches in the database
func (g *RepositoryDumper) SyncBranches(ctx context.Context) error {
return nil
}
// CreateIssues creates issues
func (g *RepositoryDumper) CreateIssues(_ context.Context, issues ...*base.Issue) error {
var err error

View File

@ -368,6 +368,11 @@ func (g *GiteaLocalUploader) SyncTags(ctx context.Context) error {
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
func (g *GiteaLocalUploader) CreateIssues(ctx context.Context, issues ...*base.Issue) error {
iss := make([]*issues_model.Issue, 0, len(issues))

View File

@ -5,9 +5,6 @@
package migrations
import (
"fmt"
"os"
"path/filepath"
"strconv"
"testing"
"time"
@ -17,15 +14,10 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
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/log"
base "code.gitea.io/gitea/modules/migration"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
repo_service "code.gitea.io/gitea/services/repository"
"github.com/stretchr/testify/assert"
@ -228,297 +220,3 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) {
assert.NoError(t, err)
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)
}
})
}
}

View File

@ -478,6 +478,15 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba
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 {

View File

@ -232,7 +232,13 @@ func isSignedIfRequired(ctx context.Context, pr *issues_model.PullRequest, doer
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
}

View File

@ -84,7 +84,7 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque
if err != nil {
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)
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 {
defer cancel()
@ -105,8 +105,15 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque
mergeCtx.sig = doer.NewGitSig()
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
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 {
mergeCtx.signKey = key
if pr.BaseRepo.GetTrustModel() == repo_model.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {

View File

@ -12,6 +12,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs"
"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
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
}
message := strings.TrimSpace(opts.Message)

View File

@ -13,6 +13,7 @@ import (
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/log"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
@ -52,7 +53,7 @@ type ApplyDiffPatchOptions struct {
}
// 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 opts.OldBranch == "" {
opts.OldBranch = repo.DefaultBranch
@ -95,7 +96,7 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode
}
}
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 !asymkey_service.IsErrWontSign(err) {
return err
@ -116,7 +117,13 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
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
}

View File

@ -303,7 +303,7 @@ func (t *TemporaryUploadRepository) CommitTree(ctx context.Context, opts *Commit
var key *git.SigningKey
var signer *git.Signature
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 {
sign, key, signer, _ = asymkey_service.SignInitialCommit(ctx, opts.DoerUser)
}

View File

@ -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
}
@ -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
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)
if err != nil {
return err
@ -686,7 +686,7 @@ func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, do
}
}
if protectedBranch.RequireSignedCommits {
_, _, _, err := asymkey_service.SignCRUDAction(ctx, doer, repo.RepoPath(), branchName)
_, _, _, err := asymkey_service.SignCRUDAction(ctx, doer, gitRepo, branchName)
if err != nil {
if !asymkey_service.IsErrWontSign(err) {
return err

View File

@ -72,22 +72,22 @@ func (dc dingtalkConvertor) Push(p *api.PushPayload) (DingtalkPayload, error) {
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 i, commit := range p.Commits {
var authorName string
if commit.Author != nil {
authorName = " - " + commit.Author.Name
}
text += fmt.Sprintf("[%s](%s) %s", commit.ID[:7], commit.URL,
strings.TrimRight(commit.Message, "\r\n")) + authorName
text.WriteString(fmt.Sprintf("[%s](%s) %s", commit.ID[:7], commit.URL,
strings.TrimRight(commit.Message, "\r\n")) + authorName)
// add linebreak to each commit but the last
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

View File

@ -159,7 +159,7 @@ func (d discordConvertor) Push(p *api.PushPayload) (DiscordPayload, error) {
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 i, commit := range p.Commits {
// 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 {
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
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

View File

@ -76,22 +76,23 @@ func (fc feishuConvertor) Push(p *api.PushPayload) (FeishuPayload, error) {
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 i, commit := range p.Commits {
var authorName string
if commit.Author != nil {
authorName = " - " + commit.Author.Name
}
text += fmt.Sprintf("[%s](%s) %s", commit.ID[:7], commit.URL,
strings.TrimRight(commit.Message, "\r\n")) + authorName
text.WriteString(fmt.Sprintf("[%s](%s) %s", commit.ID[:7], commit.URL,
strings.TrimRight(commit.Message, "\r\n")) + authorName)
// add linebreak to each commit but the last
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

View File

@ -173,18 +173,19 @@ func (m matrixConvertor) Push(p *api.PushPayload) (MatrixPayload, error) {
repoLink := htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName)
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 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
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

View File

@ -131,14 +131,14 @@ func (m msteamsConvertor) Push(p *api.PushPayload) (MSTeamsPayload, error) {
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 i, commit := range p.Commits {
text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL,
strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name)
text.WriteString(fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL,
strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name))
// add linebreak to each commit but the last
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.Sender,
title,
text,
text.String(),
titleLink,
greenColor,
&MSTeamsFact{"Commit count:", strconv.Itoa(p.TotalCommits)},

View File

@ -208,13 +208,13 @@ func (s slackConvertor) Push(p *api.PushPayload) (SlackPayload, error) {
branchLink := SlackLinkToRef(p.Repo.HTMLURL, p.Ref)
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 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
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,
Title: p.Repo.HTMLURL,
TitleLink: p.Repo.HTMLURL,
Text: attachmentText,
Text: attachmentText.String(),
}}), nil
}

View File

@ -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))
var htmlCommits string
var htmlCommits strings.Builder
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 {
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

View File

@ -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)
var text string
var text strings.Builder
// for each commit, generate attachment text
for i, commit := range p.Commits {
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")
text += fmt.Sprintf(" > [%s](%s) \r\n ><font color=\"info\">%s</font> \n ><font color=\"warning\">%s</font>", commit.ID[:7], commit.URL,
message, authorName)
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))
// add linebreak to each commit but the last
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

View File

@ -193,7 +193,13 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
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 {
commitTreeOpts.Key = signingKey
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
}
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),
Env: repo_module.FullPushingEnvironment(
doer,
@ -315,7 +321,13 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
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 {
commitTreeOpts.Key = signingKey
if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {

View File

@ -10,18 +10,25 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
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"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
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/structs"
"code.gitea.io/gitea/services/migrations"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
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)
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))
}

View File

@ -4,6 +4,7 @@
package integration
import (
"encoding/base64"
"fmt"
"net/http"
"net/http/httptest"
@ -17,7 +18,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"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/util"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
@ -153,8 +156,16 @@ func TestPullCreate(t *testing.T) {
url := test.RedirectURL(resp)
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
req := NewRequest(t, "GET", url+".diff")
req = NewRequest(t, "GET", url+".diff")
resp = session.MakeRequest(t, req, http.StatusOK)
assert.Regexp(t, `\+Hello, World \(Edited\)`, 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) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
sessionFork := loginUser(t, "user1")