Skip to content

Commit dea3d84

Browse files
JulienTantlafriks
authored andcommitted
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
1 parent cabdf84 commit dea3d84

File tree

4 files changed

+197
-0
lines changed

4 files changed

+197
-0
lines changed

cmd/hook.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"bufio"
99
"bytes"
1010
"fmt"
11+
"net/url"
1112
"os"
1213
"path/filepath"
1314
"strconv"
@@ -174,6 +175,7 @@ func runHookPostReceive(c *cli.Context) error {
174175
hookSetup("hooks/post-receive.log")
175176

176177
// the environment setted on serv command
178+
repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64)
177179
repoUser := os.Getenv(models.EnvRepoUsername)
178180
isUncyclo := (os.Getenv(models.EnvRepoIsUncyclo) == "true")
179181
repoName := os.Getenv(models.EnvRepoName)
@@ -211,6 +213,47 @@ func runHookPostReceive(c *cli.Context) error {
211213
}); err != nil {
212214
log.GitLogger.Error(2, "Update: %v", err)
213215
}
216+
217+
if strings.HasPrefix(refFullName, git.BranchPrefix) {
218+
branch := strings.TrimPrefix(refFullName, git.BranchPrefix)
219+
repo, pullRequestAllowed, err := private.GetRepository(repoID)
220+
if err != nil {
221+
log.GitLogger.Error(2, "get repo: %v", err)
222+
break
223+
}
224+
if !pullRequestAllowed {
225+
break
226+
}
227+
228+
baseRepo := repo
229+
if repo.IsFork {
230+
baseRepo = repo.BaseRepo
231+
}
232+
233+
if !repo.IsFork && branch == baseRepo.DefaultBranch {
234+
break
235+
}
236+
237+
pr, err := private.ActivePullRequest(baseRepo.ID, repo.ID, baseRepo.DefaultBranch, branch)
238+
if err != nil {
239+
log.GitLogger.Error(2, "get active pr: %v", err)
240+
break
241+
}
242+
243+
fmt.Fprintln(os.Stderr, "")
244+
if pr == nil {
245+
if repo.IsFork {
246+
branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch)
247+
}
248+
fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", branch)
249+
fmt.Fprintf(os.Stderr, " %s/compare/%s...%s\n", baseRepo.HTMLURL(), url.QueryEscape(baseRepo.DefaultBranch), url.QueryEscape(branch))
250+
} else {
251+
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
252+
fmt.Fprintf(os.Stderr, " %s/pulls/%d\n", baseRepo.HTMLURL(), pr.Index)
253+
}
254+
fmt.Fprintln(os.Stderr, "")
255+
}
256+
214257
}
215258

216259
return nil

modules/private/repository.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2018 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package private
6+
7+
import (
8+
"encoding/json"
9+
"fmt"
10+
"net/url"
11+
12+
"code.gitea.io/gitea/models"
13+
"code.gitea.io/gitea/modules/log"
14+
"code.gitea.io/gitea/modules/setting"
15+
)
16+
17+
// GetRepository return the repository by its ID and a bool about if it's allowed to have PR
18+
func GetRepository(repoID int64) (*models.Repository, bool, error) {
19+
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repository/%d", repoID)
20+
log.GitLogger.Trace("GetRepository: %s", reqURL)
21+
22+
resp, err := newInternalRequest(reqURL, "GET").Response()
23+
if err != nil {
24+
return nil, false, err
25+
}
26+
27+
var repoInfo struct {
28+
Repository *models.Repository
29+
AllowPullRequest bool
30+
}
31+
if err := json.NewDecoder(resp.Body).Decode(&repoInfo); err != nil {
32+
return nil, false, err
33+
}
34+
35+
defer resp.Body.Close()
36+
37+
// All 2XX status codes are accepted and others will return an error
38+
if resp.StatusCode/100 != 2 {
39+
return nil, false, fmt.Errorf("failed to retrieve repository: %s", decodeJSONError(resp).Err)
40+
}
41+
42+
return repoInfo.Repository, repoInfo.AllowPullRequest, nil
43+
}
44+
45+
// ActivePullRequest returns an active pull request if it exists
46+
func ActivePullRequest(baseRepoID int64, headRepoID int64, baseBranch, headBranch string) (*models.PullRequest, error) {
47+
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))
48+
log.GitLogger.Trace("ActivePullRequest: %s", reqURL)
49+
50+
resp, err := newInternalRequest(reqURL, "GET").Response()
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
var pr *models.PullRequest
56+
if err := json.NewDecoder(resp.Body).Decode(&pr); err != nil {
57+
return nil, err
58+
}
59+
60+
defer resp.Body.Close()
61+
62+
// All 2XX status codes are accepted and others will return an error
63+
if resp.StatusCode/100 != 2 {
64+
return nil, fmt.Errorf("failed to retrieve pull request: %s", decodeJSONError(resp).Err)
65+
}
66+
67+
return pr, nil
68+
}

