diff options
author | a1012112796 <1012112796@qq.com> | 2021-07-28 17:42:56 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-28 17:42:56 +0800 |
commit | 370516883717de0e6e2087c12d368eb1465ee3b0 (patch) | |
tree | 1dd7b0946ac2bbeb3b9f13518cf2df2f4ebca348 /routers | |
parent | 5b2e2d29ca50ba4c11a44d4f1de18ffcf215ba1b (diff) | |
download | gitea-370516883717de0e6e2087c12d368eb1465ee3b0.tar.gz gitea-370516883717de0e6e2087c12d368eb1465ee3b0.zip |
Add agit flow support in gitea (#14295)
* feature: add agit flow support
ref: https://git-repo.info/en/2020/03/agit-flow-and-git-repo/
example:
```Bash
git checkout -b test
echo "test" >> README.md
git commit -m "test"
git push origin HEAD:refs/for/master -o topic=test
```
Signed-off-by: a1012112796 <1012112796@qq.com>
* fix lint
* simplify code add fix some nits
* update merge help message
* Apply suggestions from code review. Thanks @jiangxin
* add forced-update message
* fix lint
* splite writePktLine
* add refs/for/<target-branch>/<topic-branch> support also
* Add test code add fix api
* fix lint
* fix test
* skip test if git version < 2.29
* try test with git 2.30.1
* fix permission check bug
* fix some nit
* logic implify and test code update
* fix bug
* apply suggestions from code review
* prepare for merge
Signed-off-by: Andrew Thornton <art27@cantab.net>
* fix permission check bug
- test code update
- apply suggestions from code review @zeripath
Signed-off-by: a1012112796 <1012112796@qq.com>
* fix bug when target branch isn't exist
* prevent some special push and fix some nits
* fix lint
* try splite
* Apply suggestions from code review
- fix permission check
- handle user rename
* fix version negotiation
* remane
* fix template
* handle empty repo
* ui: fix branch link under the title
* fix nits
Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Diffstat (limited to 'routers')
-rw-r--r-- | routers/api/v1/repo/pull.go | 2 | ||||
-rw-r--r-- | routers/private/hook.go | 142 | ||||
-rw-r--r-- | routers/private/internal.go | 1 | ||||
-rw-r--r-- | routers/private/serv.go | 6 | ||||
-rw-r--r-- | routers/web/repo/compare.go | 2 | ||||
-rw-r--r-- | routers/web/repo/http.go | 5 | ||||
-rw-r--r-- | routers/web/repo/issue.go | 3 | ||||
-rw-r--r-- | routers/web/repo/pull.go | 12 | ||||
-rw-r--r-- | routers/web/user/setting/profile.go | 9 | ||||
-rw-r--r-- | routers/web/web.go | 16 |
10 files changed, 192 insertions, 6 deletions
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 66bcabfd38..de166d7ecb 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -310,7 +310,7 @@ func CreatePullRequest(ctx *context.APIContext) { defer headGitRepo.Close() // Check if another PR exists with the same targets - existingPr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch) + existingPr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch, models.PullRequestFlowGithub) if err != nil { if !models.IsErrPullRequestNotExist(err) { ctx.Error(http.StatusInternalServerError, "GetUnmergedPullRequest", err) diff --git a/routers/private/hook.go b/routers/private/hook.go index 9f5579b6ae..4bed86f38a 100644 --- a/routers/private/hook.go +++ b/routers/private/hook.go @@ -23,6 +23,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/agit" pull_service "code.gitea.io/gitea/services/pull" repo_service "code.gitea.io/gitea/services/repository" ) @@ -155,6 +156,56 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { private.GitQuarantinePath+"="+opts.GitQuarantinePath) } + if git.SupportProcReceive { + pusher, err := models.GetUserByID(opts.UserID) + if err != nil { + log.Error("models.GetUserByID:%v", err) + ctx.Error(http.StatusInternalServerError, "") + return + } + + perm, err := models.GetUserRepoPermission(repo, pusher) + if err != nil { + log.Error("models.GetUserRepoPermission:%v", err) + ctx.Error(http.StatusInternalServerError, "") + return + } + + canCreatePullRequest := perm.CanRead(models.UnitTypePullRequests) + + for _, refFullName := range opts.RefFullNames { + // if user want update other refs (branch or tag), + // should check code write permission because + // this check was delayed. + if !strings.HasPrefix(refFullName, git.PullRequestPrefix) { + if !perm.CanWrite(models.UnitTypeCode) { + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": "User permission denied.", + }) + return + } + + break + } else if repo.IsEmpty { + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": "Can't create pull request for an empty repository.", + }) + return + } else if !canCreatePullRequest { + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": "User permission denied.", + }) + return + } else if opts.IsWiki { + // TODO: maybe can do it ... + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": "not support send pull request to wiki.", + }) + return + } + } + } + protectedTags, err := repo.GetProtectedTags() if err != nil { log.Error("Unable to get protected tags for %-v Error: %v", repo, err) @@ -392,11 +443,35 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { }) return } + } else if git.SupportProcReceive && strings.HasPrefix(refFullName, git.PullRequestPrefix) { + baseBranchName := opts.RefFullNames[i][len(git.PullRequestPrefix):] + + baseBranchExist := false + if gitRepo.IsBranchExist(baseBranchName) { + baseBranchExist = true + } + + if !baseBranchExist { + for p, v := range baseBranchName { + if v == '/' && gitRepo.IsBranchExist(baseBranchName[:p]) && p != len(baseBranchName)-1 { + baseBranchExist = true + break + } + } + } + + if !baseBranchExist { + ctx.JSON(http.StatusForbidden, private.Response{ + Err: fmt.Sprintf("Unexpected ref: %s", refFullName), + }) + return + } } else { log.Error("Unexpected ref: %s", refFullName) ctx.JSON(http.StatusInternalServerError, private.Response{ Err: fmt.Sprintf("Unexpected ref: %s", refFullName), }) + return } } @@ -537,7 +612,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { continue } - pr, err := models.GetUnmergedPullRequest(repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch) + pr, err := models.GetUnmergedPullRequest(repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch, models.PullRequestFlowGithub) if err != nil && !models.IsErrPullRequestNotExist(err) { log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err) ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ @@ -574,6 +649,30 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { }) } +// HookProcReceive proc-receive hook +func HookProcReceive(ctx *gitea_context.PrivateContext) { + opts := web.GetForm(ctx).(*private.HookOptions) + if !git.SupportProcReceive { + ctx.Status(http.StatusNotFound) + return + } + + cancel := loadRepositoryAndGitRepoByParams(ctx) + if ctx.Written() { + return + } + defer cancel() + + results := agit.ProcRecive(ctx, opts) + if ctx.Written() { + return + } + + ctx.JSON(http.StatusOK, private.HockProcReceiveResult{ + Results: results, + }) +} + // SetDefaultBranch updates the default branch func SetDefaultBranch(ctx *gitea_context.PrivateContext) { ownerName := ctx.Params(":owner") @@ -618,3 +717,44 @@ func SetDefaultBranch(ctx *gitea_context.PrivateContext) { } ctx.PlainText(http.StatusOK, []byte("success")) } + +func loadRepositoryAndGitRepoByParams(ctx *gitea_context.PrivateContext) context.CancelFunc { + ownerName := ctx.Params(":owner") + repoName := ctx.Params(":repo") + + repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) + if err != nil { + log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "Err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err), + }) + return nil + } + if repo.OwnerName == "" { + repo.OwnerName = ownerName + } + + gitRepo, err := git.OpenRepository(repo.RepoPath()) + if err != nil { + log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "Err": fmt.Sprintf("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err), + }) + return nil + } + + ctx.Repo = &gitea_context.Repository{ + Repository: repo, + GitRepo: gitRepo, + } + + // We opened it, we should close it + cancel := func() { + // If it's been set to nil then assume someone else has closed it. + if ctx.Repo.GitRepo != nil { + ctx.Repo.GitRepo.Close() + } + } + + return cancel +} diff --git a/routers/private/internal.go b/routers/private/internal.go index 9202e67218..155e8c036b 100644 --- a/routers/private/internal.go +++ b/routers/private/internal.go @@ -58,6 +58,7 @@ func Routes() *web.Route { r.Post("/ssh/log", bind(private.SSHLogOption{}), SSHLog) r.Post("/hook/pre-receive/{owner}/{repo}", bind(private.HookOptions{}), HookPreReceive) r.Post("/hook/post-receive/{owner}/{repo}", bind(private.HookOptions{}), HookPostReceive) + r.Post("/hook/proc-receive/{owner}/{repo}", bind(private.HookOptions{}), HookProcReceive) r.Post("/hook/set-default-branch/{owner}/{repo}/{branch}", SetDefaultBranch) r.Get("/serv/none/{keyid}", ServNoCommand) r.Get("/serv/command/{keyid}/{owner}/{repo}", ServCommand) diff --git a/routers/private/serv.go b/routers/private/serv.go index 6e39790eb5..f80d16a7f8 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" @@ -288,6 +289,11 @@ func ServCommand(ctx *context.PrivateContext) { return } } else { + // Because of special ref "refs/for" .. , need delay write permission check + if git.SupportProcReceive && unitType == models.UnitTypeCode { + mode = models.AccessModeRead + } + perm, err := models.GetUserRepoPermission(repo, user) if err != nil { log.Error("Unable to get permissions for %-v with key %d in %-v Error: %v", user, key.ID, repo, err) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index e66aa614cb..f405362cf5 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -653,7 +653,7 @@ func CompareDiff(ctx *context.Context) { ctx.Data["HeadTags"] = headTags if ctx.Data["PageIsComparePull"] == true { - pr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch) + pr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch, models.PullRequestFlowGithub) if err != nil { if !models.IsErrPullRequestNotExist(err) { ctx.ServerError("GetUnmergedPullRequest", err) diff --git a/routers/web/repo/http.go b/routers/web/repo/http.go index 3390f026a0..7947776f16 100644 --- a/routers/web/repo/http.go +++ b/routers/web/repo/http.go @@ -198,6 +198,11 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { return } + // Because of special ref "refs/for" .. , need delay write permission check + if git.SupportProcReceive { + accessMode = models.AccessModeRead + } + if !perm.CanAccess(accessMode, unitType) { ctx.HandleText(http.StatusForbidden, "User permission denied") return diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 9639ea8201..8518917828 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -2047,7 +2047,7 @@ func NewComment(ctx *context.Context) { if form.Status == "reopen" && issue.IsPull { pull := issue.PullRequest var err error - pr, err = models.GetUnmergedPullRequest(pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch) + pr, err = models.GetUnmergedPullRequest(pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch, pull.Flow) if err != nil { if !models.IsErrPullRequestNotExist(err) { ctx.ServerError("GetUnmergedPullRequest", err) @@ -2057,6 +2057,7 @@ func NewComment(ctx *context.Context) { // Regenerate patch and test conflict. if pr == nil { + issue.PullRequest.HeadCommitID = "" pull_service.AddToTaskQueue(issue.PullRequest) } } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 703bbd837a..565c645801 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -427,10 +427,18 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare } defer headGitRepo.Close() - headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch) + if pull.Flow == models.PullRequestFlowGithub { + headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch) + } else { + headBranchExist = git.IsReferenceExist(baseGitRepo.Path, pull.GetGitRefName()) + } if headBranchExist { - headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch) + if pull.Flow != models.PullRequestFlowGithub { + headBranchSha, err = baseGitRepo.GetRefCommitID(pull.GetGitRefName()) + } else { + headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch) + } if err != nil { ctx.ServerError("GetBranchCommitID", err) return nil diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 682f920578..bec523509c 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -23,6 +23,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/agit" "code.gitea.io/gitea/services/forms" "github.com/unknwon/i18n" @@ -76,6 +77,14 @@ func HandleUsernameChange(ctx *context.Context, user *models.User, newName strin return err } } + + // update all agit flow pull request header + err := agit.UserNameChanged(user, newName) + if err != nil { + ctx.ServerError("agit.UserNameChanged", err) + return err + } + log.Trace("User name changed: %s -> %s", user.Name, newName) return nil } diff --git a/routers/web/web.go b/routers/web/web.go index 26e6c31a47..a47fd518ac 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/metrics" @@ -146,6 +147,21 @@ func Routes() *web.Route { routes.Get("/metrics", append(common, Metrics)...) } + routes.Get("/ssh_info", func(rw http.ResponseWriter, req *http.Request) { + if !git.SupportProcReceive { + rw.WriteHeader(404) + return + } + rw.Header().Set("content-type", "text/json;charset=UTF-8") + _, err := rw.Write([]byte(`{"type":"gitea","version":1}`)) + if err != nil { + log.Error("fail to write result: err: %v", err) + rw.WriteHeader(500) + return + } + rw.WriteHeader(200) + }) + // Removed: toolbox.Toolboxer middleware will provide debug information which seems unnecessary common = append(common, context.Contexter()) |