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>
This commit is contained in:
parent
5b2e2d29ca
commit
3705168837
30 changed files with 1334 additions and 32 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue