mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Give user a link to create PR after push (#4716)
* Give user a link to create PR after push * Forks now create PR in the base repository + make sure PR creation is allowed * fix code style
This commit is contained in:
		
							
								
								
									
										43
									
								
								cmd/hook.go
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								cmd/hook.go
									
									
									
									
									
								
							@@ -8,6 +8,7 @@ import (
 | 
				
			|||||||
	"bufio"
 | 
						"bufio"
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
@@ -174,6 +175,7 @@ func runHookPostReceive(c *cli.Context) error {
 | 
				
			|||||||
	hookSetup("hooks/post-receive.log")
 | 
						hookSetup("hooks/post-receive.log")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// the environment setted on serv command
 | 
						// the environment setted on serv command
 | 
				
			||||||
 | 
						repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64)
 | 
				
			||||||
	repoUser := os.Getenv(models.EnvRepoUsername)
 | 
						repoUser := os.Getenv(models.EnvRepoUsername)
 | 
				
			||||||
	isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
 | 
						isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
 | 
				
			||||||
	repoName := os.Getenv(models.EnvRepoName)
 | 
						repoName := os.Getenv(models.EnvRepoName)
 | 
				
			||||||
@@ -211,6 +213,47 @@ func runHookPostReceive(c *cli.Context) error {
 | 
				
			|||||||
		}); err != nil {
 | 
							}); err != nil {
 | 
				
			||||||
			log.GitLogger.Error(2, "Update: %v", err)
 | 
								log.GitLogger.Error(2, "Update: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if strings.HasPrefix(refFullName, git.BranchPrefix) {
 | 
				
			||||||
 | 
								branch := strings.TrimPrefix(refFullName, git.BranchPrefix)
 | 
				
			||||||
 | 
								repo, pullRequestAllowed, err := private.GetRepository(repoID)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.GitLogger.Error(2, "get repo: %v", err)
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !pullRequestAllowed {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								baseRepo := repo
 | 
				
			||||||
 | 
								if repo.IsFork {
 | 
				
			||||||
 | 
									baseRepo = repo.BaseRepo
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if !repo.IsFork && branch == baseRepo.DefaultBranch {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								pr, err := private.ActivePullRequest(baseRepo.ID, repo.ID, baseRepo.DefaultBranch, branch)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.GitLogger.Error(2, "get active pr: %v", err)
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								fmt.Fprintln(os.Stderr, "")
 | 
				
			||||||
 | 
								if pr == nil {
 | 
				
			||||||
 | 
									if repo.IsFork {
 | 
				
			||||||
 | 
										branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", branch)
 | 
				
			||||||
 | 
									fmt.Fprintf(os.Stderr, "  %s/compare/%s...%s\n", baseRepo.HTMLURL(), url.QueryEscape(baseRepo.DefaultBranch), url.QueryEscape(branch))
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
 | 
				
			||||||
 | 
									fmt.Fprintf(os.Stderr, "  %s/pulls/%d\n", baseRepo.HTMLURL(), pr.Index)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								fmt.Fprintln(os.Stderr, "")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										68
									
								
								modules/private/repository.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								modules/private/repository.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					// Copyright 2018 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetRepository return the repository by its ID and a bool about if it's allowed to have PR
 | 
				
			||||||
 | 
					func GetRepository(repoID int64) (*models.Repository, bool, error) {
 | 
				
			||||||
 | 
						reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repository/%d", repoID)
 | 
				
			||||||
 | 
						log.GitLogger.Trace("GetRepository: %s", reqURL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						resp, err := newInternalRequest(reqURL, "GET").Response()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var repoInfo struct {
 | 
				
			||||||
 | 
							Repository       *models.Repository
 | 
				
			||||||
 | 
							AllowPullRequest bool
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := json.NewDecoder(resp.Body).Decode(&repoInfo); err != nil {
 | 
				
			||||||
 | 
							return nil, false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// All 2XX status codes are accepted and others will return an error
 | 
				
			||||||
 | 
						if resp.StatusCode/100 != 2 {
 | 
				
			||||||
 | 
							return nil, false, fmt.Errorf("failed to retrieve repository: %s", decodeJSONError(resp).Err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return repoInfo.Repository, repoInfo.AllowPullRequest, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ActivePullRequest returns an active pull request if it exists
 | 
				
			||||||
 | 
					func ActivePullRequest(baseRepoID int64, headRepoID int64, baseBranch, headBranch string) (*models.PullRequest, error) {
 | 
				
			||||||
 | 
						reqURL := setting.LocalURL + fmt.Sprintf("api/internal/active-pull-request?baseRepoID=%d&headRepoID=%d&baseBranch=%s&headBranch=%s", baseRepoID, headRepoID, url.QueryEscape(baseBranch), url.QueryEscape(headBranch))
 | 
				
			||||||
 | 
						log.GitLogger.Trace("ActivePullRequest: %s", reqURL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						resp, err := newInternalRequest(reqURL, "GET").Response()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var pr *models.PullRequest
 | 
				
			||||||
 | 
						if err := json.NewDecoder(resp.Body).Decode(&pr); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// All 2XX status codes are accepted and others will return an error
 | 
				
			||||||
 | 
						if resp.StatusCode/100 != 2 {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("failed to retrieve pull request: %s", decodeJSONError(resp).Err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return pr, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -44,5 +44,7 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
				
			|||||||
		m.Post("/push/update", PushUpdate)
 | 
							m.Post("/push/update", PushUpdate)
 | 
				
			||||||
		m.Get("/protectedbranch/:pbid/:userid", CanUserPush)
 | 
							m.Get("/protectedbranch/:pbid/:userid", CanUserPush)
 | 
				
			||||||
		m.Get("/branch/:id/*", GetProtectedBranchBy)
 | 
							m.Get("/branch/:id/*", GetProtectedBranchBy)
 | 
				
			||||||
 | 
							m.Get("/repository/:rid", GetRepository)
 | 
				
			||||||
 | 
							m.Get("/active-pull-request", GetActivePullRequest)
 | 
				
			||||||
	}, CheckInternalToken)
 | 
						}, CheckInternalToken)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										84
									
								
								routers/private/repository.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								routers/private/repository.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					// Copyright 2018 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						macaron "gopkg.in/macaron.v1"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetRepository return the default branch of a repository
 | 
				
			||||||
 | 
					func GetRepository(ctx *macaron.Context) {
 | 
				
			||||||
 | 
						repoID := ctx.ParamsInt64(":rid")
 | 
				
			||||||
 | 
						repository, err := models.GetRepositoryByID(repoID)
 | 
				
			||||||
 | 
						repository.MustOwnerName()
 | 
				
			||||||
 | 
						allowPulls := repository.AllowsPulls()
 | 
				
			||||||
 | 
						// put it back to nil because json unmarshal can't unmarshal it
 | 
				
			||||||
 | 
						repository.Units = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 | 
								"err": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if repository.IsFork {
 | 
				
			||||||
 | 
							repository.GetBaseRepo()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 | 
									"err": err.Error(),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							repository.BaseRepo.MustOwnerName()
 | 
				
			||||||
 | 
							allowPulls = repository.BaseRepo.AllowsPulls()
 | 
				
			||||||
 | 
							// put it back to nil because json unmarshal can't unmarshal it
 | 
				
			||||||
 | 
							repository.BaseRepo.Units = nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.JSON(http.StatusOK, struct {
 | 
				
			||||||
 | 
							Repository       *models.Repository
 | 
				
			||||||
 | 
							AllowPullRequest bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							Repository:       repository,
 | 
				
			||||||
 | 
							AllowPullRequest: allowPulls,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetActivePullRequest return an active pull request when it exists or an empty object
 | 
				
			||||||
 | 
					func GetActivePullRequest(ctx *macaron.Context) {
 | 
				
			||||||
 | 
						baseRepoID := ctx.QueryInt64("baseRepoID")
 | 
				
			||||||
 | 
						headRepoID := ctx.QueryInt64("headRepoID")
 | 
				
			||||||
 | 
						baseBranch, err := url.QueryUnescape(ctx.QueryTrim("baseBranch"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 | 
								"err": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						headBranch, err := url.QueryUnescape(ctx.QueryTrim("headBranch"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 | 
								"err": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pr, err := models.GetUnmergedPullRequest(headRepoID, baseRepoID, headBranch, baseBranch)
 | 
				
			||||||
 | 
						if err != nil && !models.IsErrPullRequestNotExist(err) {
 | 
				
			||||||
 | 
							ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 | 
								"err": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.JSON(http.StatusOK, pr)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user