mirror of
https://github.com/go-gitea/gitea.git
synced 2025-02-20 11:43:57 +08:00
agit flow add refs/for-review/<pull index> support
Signed-off-by: a1012112796 <1012112796@qq.com>
This commit is contained in:
parent
9000811118
commit
70d2872ff2
@ -181,7 +181,8 @@ func runServ(c *cli.Context) error {
|
||||
if git.DefaultFeatures().SupportProcReceive {
|
||||
// for AGit Flow
|
||||
if cmd == "ssh_info" {
|
||||
fmt.Print(`{"type":"gitea","version":1}`)
|
||||
data := private.GetSshInfo(ctx)
|
||||
fmt.Print(data)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -259,10 +259,16 @@ func syncGitConfig() (err error) {
|
||||
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := configAddNonExist("receive.procReceiveRefs", "refs/for-review"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := configUnsetAll("receive.procReceiveRefs", "refs/for-review"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user.
|
||||
|
@ -67,7 +67,8 @@ func (ref *Reference) RefGroup() string {
|
||||
// or refs/for/<targe-branch> -o topic='<topic-branch>'
|
||||
const ForPrefix = "refs/for/"
|
||||
|
||||
// TODO: /refs/for-review for suggest change interface
|
||||
// ForReviewPrefix special ref to update a pull request: refs/for-review/<pull index>
|
||||
const ForReviewPrefix = "refs/for-review/"
|
||||
|
||||
// RefName represents a full git reference name
|
||||
type RefName string
|
||||
@ -104,6 +105,12 @@ func (ref RefName) IsFor() bool {
|
||||
return strings.HasPrefix(string(ref), ForPrefix)
|
||||
}
|
||||
|
||||
var forReviewPattern = regexp.MustCompile(ForReviewPrefix + `[1-9][0-9]*`)
|
||||
|
||||
func (ref RefName) IsForReview() bool {
|
||||
return forReviewPattern.MatchString(string(ref))
|
||||
}
|
||||
|
||||
func (ref RefName) nameWithoutPrefix(prefix string) string {
|
||||
if strings.HasPrefix(string(ref), prefix) {
|
||||
return strings.TrimPrefix(string(ref), prefix)
|
||||
|
@ -36,6 +36,29 @@ func ReloadTemplates(ctx context.Context) ResponseExtra {
|
||||
return requestJSONClientMsg(req, "Reloaded")
|
||||
}
|
||||
|
||||
// Shutdown calls the internal shutdown function
|
||||
func GetSshInfo(ctx context.Context) string {
|
||||
reqURL := setting.LocalURL + "ssh_info"
|
||||
req := newInternalRequest(ctx, reqURL, "GET")
|
||||
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return ""
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(content)
|
||||
}
|
||||
|
||||
// FlushOptions represents the options for the flush call
|
||||
type FlushOptions struct {
|
||||
Timeout time.Duration
|
||||
|
@ -7,6 +7,8 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
@ -124,6 +126,8 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
|
||||
preReceiveTag(ourCtx, refFullName)
|
||||
case git.DefaultFeatures().SupportProcReceive && refFullName.IsFor():
|
||||
preReceiveFor(ourCtx, refFullName)
|
||||
case git.DefaultFeatures().SupportProcReceive && refFullName.IsForReview():
|
||||
preReceiveForReview(ourCtx, refFullName)
|
||||
default:
|
||||
ourCtx.AssertCanWriteCode()
|
||||
}
|
||||
@ -447,6 +451,77 @@ func preReceiveFor(ctx *preReceiveContext, refFullName git.RefName) {
|
||||
}
|
||||
}
|
||||
|
||||
func canUpdateAgitPull(ctx *preReceiveContext, pull *issues_model.PullRequest) bool {
|
||||
if pull.Flow != issues_model.PullRequestFlowAGit {
|
||||
return false
|
||||
}
|
||||
|
||||
if ctx.opts.UserID == pull.Issue.PosterID {
|
||||
return true
|
||||
}
|
||||
|
||||
if !pull.AllowMaintainerEdit {
|
||||
return false
|
||||
}
|
||||
|
||||
if !ctx.loadPusherAndPermission() {
|
||||
return false
|
||||
}
|
||||
|
||||
return ctx.userPerm.CanWrite(unit.TypeCode)
|
||||
}
|
||||
|
||||
func preReceiveForReview(ctx *preReceiveContext, refFullName git.RefName) {
|
||||
if !ctx.AssertCreatePullRequest() {
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
ctx.JSON(http.StatusForbidden, private.Response{
|
||||
UserMsg: "Can't create pull request for an empty repository.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.opts.IsWiki {
|
||||
ctx.JSON(http.StatusForbidden, private.Response{
|
||||
UserMsg: "Pull requests are not supported on the wiki.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
pullIndex, err := strconv.ParseInt(strings.TrimPrefix(string(refFullName), git.ForReviewPrefix), 10, 64)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusForbidden, private.Response{
|
||||
UserMsg: "Unknow pull request index.",
|
||||
})
|
||||
return
|
||||
}
|
||||
pull, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, pullIndex)
|
||||
if err != nil {
|
||||
log.Error("preReceiveForReview: GetPullRequestByIndex: err: %v", err)
|
||||
ctx.JSON(http.StatusForbidden, private.Response{
|
||||
UserMsg: "Unknow pull request index.",
|
||||
})
|
||||
return
|
||||
}
|
||||
err = pull.LoadIssue(ctx)
|
||||
if err != nil {
|
||||
log.Error("preReceiveForReview: pull.LoadIssue: err: %v", err)
|
||||
ctx.JSON(http.StatusForbidden, private.Response{
|
||||
UserMsg: "Unknow pull request.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if !canUpdateAgitPull(ctx, pull) {
|
||||
ctx.JSON(http.StatusForbidden, private.Response{
|
||||
UserMsg: "Unknow pull request.",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func generateGitEnv(opts *private.HookOptions) (env []string) {
|
||||
env = os.Environ()
|
||||
if opts.GitAlternativeObjectDirectories != "" {
|
||||
|
@ -20,7 +20,7 @@ func SSHInfo(rw http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
rw.Header().Set("content-type", "text/json;charset=UTF-8")
|
||||
_, err := rw.Write([]byte(`{"type":"gitea","version":1}`))
|
||||
_, err := rw.Write([]byte(`{"type":"gitea","version":2}`))
|
||||
if err != nil {
|
||||
log.Error("fail to write result: err: %v", err)
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
|
@ -36,6 +36,75 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
|
||||
return nil, fmt.Errorf("failed to get user. Error: %w", err)
|
||||
}
|
||||
|
||||
updateExistPull := func(pr *issues_model.PullRequest, i int) error {
|
||||
// update exist pull request
|
||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
||||
return fmt.Errorf("unable to load base repository for PR[%d] Error: %w", pr.ID, err)
|
||||
}
|
||||
|
||||
oldCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get ref commit id in base repository for PR[%d] Error: %w", pr.ID, err)
|
||||
}
|
||||
|
||||
if oldCommitID == opts.NewCommitIDs[i] {
|
||||
results = append(results, private.HookProcReceiveRefResult{
|
||||
OriginalRef: opts.RefFullNames[i],
|
||||
OldOID: opts.OldCommitIDs[i],
|
||||
NewOID: opts.NewCommitIDs[i],
|
||||
Err: "new commit is same with old commit",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
if !forcePush {
|
||||
output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").
|
||||
AddDynamicArguments(oldCommitID, "^"+opts.NewCommitIDs[i]).
|
||||
RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: os.Environ()})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to detect force push: %w", err)
|
||||
} else if len(output) > 0 {
|
||||
results = append(results, private.HookProcReceiveRefResult{
|
||||
OriginalRef: opts.RefFullNames[i],
|
||||
OldOID: opts.OldCommitIDs[i],
|
||||
NewOID: opts.NewCommitIDs[i],
|
||||
Err: "request `force-push` push option",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
pr.HeadCommitID = opts.NewCommitIDs[i]
|
||||
if err = pull_service.UpdateRef(ctx, pr); err != nil {
|
||||
return fmt.Errorf("failed to update pull ref. Error: %w", err)
|
||||
}
|
||||
|
||||
pull_service.AddToTaskQueue(ctx, pr)
|
||||
err = pr.LoadIssue(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load pull issue. Error: %w", err)
|
||||
}
|
||||
comment, err := pull_service.CreatePushPullComment(ctx, pusher, pr, oldCommitID, opts.NewCommitIDs[i])
|
||||
if err == nil && comment != nil {
|
||||
notify_service.PullRequestPushCommits(ctx, pusher, pr, comment)
|
||||
}
|
||||
notify_service.PullRequestSynchronized(ctx, pusher, pr)
|
||||
isForcePush := comment != nil && comment.IsForcePush
|
||||
|
||||
results = append(results, private.HookProcReceiveRefResult{
|
||||
OldOID: oldCommitID,
|
||||
NewOID: opts.NewCommitIDs[i],
|
||||
Ref: pr.GetGitRefName(),
|
||||
OriginalRef: opts.RefFullNames[i],
|
||||
IsForcePush: isForcePush,
|
||||
IsCreatePR: false,
|
||||
URL: fmt.Sprintf("%s/pulls/%d", repo.HTMLURL(), pr.Index),
|
||||
ShouldShowMessage: setting.Git.PullRequestPushMessage && repo.AllowsPulls(ctx),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := range opts.OldCommitIDs {
|
||||
if opts.NewCommitIDs[i] == objectFormat.EmptyObjectID().String() {
|
||||
results = append(results, private.HookProcReceiveRefResult{
|
||||
@ -47,6 +116,37 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
|
||||
continue
|
||||
}
|
||||
|
||||
if opts.RefFullNames[i].IsForReview() {
|
||||
// try match refs/for-review/<pull index>
|
||||
pullIndex, err := strconv.ParseInt(strings.TrimPrefix(string(opts.RefFullNames[i]), git.ForReviewPrefix), 10, 64)
|
||||
if err != nil {
|
||||
results = append(results, private.HookProcReceiveRefResult{
|
||||
OriginalRef: opts.RefFullNames[i],
|
||||
OldOID: opts.OldCommitIDs[i],
|
||||
NewOID: opts.NewCommitIDs[i],
|
||||
Err: "Unknow pull request index",
|
||||
})
|
||||
continue
|
||||
}
|
||||
pull, err := issues_model.GetPullRequestByIndex(ctx, repo.ID, pullIndex)
|
||||
if err != nil {
|
||||
results = append(results, private.HookProcReceiveRefResult{
|
||||
OriginalRef: opts.RefFullNames[i],
|
||||
OldOID: opts.OldCommitIDs[i],
|
||||
NewOID: opts.NewCommitIDs[i],
|
||||
Err: "Unknow pull request index",
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
err = updateExistPull(pull, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !opts.RefFullNames[i].IsFor() {
|
||||
results = append(results, private.HookProcReceiveRefResult{
|
||||
IsNotMatched: true,
|
||||
@ -158,70 +258,10 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
|
||||
continue
|
||||
}
|
||||
|
||||
// update exist pull request
|
||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
||||
return nil, fmt.Errorf("unable to load base repository for PR[%d] Error: %w", pr.ID, err)
|
||||
}
|
||||
|
||||
oldCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
|
||||
err = updateExistPull(pr, i)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get ref commit id in base repository for PR[%d] Error: %w", pr.ID, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if oldCommitID == opts.NewCommitIDs[i] {
|
||||
results = append(results, private.HookProcReceiveRefResult{
|
||||
OriginalRef: opts.RefFullNames[i],
|
||||
OldOID: opts.OldCommitIDs[i],
|
||||
NewOID: opts.NewCommitIDs[i],
|
||||
Err: "new commit is same with old commit",
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if !forcePush {
|
||||
output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").
|
||||
AddDynamicArguments(oldCommitID, "^"+opts.NewCommitIDs[i]).
|
||||
RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: os.Environ()})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to detect force push: %w", err)
|
||||
} else if len(output) > 0 {
|
||||
results = append(results, private.HookProcReceiveRefResult{
|
||||
OriginalRef: opts.RefFullNames[i],
|
||||
OldOID: opts.OldCommitIDs[i],
|
||||
NewOID: opts.NewCommitIDs[i],
|
||||
Err: "request `force-push` push option",
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
pr.HeadCommitID = opts.NewCommitIDs[i]
|
||||
if err = pull_service.UpdateRef(ctx, pr); err != nil {
|
||||
return nil, fmt.Errorf("failed to update pull ref. Error: %w", err)
|
||||
}
|
||||
|
||||
pull_service.AddToTaskQueue(ctx, pr)
|
||||
err = pr.LoadIssue(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load pull issue. Error: %w", err)
|
||||
}
|
||||
comment, err := pull_service.CreatePushPullComment(ctx, pusher, pr, oldCommitID, opts.NewCommitIDs[i])
|
||||
if err == nil && comment != nil {
|
||||
notify_service.PullRequestPushCommits(ctx, pusher, pr, comment)
|
||||
}
|
||||
notify_service.PullRequestSynchronized(ctx, pusher, pr)
|
||||
isForcePush := comment != nil && comment.IsForcePush
|
||||
|
||||
results = append(results, private.HookProcReceiveRefResult{
|
||||
OldOID: oldCommitID,
|
||||
NewOID: opts.NewCommitIDs[i],
|
||||
Ref: pr.GetGitRefName(),
|
||||
OriginalRef: opts.RefFullNames[i],
|
||||
IsForcePush: isForcePush,
|
||||
IsCreatePR: false,
|
||||
URL: fmt.Sprintf("%s/pulls/%d", repo.HTMLURL(), pr.Index),
|
||||
ShouldShowMessage: setting.Git.PullRequestPushMessage && repo.AllowsPulls(ctx),
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
@ -22,6 +22,11 @@ func SetAllowEdits(ctx context.Context, doer *user_model.User, pr *issues_model.
|
||||
return ErrUserHasNoPermissionForAction
|
||||
}
|
||||
|
||||
if doer.ID == pr.Issue.PosterID {
|
||||
pr.AllowMaintainerEdit = allow
|
||||
return issues_model.UpdateAllowEdits(ctx, pr)
|
||||
}
|
||||
|
||||
if err := pr.LoadHeadRepo(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -674,7 +674,7 @@
|
||||
{{end}}
|
||||
|
||||
{{if and .Issue.IsPull .IsIssuePoster (not .Issue.IsClosed) .Issue.PullRequest.HeadRepo}}
|
||||
{{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
|
||||
{{if or (eq .SignedUserID .Issue.PosterID) (and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo)}}
|
||||
<div class="divider"></div>
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox loading-icon-2px" id="allow-edits-from-maintainers"
|
||||
|
@ -844,6 +844,48 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string
|
||||
assert.False(t, prMsg.HasMerged)
|
||||
assert.Equal(t, commit, prMsg.Head.Sha)
|
||||
})
|
||||
|
||||
t.Run("AddCommit3", func(t *testing.T) {
|
||||
err := os.WriteFile(path.Join(dstPath, "test_file_2"), []byte("## test content \n ## test content 2"), 0o666)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
err = git.AddChanges(dstPath, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = git.CommitChanges(dstPath, git.CommitChangesOptions{
|
||||
Committer: &git.Signature{
|
||||
Email: "user2@example.com",
|
||||
Name: "user2",
|
||||
When: time.Now(),
|
||||
},
|
||||
Author: &git.Signature{
|
||||
Email: "user2@example.com",
|
||||
Name: "user2",
|
||||
When: time.Now(),
|
||||
},
|
||||
Message: "Testing commit 3",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
commit, err = gitRepo.GetRefCommitID("HEAD")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("PushByForReview", func(t *testing.T) {
|
||||
err := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments(fmt.Sprintf("HEAD:refs/for-review/%d", pr1.Index)).Run(&git.RunOpts{Dir: dstPath})
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
assert.False(t, prMsg.HasMerged)
|
||||
assert.Equal(t, commit, prMsg.Head.Sha)
|
||||
})
|
||||
|
||||
t.Run("Merge", doAPIMergePullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index))
|
||||
t.Run("CheckoutMasterAgain", doGitCheckoutBranch(dstPath, "master"))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user