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:
		
							parent
							
								
									cabdf84f1f
								
							
						
					
					
						commit
						dea3d849e1
					
				
					 4 changed files with 197 additions and 0 deletions
				
			
		
							
								
								
									
										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) | ||||||
|  | } | ||||||
		Loading…
	
		Reference in a new issue