routers/private/internal.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,7 @@ func RegisterRoutes(m *macaron.Macaron) {
4444
m.Post("/push/update", PushUpdate)
4545
m.Get("/protectedbranch/:pbid/:userid", CanUserPush)
4646
m.Get("/branch/:id/*", GetProtectedBranchBy)
47+
m.Get("/repository/:rid", GetRepository)
48+
m.Get("/active-pull-request", GetActivePullRequest)
4749
}, CheckInternalToken)
4850
}

routers/private/repository.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2018 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package private
6+
7+
import (
8+
"net/http"
9+
"net/url"
10+
11+
"code.gitea.io/gitea/models"
12+
13+
macaron "gopkg.in/macaron.v1"
14+
)
15+
16+
// GetRepository return the default branch of a repository
17+
func GetRepository(ctx *macaron.Context) {
18+
repoID := ctx.ParamsInt64(":rid")
19+
repository, err := models.GetRepositoryByID(repoID)
20+
repository.MustOwnerName()
21+
allowPulls := repository.AllowsPulls()
22+
// put it back to nil because json unmarshal can't unmarshal it
23+
repository.Units = nil
24+
25+
if err != nil {
26+
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
27+
"err": err.Error(),
28+
})
29+
return
30+
}
31+
32+
if repository.IsFork {
33+
repository.GetBaseRepo()
34+
if err != nil {
35+
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
36+
"err": err.Error(),
37+
})
38+
return
39+
}
40+
repository.BaseRepo.MustOwnerName()
41+
allowPulls = repository.BaseRepo.AllowsPulls()
42+
// put it back to nil because json unmarshal can't unmarshal it
43+
repository.BaseRepo.Units = nil
44+
}
45+
46+
ctx.JSON(http.StatusOK, struct {
47+
Repository *models.Repository
48+
AllowPullRequest bool
49+
}{
50+
Repository: repository,
51+
AllowPullRequest: allowPulls,
52+
})
53+
}
54+
55+
// GetActivePullRequest return an active pull request when it exists or an empty object
56+
func GetActivePullRequest(ctx *macaron.Context) {
57+
baseRepoID := ctx.QueryInt64("baseRepoID")
58+
headRepoID := ctx.QueryInt64("headRepoID")
59+
baseBranch, err := url.QueryUnescape(ctx.QueryTrim("baseBranch"))
60+
if err != nil {
61+
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
62+
"err": err.Error(),
63+
})
64+
return
65+
}
66+
67+
headBranch, err := url.QueryUnescape(ctx.QueryTrim("headBranch"))
68+
if err != nil {
69+
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
70+
"err": err.Error(),
71+
})
72+
return
73+
}
74+
75+
pr, err := models.GetUnmergedPullRequest(headRepoID, baseRepoID, headBranch, baseBranch)
76+
if err != nil && !models.IsErrPullRequestNotExist(err) {
77+
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
78+
"err": err.Error(),
79+
})
80+
return
81+
}
82+
83+
ctx.JSON(http.StatusOK, pr)
84+
}

0 commit comments

Comments
 (0)