mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 20:36:07 +01:00 
			
		
		
		
	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:
		
							
								
								
									
										346
									
								
								cmd/hook.go
									
									
									
									
									
								
							
							
						
						
									
										346
									
								
								cmd/hook.go
									
									
									
									
									
								
							@@ -38,6 +38,7 @@ var (
 | 
				
			|||||||
			subcmdHookPreReceive,
 | 
								subcmdHookPreReceive,
 | 
				
			||||||
			subcmdHookUpdate,
 | 
								subcmdHookUpdate,
 | 
				
			||||||
			subcmdHookPostReceive,
 | 
								subcmdHookPostReceive,
 | 
				
			||||||
 | 
								subcmdHookProcReceive,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -74,6 +75,18 @@ var (
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						// Note: new hook since git 2.29
 | 
				
			||||||
 | 
						subcmdHookProcReceive = cli.Command{
 | 
				
			||||||
 | 
							Name:        "proc-receive",
 | 
				
			||||||
 | 
							Usage:       "Delegate proc-receive Git hook",
 | 
				
			||||||
 | 
							Description: "This command should only be called by Git",
 | 
				
			||||||
 | 
							Action:      runHookProcReceive,
 | 
				
			||||||
 | 
							Flags: []cli.Flag{
 | 
				
			||||||
 | 
								cli.BoolFlag{
 | 
				
			||||||
 | 
									Name: "debug",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type delayWriter struct {
 | 
					type delayWriter struct {
 | 
				
			||||||
@@ -205,6 +218,11 @@ Gitea or set your environment appropriately.`, "")
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						supportProcRecive := false
 | 
				
			||||||
 | 
						if git.CheckGitVersionAtLeast("2.29") == nil {
 | 
				
			||||||
 | 
							supportProcRecive = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for scanner.Scan() {
 | 
						for scanner.Scan() {
 | 
				
			||||||
		// TODO: support news feeds for wiki
 | 
							// TODO: support news feeds for wiki
 | 
				
			||||||
		if isWiki {
 | 
							if isWiki {
 | 
				
			||||||
@@ -223,7 +241,9 @@ Gitea or set your environment appropriately.`, "")
 | 
				
			|||||||
		lastline++
 | 
							lastline++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// If the ref is a branch or tag, check if it's protected
 | 
							// If the ref is a branch or tag, check if it's protected
 | 
				
			||||||
		if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
 | 
							// if supportProcRecive all ref should be checked because
 | 
				
			||||||
 | 
							// permission check was delayed
 | 
				
			||||||
 | 
							if supportProcRecive || strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
 | 
				
			||||||
			oldCommitIDs[count] = oldCommitID
 | 
								oldCommitIDs[count] = oldCommitID
 | 
				
			||||||
			newCommitIDs[count] = newCommitID
 | 
								newCommitIDs[count] = newCommitID
 | 
				
			||||||
			refFullNames[count] = refFullName
 | 
								refFullNames[count] = refFullName
 | 
				
			||||||
@@ -463,3 +483,327 @@ func pushOptions() map[string]string {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return opts
 | 
						return opts
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func runHookProcReceive(c *cli.Context) error {
 | 
				
			||||||
 | 
						setup("hooks/proc-receive.log", c.Bool("debug"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
 | 
				
			||||||
 | 
							if setting.OnlyAllowPushIfGiteaEnvironmentSet {
 | 
				
			||||||
 | 
								return fail(`Rejecting changes as Gitea environment not set.
 | 
				
			||||||
 | 
					If you are pushing over SSH you must push with a key managed by
 | 
				
			||||||
 | 
					Gitea or set your environment appropriately.`, "")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx, cancel := installSignals()
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if git.CheckGitVersionAtLeast("2.29") != nil {
 | 
				
			||||||
 | 
							return fail("Internal Server Error", "git not support proc-receive.")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						reader := bufio.NewReader(os.Stdin)
 | 
				
			||||||
 | 
						repoUser := os.Getenv(models.EnvRepoUsername)
 | 
				
			||||||
 | 
						repoName := os.Getenv(models.EnvRepoName)
 | 
				
			||||||
 | 
						pusherID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
 | 
				
			||||||
 | 
						pusherName := os.Getenv(models.EnvPusherName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 1. Version and features negotiation.
 | 
				
			||||||
 | 
						// S: PKT-LINE(version=1\0push-options atomic...) / PKT-LINE(version=1\n)
 | 
				
			||||||
 | 
						// S: flush-pkt
 | 
				
			||||||
 | 
						// H: PKT-LINE(version=1\0push-options...)
 | 
				
			||||||
 | 
						// H: flush-pkt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rs, err := readPktLine(reader, pktLineTypeData)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const VersionHead string = "version=1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							hasPushOptions bool
 | 
				
			||||||
 | 
							response       = []byte(VersionHead)
 | 
				
			||||||
 | 
							requestOptions []string
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						index := bytes.IndexByte(rs.Data, byte(0))
 | 
				
			||||||
 | 
						if index >= len(rs.Data) {
 | 
				
			||||||
 | 
							return fail("Internal Server Error", "pkt-line: format error "+fmt.Sprint(rs.Data))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if index < 0 {
 | 
				
			||||||
 | 
							if len(rs.Data) == 10 && rs.Data[9] == '\n' {
 | 
				
			||||||
 | 
								index = 9
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return fail("Internal Server Error", "pkt-line: format error "+fmt.Sprint(rs.Data))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if string(rs.Data[0:index]) != VersionHead {
 | 
				
			||||||
 | 
							return fail("Internal Server Error", "Received unsupported version: %s", string(rs.Data[0:index]))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						requestOptions = strings.Split(string(rs.Data[index+1:]), " ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, option := range requestOptions {
 | 
				
			||||||
 | 
							if strings.HasPrefix(option, "push-options") {
 | 
				
			||||||
 | 
								response = append(response, byte(0))
 | 
				
			||||||
 | 
								response = append(response, []byte("push-options")...)
 | 
				
			||||||
 | 
								hasPushOptions = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						response = append(response, '\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = readPktLine(reader, pktLineTypeFlush)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = writeDataPktLine(os.Stdout, response)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = writeFlushPktLine(os.Stdout)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 2. receive commands from server.
 | 
				
			||||||
 | 
						// S: PKT-LINE(<old-oid> <new-oid> <ref>)
 | 
				
			||||||
 | 
						// S: ... ...
 | 
				
			||||||
 | 
						// S: flush-pkt
 | 
				
			||||||
 | 
						// # [receive push-options]
 | 
				
			||||||
 | 
						// S: PKT-LINE(push-option)
 | 
				
			||||||
 | 
						// S: ... ...
 | 
				
			||||||
 | 
						// S: flush-pkt
 | 
				
			||||||
 | 
						hookOptions := private.HookOptions{
 | 
				
			||||||
 | 
							UserName: pusherName,
 | 
				
			||||||
 | 
							UserID:   pusherID,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize)
 | 
				
			||||||
 | 
						hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize)
 | 
				
			||||||
 | 
						hookOptions.RefFullNames = make([]string, 0, hookBatchSize)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							// note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed
 | 
				
			||||||
 | 
							rs, err = readPktLine(reader, pktLineTypeUnknow)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if rs.Type == pktLineTypeFlush {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							t := strings.SplitN(string(rs.Data), " ", 3)
 | 
				
			||||||
 | 
							if len(t) != 3 {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							hookOptions.OldCommitIDs = append(hookOptions.OldCommitIDs, t[0])
 | 
				
			||||||
 | 
							hookOptions.NewCommitIDs = append(hookOptions.NewCommitIDs, t[1])
 | 
				
			||||||
 | 
							hookOptions.RefFullNames = append(hookOptions.RefFullNames, t[2])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hookOptions.GitPushOptions = make(map[string]string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if hasPushOptions {
 | 
				
			||||||
 | 
							for {
 | 
				
			||||||
 | 
								rs, err = readPktLine(reader, pktLineTypeUnknow)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if rs.Type == pktLineTypeFlush {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								kv := strings.SplitN(string(rs.Data), "=", 2)
 | 
				
			||||||
 | 
								if len(kv) == 2 {
 | 
				
			||||||
 | 
									hookOptions.GitPushOptions[kv[0]] = kv[1]
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 3. run hook
 | 
				
			||||||
 | 
						resp, err := private.HookProcReceive(ctx, repoUser, repoName, hookOptions)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fail("Internal Server Error", "run proc-receive hook failed :%v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 4. response result to service
 | 
				
			||||||
 | 
						// # a. OK, but has an alternate reference.  The alternate reference name
 | 
				
			||||||
 | 
						// # and other status can be given in option directives.
 | 
				
			||||||
 | 
						// H: PKT-LINE(ok <ref>)
 | 
				
			||||||
 | 
						// H: PKT-LINE(option refname <refname>)
 | 
				
			||||||
 | 
						// H: PKT-LINE(option old-oid <old-oid>)
 | 
				
			||||||
 | 
						// H: PKT-LINE(option new-oid <new-oid>)
 | 
				
			||||||
 | 
						// H: PKT-LINE(option forced-update)
 | 
				
			||||||
 | 
						// H: ... ...
 | 
				
			||||||
 | 
						// H: flush-pkt
 | 
				
			||||||
 | 
						// # b. NO, I reject it.
 | 
				
			||||||
 | 
						// H: PKT-LINE(ng <ref> <reason>)
 | 
				
			||||||
 | 
						// # c. Fall through, let 'receive-pack' to execute it.
 | 
				
			||||||
 | 
						// H: PKT-LINE(ok <ref>)
 | 
				
			||||||
 | 
						// H: PKT-LINE(option fall-through)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, rs := range resp.Results {
 | 
				
			||||||
 | 
							if len(rs.Err) > 0 {
 | 
				
			||||||
 | 
								err = writeDataPktLine(os.Stdout, []byte("ng "+rs.OriginalRef+" "+rs.Err))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if rs.IsNotMatched {
 | 
				
			||||||
 | 
								err = writeDataPktLine(os.Stdout, []byte("ok "+rs.OriginalRef))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								err = writeDataPktLine(os.Stdout, []byte("option fall-through"))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = writeDataPktLine(os.Stdout, []byte("ok "+rs.OriginalRef))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err = writeDataPktLine(os.Stdout, []byte("option refname "+rs.Ref))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if rs.OldOID != git.EmptySHA {
 | 
				
			||||||
 | 
								err = writeDataPktLine(os.Stdout, []byte("option old-oid "+rs.OldOID))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err = writeDataPktLine(os.Stdout, []byte("option new-oid "+rs.NewOID))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if rs.IsForcePush {
 | 
				
			||||||
 | 
								err = writeDataPktLine(os.Stdout, []byte("option forced-update"))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = writeFlushPktLine(os.Stdout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// git PKT-Line api
 | 
				
			||||||
 | 
					// pktLineType message type of pkt-line
 | 
				
			||||||
 | 
					type pktLineType int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// UnKnow type
 | 
				
			||||||
 | 
						pktLineTypeUnknow pktLineType = 0
 | 
				
			||||||
 | 
						// flush-pkt "0000"
 | 
				
			||||||
 | 
						pktLineTypeFlush pktLineType = iota
 | 
				
			||||||
 | 
						// data line
 | 
				
			||||||
 | 
						pktLineTypeData
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// gitPktLine pkt-line api
 | 
				
			||||||
 | 
					type gitPktLine struct {
 | 
				
			||||||
 | 
						Type   pktLineType
 | 
				
			||||||
 | 
						Length uint64
 | 
				
			||||||
 | 
						Data   []byte
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func readPktLine(in *bufio.Reader, requestType pktLineType) (*gitPktLine, error) {
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							err error
 | 
				
			||||||
 | 
							r   *gitPktLine
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// read prefix
 | 
				
			||||||
 | 
						lengthBytes := make([]byte, 4)
 | 
				
			||||||
 | 
						for i := 0; i < 4; i++ {
 | 
				
			||||||
 | 
							lengthBytes[i], err = in.ReadByte()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, fail("Internal Server Error", "Pkt-Line: read stdin failed : %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r = new(gitPktLine)
 | 
				
			||||||
 | 
						r.Length, err = strconv.ParseUint(string(lengthBytes), 16, 32)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fail("Internal Server Error", "Pkt-Line format is wrong :%v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if r.Length == 0 {
 | 
				
			||||||
 | 
							if requestType == pktLineTypeData {
 | 
				
			||||||
 | 
								return nil, fail("Internal Server Error", "Pkt-Line format is wrong")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							r.Type = pktLineTypeFlush
 | 
				
			||||||
 | 
							return r, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if r.Length <= 4 || r.Length > 65520 || requestType == pktLineTypeFlush {
 | 
				
			||||||
 | 
							return nil, fail("Internal Server Error", "Pkt-Line format is wrong")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r.Data = make([]byte, r.Length-4)
 | 
				
			||||||
 | 
						for i := range r.Data {
 | 
				
			||||||
 | 
							r.Data[i], err = in.ReadByte()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, fail("Internal Server Error", "Pkt-Line: read stdin failed : %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r.Type = pktLineTypeData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return r, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func writeFlushPktLine(out io.Writer) error {
 | 
				
			||||||
 | 
						l, err := out.Write([]byte("0000"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if l != 4 {
 | 
				
			||||||
 | 
							return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func writeDataPktLine(out io.Writer, data []byte) error {
 | 
				
			||||||
 | 
						hexchar := []byte("0123456789abcdef")
 | 
				
			||||||
 | 
						hex := func(n uint64) byte {
 | 
				
			||||||
 | 
							return hexchar[(n)&15]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						length := uint64(len(data) + 4)
 | 
				
			||||||
 | 
						tmp := make([]byte, 4)
 | 
				
			||||||
 | 
						tmp[0] = hex(length >> 12)
 | 
				
			||||||
 | 
						tmp[1] = hex(length >> 8)
 | 
				
			||||||
 | 
						tmp[2] = hex(length >> 4)
 | 
				
			||||||
 | 
						tmp[3] = hex(length)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lr, err := out.Write(tmp)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if 4 != lr {
 | 
				
			||||||
 | 
							return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lr, err = out.Write(data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if int(length-4) != lr {
 | 
				
			||||||
 | 
							return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										41
									
								
								cmd/hook_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								cmd/hook_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					// Copyright 2021 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 cmd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestPktLine(t *testing.T) {
 | 
				
			||||||
 | 
						// test read
 | 
				
			||||||
 | 
						s := strings.NewReader("0000")
 | 
				
			||||||
 | 
						r := bufio.NewReader(s)
 | 
				
			||||||
 | 
						result, err := readPktLine(r, pktLineTypeFlush)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						assert.Equal(t, pktLineTypeFlush, result.Type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s = strings.NewReader("0006a\n")
 | 
				
			||||||
 | 
						r = bufio.NewReader(s)
 | 
				
			||||||
 | 
						result, err = readPktLine(r, pktLineTypeData)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						assert.Equal(t, pktLineTypeData, result.Type)
 | 
				
			||||||
 | 
						assert.Equal(t, []byte("a\n"), result.Data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// test write
 | 
				
			||||||
 | 
						w := bytes.NewBuffer([]byte{})
 | 
				
			||||||
 | 
						err = writeFlushPktLine(w)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						assert.Equal(t, []byte("0000"), w.Bytes())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w.Reset()
 | 
				
			||||||
 | 
						err = writeDataPktLine(w, []byte("a\nb"))
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						assert.Equal(t, []byte("0007a\nb"), w.Bytes())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -17,6 +17,7 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/json"
 | 
						"code.gitea.io/gitea/modules/json"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/pprof"
 | 
						"code.gitea.io/gitea/modules/pprof"
 | 
				
			||||||
@@ -146,6 +147,13 @@ func runServ(c *cli.Context) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(words) < 2 {
 | 
						if len(words) < 2 {
 | 
				
			||||||
 | 
							if git.CheckGitVersionAtLeast("2.29") == nil {
 | 
				
			||||||
 | 
								// for AGit Flow
 | 
				
			||||||
 | 
								if cmd == "ssh_info" {
 | 
				
			||||||
 | 
									fmt.Print(`{"type":"gitea","version":1}`)
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		return fail("Too few arguments", "Too few arguments in cmd: %s", cmd)
 | 
							return fail("Too few arguments", "Too few arguments in cmd: %s", cmd)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -70,6 +70,7 @@ func testGit(t *testing.T, u *url.URL) {
 | 
				
			|||||||
		rawTest(t, &httpContext, little, big, littleLFS, bigLFS)
 | 
							rawTest(t, &httpContext, little, big, littleLFS, bigLFS)
 | 
				
			||||||
		mediaTest(t, &httpContext, little, big, littleLFS, bigLFS)
 | 
							mediaTest(t, &httpContext, little, big, littleLFS, bigLFS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "master", "test/head"))
 | 
				
			||||||
		t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath))
 | 
							t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath))
 | 
				
			||||||
		t.Run("CreatePRAndSetManuallyMerged", doCreatePRAndSetManuallyMerged(httpContext, httpContext, dstPath, "master", "test-manually-merge"))
 | 
							t.Run("CreatePRAndSetManuallyMerged", doCreatePRAndSetManuallyMerged(httpContext, httpContext, dstPath, "master", "test-manually-merge"))
 | 
				
			||||||
		t.Run("MergeFork", func(t *testing.T) {
 | 
							t.Run("MergeFork", func(t *testing.T) {
 | 
				
			||||||
@@ -111,6 +112,7 @@ func testGit(t *testing.T, u *url.URL) {
 | 
				
			|||||||
			rawTest(t, &sshContext, little, big, littleLFS, bigLFS)
 | 
								rawTest(t, &sshContext, little, big, littleLFS, bigLFS)
 | 
				
			||||||
			mediaTest(t, &sshContext, little, big, littleLFS, bigLFS)
 | 
								mediaTest(t, &sshContext, little, big, littleLFS, bigLFS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "master", "test/head2"))
 | 
				
			||||||
			t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath))
 | 
								t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath))
 | 
				
			||||||
			t.Run("MergeFork", func(t *testing.T) {
 | 
								t.Run("MergeFork", func(t *testing.T) {
 | 
				
			||||||
				defer PrintCurrentTest(t)()
 | 
									defer PrintCurrentTest(t)()
 | 
				
			||||||
@@ -593,3 +595,162 @@ func doBranchDelete(ctx APITestContext, owner, repo, branch string) func(*testin
 | 
				
			|||||||
		ctx.Session.MakeRequest(t, req, http.StatusOK)
 | 
							ctx.Session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headBranch string) func(t *testing.T) {
 | 
				
			||||||
 | 
						return func(t *testing.T) {
 | 
				
			||||||
 | 
							defer PrintCurrentTest(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// skip this test if git version is low
 | 
				
			||||||
 | 
							if git.CheckGitVersionAtLeast("2.29") != nil {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							gitRepo, err := git.OpenRepository(dstPath)
 | 
				
			||||||
 | 
							if !assert.NoError(t, err) {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer gitRepo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var (
 | 
				
			||||||
 | 
								pr1, pr2 *models.PullRequest
 | 
				
			||||||
 | 
								commit   string
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							repo, err := models.GetRepositoryByOwnerAndName(ctx.Username, ctx.Reponame)
 | 
				
			||||||
 | 
							if !assert.NoError(t, err) {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pullNum := models.GetCount(t, &models.PullRequest{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							t.Run("AddCommit", func(t *testing.T) {
 | 
				
			||||||
 | 
								err := ioutil.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0666)
 | 
				
			||||||
 | 
								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 1",
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								assert.NoError(t, err)
 | 
				
			||||||
 | 
								commit, err = gitRepo.GetRefCommitID("HEAD")
 | 
				
			||||||
 | 
								assert.NoError(t, err)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							t.Run("Push", func(t *testing.T) {
 | 
				
			||||||
 | 
								_, err := git.NewCommand("push", "origin", "HEAD:refs/for/master", "-o", "topic="+headBranch).RunInDir(dstPath)
 | 
				
			||||||
 | 
								if !assert.NoError(t, err) {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								models.AssertCount(t, &models.PullRequest{}, pullNum+1)
 | 
				
			||||||
 | 
								pr1 = models.AssertExistsAndLoadBean(t, &models.PullRequest{
 | 
				
			||||||
 | 
									HeadRepoID: repo.ID,
 | 
				
			||||||
 | 
									Flow:       models.PullRequestFlowAGit,
 | 
				
			||||||
 | 
								}).(*models.PullRequest)
 | 
				
			||||||
 | 
								if !assert.NotEmpty(t, pr1) {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t)
 | 
				
			||||||
 | 
								if !assert.NoError(t, err) {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								assert.Equal(t, "user2/"+headBranch, pr1.HeadBranch)
 | 
				
			||||||
 | 
								assert.Equal(t, false, prMsg.HasMerged)
 | 
				
			||||||
 | 
								assert.Contains(t, "Testing commit 1", prMsg.Body)
 | 
				
			||||||
 | 
								assert.Equal(t, commit, prMsg.Head.Sha)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								_, err = git.NewCommand("push", "origin", "HEAD:refs/for/master/test/"+headBranch).RunInDir(dstPath)
 | 
				
			||||||
 | 
								if !assert.NoError(t, err) {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								models.AssertCount(t, &models.PullRequest{}, pullNum+2)
 | 
				
			||||||
 | 
								pr2 = models.AssertExistsAndLoadBean(t, &models.PullRequest{
 | 
				
			||||||
 | 
									HeadRepoID: repo.ID,
 | 
				
			||||||
 | 
									Index:      pr1.Index + 1,
 | 
				
			||||||
 | 
									Flow:       models.PullRequestFlowAGit,
 | 
				
			||||||
 | 
								}).(*models.PullRequest)
 | 
				
			||||||
 | 
								if !assert.NotEmpty(t, pr2) {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t)
 | 
				
			||||||
 | 
								if !assert.NoError(t, err) {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								assert.Equal(t, "user2/test/"+headBranch, pr2.HeadBranch)
 | 
				
			||||||
 | 
								assert.Equal(t, false, prMsg.HasMerged)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if pr1 == nil || pr2 == nil {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							t.Run("AddCommit2", func(t *testing.T) {
 | 
				
			||||||
 | 
								err := ioutil.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content \n ## test content 2"), 0666)
 | 
				
			||||||
 | 
								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 2",
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								assert.NoError(t, err)
 | 
				
			||||||
 | 
								commit, err = gitRepo.GetRefCommitID("HEAD")
 | 
				
			||||||
 | 
								assert.NoError(t, err)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							t.Run("Push2", func(t *testing.T) {
 | 
				
			||||||
 | 
								_, err := git.NewCommand("push", "origin", "HEAD:refs/for/master", "-o", "topic="+headBranch).RunInDir(dstPath)
 | 
				
			||||||
 | 
								if !assert.NoError(t, err) {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								models.AssertCount(t, &models.PullRequest{}, pullNum+2)
 | 
				
			||||||
 | 
								prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t)
 | 
				
			||||||
 | 
								if !assert.NoError(t, err) {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								assert.Equal(t, false, prMsg.HasMerged)
 | 
				
			||||||
 | 
								assert.Equal(t, commit, prMsg.Head.Sha)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								_, err = git.NewCommand("push", "origin", "HEAD:refs/for/master/test/"+headBranch).RunInDir(dstPath)
 | 
				
			||||||
 | 
								if !assert.NoError(t, err) {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								models.AssertCount(t, &models.PullRequest{}, pullNum+2)
 | 
				
			||||||
 | 
								prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t)
 | 
				
			||||||
 | 
								if !assert.NoError(t, err) {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								assert.Equal(t, false, 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"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -329,6 +329,8 @@ var migrations = []Migration{
 | 
				
			|||||||
	NewMigration("Add key is verified to gpg key", addKeyIsVerified),
 | 
						NewMigration("Add key is verified to gpg key", addKeyIsVerified),
 | 
				
			||||||
	// v189 -> v190
 | 
						// v189 -> v190
 | 
				
			||||||
	NewMigration("Unwrap ldap.Sources", unwrapLDAPSourceCfg),
 | 
						NewMigration("Unwrap ldap.Sources", unwrapLDAPSourceCfg),
 | 
				
			||||||
 | 
						// v190 -> v191
 | 
				
			||||||
 | 
						NewMigration("Add agit flow pull request support", addAgitFlowPullRequest),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetCurrentDBVersion returns the current db version
 | 
					// GetCurrentDBVersion returns the current db version
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										24
									
								
								models/migrations/v190.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								models/migrations/v190.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					// Copyright 2021 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 migrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"xorm.io/xorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func addAgitFlowPullRequest(x *xorm.Engine) error {
 | 
				
			||||||
 | 
						type PullRequestFlow int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type PullRequest struct {
 | 
				
			||||||
 | 
							Flow PullRequestFlow `xorm:"NOT NULL DEFAULT 0"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := x.Sync2(new(PullRequest)); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("sync2: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -38,6 +38,16 @@ const (
 | 
				
			|||||||
	PullRequestStatusEmpty
 | 
						PullRequestStatusEmpty
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PullRequestFlow the flow of pull request
 | 
				
			||||||
 | 
					type PullRequestFlow int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// PullRequestFlowGithub github flow from head branch to base branch
 | 
				
			||||||
 | 
						PullRequestFlowGithub PullRequestFlow = iota
 | 
				
			||||||
 | 
						// PullRequestFlowAGit Agit flow pull request, head branch is not exist
 | 
				
			||||||
 | 
						PullRequestFlowAGit
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PullRequest represents relation between pull request and repositories.
 | 
					// PullRequest represents relation between pull request and repositories.
 | 
				
			||||||
type PullRequest struct {
 | 
					type PullRequest struct {
 | 
				
			||||||
	ID              int64 `xorm:"pk autoincr"`
 | 
						ID              int64 `xorm:"pk autoincr"`
 | 
				
			||||||
@@ -58,6 +68,7 @@ type PullRequest struct {
 | 
				
			|||||||
	BaseRepoID      int64       `xorm:"INDEX"`
 | 
						BaseRepoID      int64       `xorm:"INDEX"`
 | 
				
			||||||
	BaseRepo        *Repository `xorm:"-"`
 | 
						BaseRepo        *Repository `xorm:"-"`
 | 
				
			||||||
	HeadBranch      string
 | 
						HeadBranch      string
 | 
				
			||||||
 | 
						HeadCommitID    string `xorm:"-"`
 | 
				
			||||||
	BaseBranch      string
 | 
						BaseBranch      string
 | 
				
			||||||
	ProtectedBranch *ProtectedBranch `xorm:"-"`
 | 
						ProtectedBranch *ProtectedBranch `xorm:"-"`
 | 
				
			||||||
	MergeBase       string           `xorm:"VARCHAR(40)"`
 | 
						MergeBase       string           `xorm:"VARCHAR(40)"`
 | 
				
			||||||
@@ -69,6 +80,8 @@ type PullRequest struct {
 | 
				
			|||||||
	MergedUnix     timeutil.TimeStamp `xorm:"updated INDEX"`
 | 
						MergedUnix     timeutil.TimeStamp `xorm:"updated INDEX"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	isHeadRepoLoaded bool `xorm:"-"`
 | 
						isHeadRepoLoaded bool `xorm:"-"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Flow PullRequestFlow `xorm:"NOT NULL DEFAULT 0"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MustHeadUserName returns the HeadRepo's username if failed return blank
 | 
					// MustHeadUserName returns the HeadRepo's username if failed return blank
 | 
				
			||||||
@@ -470,11 +483,11 @@ func NewPullRequest(repo *Repository, issue *Issue, labelIDs []int64, uuids []st
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// GetUnmergedPullRequest returns a pull request that is open and has not been merged
 | 
					// GetUnmergedPullRequest returns a pull request that is open and has not been merged
 | 
				
			||||||
// by given head/base and repo/branch.
 | 
					// by given head/base and repo/branch.
 | 
				
			||||||
func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
 | 
					func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string, flow PullRequestFlow) (*PullRequest, error) {
 | 
				
			||||||
	pr := new(PullRequest)
 | 
						pr := new(PullRequest)
 | 
				
			||||||
	has, err := x.
 | 
						has, err := x.
 | 
				
			||||||
		Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
 | 
							Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND flow = ? AND issue.is_closed=?",
 | 
				
			||||||
			headRepoID, headBranch, baseRepoID, baseBranch, false, false).
 | 
								headRepoID, headBranch, baseRepoID, baseBranch, false, flow, false).
 | 
				
			||||||
		Join("INNER", "issue", "issue.id=pull_request.issue_id").
 | 
							Join("INNER", "issue", "issue.id=pull_request.issue_id").
 | 
				
			||||||
		Get(pr)
 | 
							Get(pr)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -491,7 +504,7 @@ func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch
 | 
				
			|||||||
func GetLatestPullRequestByHeadInfo(repoID int64, branch string) (*PullRequest, error) {
 | 
					func GetLatestPullRequestByHeadInfo(repoID int64, branch string) (*PullRequest, error) {
 | 
				
			||||||
	pr := new(PullRequest)
 | 
						pr := new(PullRequest)
 | 
				
			||||||
	has, err := x.
 | 
						has, err := x.
 | 
				
			||||||
		Where("head_repo_id = ? AND head_branch = ?", repoID, branch).
 | 
							Where("head_repo_id = ? AND head_branch = ? AND flow = ?", repoID, branch, PullRequestFlowGithub).
 | 
				
			||||||
		OrderBy("id DESC").
 | 
							OrderBy("id DESC").
 | 
				
			||||||
		Get(pr)
 | 
							Get(pr)
 | 
				
			||||||
	if !has {
 | 
						if !has {
 | 
				
			||||||
@@ -566,6 +579,20 @@ func getPullRequestByIssueID(e Engine, issueID int64) (*PullRequest, error) {
 | 
				
			|||||||
	return pr, pr.loadAttributes(e)
 | 
						return pr, pr.loadAttributes(e)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetAllUnmergedAgitPullRequestByPoster get all unmerged agit flow pull request
 | 
				
			||||||
 | 
					// By poster id.
 | 
				
			||||||
 | 
					func GetAllUnmergedAgitPullRequestByPoster(uid int64) ([]*PullRequest, error) {
 | 
				
			||||||
 | 
						pulls := make([]*PullRequest, 0, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := x.
 | 
				
			||||||
 | 
							Where("has_merged=? AND flow = ? AND issue.is_closed=? AND issue.poster_id=?",
 | 
				
			||||||
 | 
								false, PullRequestFlowAGit, false, uid).
 | 
				
			||||||
 | 
							Join("INNER", "issue", "issue.id=pull_request.issue_id").
 | 
				
			||||||
 | 
							Find(&pulls)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return pulls, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetPullRequestByIssueID returns pull request by given issue ID.
 | 
					// GetPullRequestByIssueID returns pull request by given issue ID.
 | 
				
			||||||
func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) {
 | 
					func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) {
 | 
				
			||||||
	return getPullRequestByIssueID(x, issueID)
 | 
						return getPullRequestByIssueID(x, issueID)
 | 
				
			||||||
@@ -663,6 +690,10 @@ func (pr *PullRequest) GetBaseBranchHTMLURL() string {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// GetHeadBranchHTMLURL returns the HTML URL of the head branch
 | 
					// GetHeadBranchHTMLURL returns the HTML URL of the head branch
 | 
				
			||||||
func (pr *PullRequest) GetHeadBranchHTMLURL() string {
 | 
					func (pr *PullRequest) GetHeadBranchHTMLURL() string {
 | 
				
			||||||
 | 
						if pr.Flow == PullRequestFlowAGit {
 | 
				
			||||||
 | 
							return ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := pr.LoadHeadRepo(); err != nil {
 | 
						if err := pr.LoadHeadRepo(); err != nil {
 | 
				
			||||||
		log.Error("LoadHeadRepo: %v", err)
 | 
							log.Error("LoadHeadRepo: %v", err)
 | 
				
			||||||
		return ""
 | 
							return ""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,8 +51,8 @@ func listPullRequestStatement(baseRepoID int64, opts *PullRequestsOptions) (*xor
 | 
				
			|||||||
func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) {
 | 
					func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) {
 | 
				
			||||||
	prs := make([]*PullRequest, 0, 2)
 | 
						prs := make([]*PullRequest, 0, 2)
 | 
				
			||||||
	return prs, x.
 | 
						return prs, x.
 | 
				
			||||||
		Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ?",
 | 
							Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ? AND flow = ?",
 | 
				
			||||||
			repoID, branch, false, false).
 | 
								repoID, branch, false, false, PullRequestFlowGithub).
 | 
				
			||||||
		Join("INNER", "issue", "issue.id = pull_request.issue_id").
 | 
							Join("INNER", "issue", "issue.id = pull_request.issue_id").
 | 
				
			||||||
		Find(&prs)
 | 
							Find(&prs)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,11 +92,11 @@ func TestPullRequestsOldest(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestGetUnmergedPullRequest(t *testing.T) {
 | 
					func TestGetUnmergedPullRequest(t *testing.T) {
 | 
				
			||||||
	assert.NoError(t, PrepareTestDatabase())
 | 
						assert.NoError(t, PrepareTestDatabase())
 | 
				
			||||||
	pr, err := GetUnmergedPullRequest(1, 1, "branch2", "master")
 | 
						pr, err := GetUnmergedPullRequest(1, 1, "branch2", "master", PullRequestFlowGithub)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	assert.Equal(t, int64(2), pr.ID)
 | 
						assert.Equal(t, int64(2), pr.ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = GetUnmergedPullRequest(1, 9223372036854775807, "branch1", "master")
 | 
						_, err = GetUnmergedPullRequest(1, 9223372036854775807, "branch1", "master", PullRequestFlowGithub)
 | 
				
			||||||
	assert.Error(t, err)
 | 
						assert.Error(t, err)
 | 
				
			||||||
	assert.True(t, IsErrPullRequestNotExist(err))
 | 
						assert.True(t, IsErrPullRequestNotExist(err))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -95,7 +95,25 @@ func ToAPIPullRequest(pr *models.PullRequest) *api.PullRequest {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if pr.HeadRepo != nil {
 | 
						if pr.Flow == models.PullRequestFlowAGit {
 | 
				
			||||||
 | 
							gitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error("OpenRepository[%s]: %v", pr.GetGitRefName(), err)
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer gitRepo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							apiPullRequest.Head.Sha, err = gitRepo.GetRefCommitID(pr.GetGitRefName())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err)
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							apiPullRequest.Head.RepoID = pr.BaseRepoID
 | 
				
			||||||
 | 
							apiPullRequest.Head.Repository = apiPullRequest.Base.Repository
 | 
				
			||||||
 | 
							apiPullRequest.Head.Name = ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if pr.HeadRepo != nil && pr.Flow == models.PullRequestFlowGithub {
 | 
				
			||||||
		apiPullRequest.Head.RepoID = pr.HeadRepo.ID
 | 
							apiPullRequest.Head.RepoID = pr.HeadRepo.ID
 | 
				
			||||||
		apiPullRequest.Head.Repository = ToRepo(pr.HeadRepo, models.AccessModeNone)
 | 
							apiPullRequest.Head.Repository = ToRepo(pr.HeadRepo, models.AccessModeNone)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,6 +37,9 @@ var (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// will be checked on Init
 | 
						// will be checked on Init
 | 
				
			||||||
	goVersionLessThan115 = true
 | 
						goVersionLessThan115 = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// SupportProcReceive version >= 2.29.0
 | 
				
			||||||
 | 
						SupportProcReceive bool
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LocalVersion returns current Git version from shell.
 | 
					// LocalVersion returns current Git version from shell.
 | 
				
			||||||
@@ -183,6 +186,19 @@ func Init(ctx context.Context) error {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if CheckGitVersionAtLeast("2.29") == nil {
 | 
				
			||||||
 | 
							// set support for AGit flow
 | 
				
			||||||
 | 
							if err := checkAndAddConfig("receive.procReceiveRefs", "refs/for"); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							SupportProcReceive = true
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if err := checkAndRemoveConfig("receive.procReceiveRefs", "refs/for"); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							SupportProcReceive = false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if runtime.GOOS == "windows" {
 | 
						if runtime.GOOS == "windows" {
 | 
				
			||||||
		if err := checkAndSetConfig("core.longpaths", "true", true); err != nil {
 | 
							if err := checkAndSetConfig("core.longpaths", "true", true); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
@@ -232,6 +248,51 @@ func checkAndSetConfig(key, defaultValue string, forceToDefault bool) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func checkAndAddConfig(key, value string) error {
 | 
				
			||||||
 | 
						_, stderr, err := process.GetManager().Exec("git.Init(get setting)", GitExecutable, "config", "--get", key, value)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							perr, ok := err.(*process.Error)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return fmt.Errorf("Failed to get git %s(%v) errType %T: %s", key, err, err, stderr)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							eerr, ok := perr.Err.(*exec.ExitError)
 | 
				
			||||||
 | 
							if !ok || eerr.ExitCode() != 1 {
 | 
				
			||||||
 | 
								return fmt.Errorf("Failed to get git %s(%v) errType %T: %s", key, err, err, stderr)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if eerr.ExitCode() == 1 {
 | 
				
			||||||
 | 
								if _, stderr, err = process.GetManager().Exec(fmt.Sprintf("git.Init(set %s)", key), "git", "config", "--global", "--add", key, value); err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("Failed to set git %s(%s): %s", key, err, stderr)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func checkAndRemoveConfig(key, value string) error {
 | 
				
			||||||
 | 
						_, stderr, err := process.GetManager().Exec("git.Init(get setting)", GitExecutable, "config", "--get", key, value)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							perr, ok := err.(*process.Error)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return fmt.Errorf("Failed to get git %s(%v) errType %T: %s", key, err, err, stderr)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							eerr, ok := perr.Err.(*exec.ExitError)
 | 
				
			||||||
 | 
							if !ok || eerr.ExitCode() != 1 {
 | 
				
			||||||
 | 
								return fmt.Errorf("Failed to get git %s(%v) errType %T: %s", key, err, err, stderr)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if eerr.ExitCode() == 1 {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, stderr, err = process.GetManager().Exec(fmt.Sprintf("git.Init(set %s)", key), "git", "config", "--global", "--unset-all", key, value); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Failed to set git %s(%s): %s", key, err, stderr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Fsck verifies the connectivity and validity of the objects in the database
 | 
					// Fsck verifies the connectivity and validity of the objects in the database
 | 
				
			||||||
func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args ...string) error {
 | 
					func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args ...string) error {
 | 
				
			||||||
	// Make sure timeout makes sense.
 | 
						// Make sure timeout makes sense.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,14 @@ import (
 | 
				
			|||||||
// BranchPrefix base dir of the branch information file store on git
 | 
					// BranchPrefix base dir of the branch information file store on git
 | 
				
			||||||
const BranchPrefix = "refs/heads/"
 | 
					const BranchPrefix = "refs/heads/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AGit Flow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PullRequestPrefix sepcial ref to create a pull request: refs/for/<targe-branch>/<topic-branch>
 | 
				
			||||||
 | 
					// or refs/for/<targe-branch> -o topic='<topic-branch>'
 | 
				
			||||||
 | 
					const PullRequestPrefix = "refs/for/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: /refs/for-review for suggest change interface
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsReferenceExist returns true if given reference exists in the repository.
 | 
					// IsReferenceExist returns true if given reference exists in the repository.
 | 
				
			||||||
func IsReferenceExist(repoPath, name string) bool {
 | 
					func IsReferenceExist(repoPath, name string) bool {
 | 
				
			||||||
	_, err := NewCommand("show-ref", "--verify", "--", name).RunInDir(repoPath)
 | 
						_, err := NewCommand("show-ref", "--verify", "--", name).RunInDir(repoPath)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ package private
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
@@ -56,6 +57,7 @@ type HookOptions struct {
 | 
				
			|||||||
	GitPushOptions                  GitPushOptions
 | 
						GitPushOptions                  GitPushOptions
 | 
				
			||||||
	PullRequestID                   int64
 | 
						PullRequestID                   int64
 | 
				
			||||||
	IsDeployKey                     bool
 | 
						IsDeployKey                     bool
 | 
				
			||||||
 | 
						IsWiki                          bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SSHLogOption ssh log options
 | 
					// SSHLogOption ssh log options
 | 
				
			||||||
@@ -79,6 +81,23 @@ type HookPostReceiveBranchResult struct {
 | 
				
			|||||||
	URL     string
 | 
						URL     string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HockProcReceiveResult represents an individual result from ProcReceive
 | 
				
			||||||
 | 
					type HockProcReceiveResult struct {
 | 
				
			||||||
 | 
						Results []HockProcReceiveRefResult
 | 
				
			||||||
 | 
						Err     string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HockProcReceiveRefResult represents an individual result from ProcReceive
 | 
				
			||||||
 | 
					type HockProcReceiveRefResult struct {
 | 
				
			||||||
 | 
						OldOID       string
 | 
				
			||||||
 | 
						NewOID       string
 | 
				
			||||||
 | 
						Ref          string
 | 
				
			||||||
 | 
						OriginalRef  string
 | 
				
			||||||
 | 
						IsForcePush  bool
 | 
				
			||||||
 | 
						IsNotMatched bool
 | 
				
			||||||
 | 
						Err          string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// HookPreReceive check whether the provided commits are allowed
 | 
					// HookPreReceive check whether the provided commits are allowed
 | 
				
			||||||
func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (int, string) {
 | 
					func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (int, string) {
 | 
				
			||||||
	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s",
 | 
						reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s",
 | 
				
			||||||
@@ -130,6 +149,33 @@ func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookO
 | 
				
			|||||||
	return res, ""
 | 
						return res, ""
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HookProcReceive proc-receive hook
 | 
				
			||||||
 | 
					func HookProcReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HockProcReceiveResult, error) {
 | 
				
			||||||
 | 
						reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/proc-receive/%s/%s",
 | 
				
			||||||
 | 
							url.PathEscape(ownerName),
 | 
				
			||||||
 | 
							url.PathEscape(repoName),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						req := newInternalRequest(ctx, reqURL, "POST")
 | 
				
			||||||
 | 
						req = req.Header("Content-Type", "application/json")
 | 
				
			||||||
 | 
						req.SetTimeout(60*time.Second, time.Duration(60+len(opts.OldCommitIDs))*time.Second)
 | 
				
			||||||
 | 
						jsonBytes, _ := json.Marshal(opts)
 | 
				
			||||||
 | 
						req.Body(jsonBytes)
 | 
				
			||||||
 | 
						resp, err := req.Response()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Unable to contact gitea: %v", err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if resp.StatusCode != http.StatusOK {
 | 
				
			||||||
 | 
							return nil, errors.New(decodeJSONError(resp).Err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res := &HockProcReceiveResult{}
 | 
				
			||||||
 | 
						_ = json.NewDecoder(resp.Body).Decode(res)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SetDefaultBranch will set the default branch to the provided branch for the provided repository
 | 
					// SetDefaultBranch will set the default branch to the provided branch for the provided repository
 | 
				
			||||||
func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) error {
 | 
					func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) error {
 | 
				
			||||||
	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/set-default-branch/%s/%s/%s",
 | 
						reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/set-default-branch/%s/%s/%s",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ import (
 | 
				
			|||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
@@ -75,6 +76,14 @@ done
 | 
				
			|||||||
		fmt.Sprintf("#!/usr/bin/env %s\n%s hook --config=%s update $1 $2 $3\n", setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
 | 
							fmt.Sprintf("#!/usr/bin/env %s\n%s hook --config=%s update $1 $2 $3\n", setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
 | 
				
			||||||
		fmt.Sprintf("#!/usr/bin/env %s\n%s hook --config=%s post-receive\n", setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
 | 
							fmt.Sprintf("#!/usr/bin/env %s\n%s hook --config=%s post-receive\n", setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if git.SupportProcReceive {
 | 
				
			||||||
 | 
							hookNames = append(hookNames, "proc-receive")
 | 
				
			||||||
 | 
							hookTpls = append(hookTpls,
 | 
				
			||||||
 | 
								fmt.Sprintf("#!/usr/bin/env %s\n%s hook --config=%s proc-receive\n", setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)))
 | 
				
			||||||
 | 
							giteaHookTpls = append(giteaHookTpls, "")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -310,7 +310,7 @@ func CreatePullRequest(ctx *context.APIContext) {
 | 
				
			|||||||
	defer headGitRepo.Close()
 | 
						defer headGitRepo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check if another PR exists with the same targets
 | 
						// 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 err != nil {
 | 
				
			||||||
		if !models.IsErrPullRequestNotExist(err) {
 | 
							if !models.IsErrPullRequestNotExist(err) {
 | 
				
			||||||
			ctx.Error(http.StatusInternalServerError, "GetUnmergedPullRequest", err)
 | 
								ctx.Error(http.StatusInternalServerError, "GetUnmergedPullRequest", err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/web"
 | 
						"code.gitea.io/gitea/modules/web"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/services/agit"
 | 
				
			||||||
	pull_service "code.gitea.io/gitea/services/pull"
 | 
						pull_service "code.gitea.io/gitea/services/pull"
 | 
				
			||||||
	repo_service "code.gitea.io/gitea/services/repository"
 | 
						repo_service "code.gitea.io/gitea/services/repository"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -155,6 +156,56 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
 | 
				
			|||||||
			private.GitQuarantinePath+"="+opts.GitQuarantinePath)
 | 
								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()
 | 
						protectedTags, err := repo.GetProtectedTags()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error("Unable to get protected tags for %-v Error: %v", repo, err)
 | 
							log.Error("Unable to get protected tags for %-v Error: %v", repo, err)
 | 
				
			||||||
@@ -392,11 +443,35 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
 | 
				
			|||||||
				})
 | 
									})
 | 
				
			||||||
				return
 | 
									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 {
 | 
							} else {
 | 
				
			||||||
			log.Error("Unexpected ref: %s", refFullName)
 | 
								log.Error("Unexpected ref: %s", refFullName)
 | 
				
			||||||
			ctx.JSON(http.StatusInternalServerError, private.Response{
 | 
								ctx.JSON(http.StatusInternalServerError, private.Response{
 | 
				
			||||||
				Err: fmt.Sprintf("Unexpected ref: %s", refFullName),
 | 
									Err: fmt.Sprintf("Unexpected ref: %s", refFullName),
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -537,7 +612,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
 | 
				
			|||||||
				continue
 | 
									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) {
 | 
								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)
 | 
									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{
 | 
									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
 | 
					// SetDefaultBranch updates the default branch
 | 
				
			||||||
func SetDefaultBranch(ctx *gitea_context.PrivateContext) {
 | 
					func SetDefaultBranch(ctx *gitea_context.PrivateContext) {
 | 
				
			||||||
	ownerName := ctx.Params(":owner")
 | 
						ownerName := ctx.Params(":owner")
 | 
				
			||||||
@@ -618,3 +717,44 @@ func SetDefaultBranch(ctx *gitea_context.PrivateContext) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.PlainText(http.StatusOK, []byte("success"))
 | 
						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("/ssh/log", bind(private.SSHLogOption{}), SSHLog)
 | 
				
			||||||
	r.Post("/hook/pre-receive/{owner}/{repo}", bind(private.HookOptions{}), HookPreReceive)
 | 
						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/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.Post("/hook/set-default-branch/{owner}/{repo}/{branch}", SetDefaultBranch)
 | 
				
			||||||
	r.Get("/serv/none/{keyid}", ServNoCommand)
 | 
						r.Get("/serv/none/{keyid}", ServNoCommand)
 | 
				
			||||||
	r.Get("/serv/command/{keyid}/{owner}/{repo}", ServCommand)
 | 
						r.Get("/serv/command/{keyid}/{owner}/{repo}", ServCommand)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/private"
 | 
						"code.gitea.io/gitea/modules/private"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
@@ -288,6 +289,11 @@ func ServCommand(ctx *context.PrivateContext) {
 | 
				
			|||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else {
 | 
							} 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)
 | 
								perm, err := models.GetUserRepoPermission(repo, user)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Error("Unable to get permissions for %-v with key %d in %-v Error: %v", user, key.ID, repo, err)
 | 
									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
 | 
						ctx.Data["HeadTags"] = headTags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if ctx.Data["PageIsComparePull"] == true {
 | 
						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 err != nil {
 | 
				
			||||||
			if !models.IsErrPullRequestNotExist(err) {
 | 
								if !models.IsErrPullRequestNotExist(err) {
 | 
				
			||||||
				ctx.ServerError("GetUnmergedPullRequest", err)
 | 
									ctx.ServerError("GetUnmergedPullRequest", err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -198,6 +198,11 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
 | 
				
			|||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Because of special ref "refs/for" .. , need delay write permission check
 | 
				
			||||||
 | 
								if git.SupportProcReceive {
 | 
				
			||||||
 | 
									accessMode = models.AccessModeRead
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !perm.CanAccess(accessMode, unitType) {
 | 
								if !perm.CanAccess(accessMode, unitType) {
 | 
				
			||||||
				ctx.HandleText(http.StatusForbidden, "User permission denied")
 | 
									ctx.HandleText(http.StatusForbidden, "User permission denied")
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2047,7 +2047,7 @@ func NewComment(ctx *context.Context) {
 | 
				
			|||||||
			if form.Status == "reopen" && issue.IsPull {
 | 
								if form.Status == "reopen" && issue.IsPull {
 | 
				
			||||||
				pull := issue.PullRequest
 | 
									pull := issue.PullRequest
 | 
				
			||||||
				var err error
 | 
									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 err != nil {
 | 
				
			||||||
					if !models.IsErrPullRequestNotExist(err) {
 | 
										if !models.IsErrPullRequestNotExist(err) {
 | 
				
			||||||
						ctx.ServerError("GetUnmergedPullRequest", err)
 | 
											ctx.ServerError("GetUnmergedPullRequest", err)
 | 
				
			||||||
@@ -2057,6 +2057,7 @@ func NewComment(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				// Regenerate patch and test conflict.
 | 
									// Regenerate patch and test conflict.
 | 
				
			||||||
				if pr == nil {
 | 
									if pr == nil {
 | 
				
			||||||
 | 
										issue.PullRequest.HeadCommitID = ""
 | 
				
			||||||
					pull_service.AddToTaskQueue(issue.PullRequest)
 | 
										pull_service.AddToTaskQueue(issue.PullRequest)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -427,10 +427,18 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		defer headGitRepo.Close()
 | 
							defer headGitRepo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if pull.Flow == models.PullRequestFlowGithub {
 | 
				
			||||||
			headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch)
 | 
								headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								headBranchExist = git.IsReferenceExist(baseGitRepo.Path, pull.GetGitRefName())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if headBranchExist {
 | 
							if headBranchExist {
 | 
				
			||||||
 | 
								if pull.Flow != models.PullRequestFlowGithub {
 | 
				
			||||||
 | 
									headBranchSha, err = baseGitRepo.GetRefCommitID(pull.GetGitRefName())
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
				headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch)
 | 
									headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				ctx.ServerError("GetBranchCommitID", err)
 | 
									ctx.ServerError("GetBranchCommitID", err)
 | 
				
			||||||
				return nil
 | 
									return nil
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/web"
 | 
						"code.gitea.io/gitea/modules/web"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/web/middleware"
 | 
						"code.gitea.io/gitea/modules/web/middleware"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/services/agit"
 | 
				
			||||||
	"code.gitea.io/gitea/services/forms"
 | 
						"code.gitea.io/gitea/services/forms"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/unknwon/i18n"
 | 
						"github.com/unknwon/i18n"
 | 
				
			||||||
@@ -76,6 +77,14 @@ func HandleUsernameChange(ctx *context.Context, user *models.User, newName strin
 | 
				
			|||||||
			return err
 | 
								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)
 | 
						log.Trace("User name changed: %s -> %s", user.Name, newName)
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/httpcache"
 | 
						"code.gitea.io/gitea/modules/httpcache"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/metrics"
 | 
						"code.gitea.io/gitea/modules/metrics"
 | 
				
			||||||
@@ -146,6 +147,21 @@ func Routes() *web.Route {
 | 
				
			|||||||
		routes.Get("/metrics", append(common, Metrics)...)
 | 
							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
 | 
						// Removed: toolbox.Toolboxer middleware will provide debug information which seems unnecessary
 | 
				
			||||||
	common = append(common, context.Contexter())
 | 
						common = append(common, context.Contexter())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										288
									
								
								services/agit/agit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								services/agit/agit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,288 @@
 | 
				
			|||||||
 | 
					// Copyright 2021 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 agit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"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/notification"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/private"
 | 
				
			||||||
 | 
						pull_service "code.gitea.io/gitea/services/pull"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ProcRecive handle proc receive work
 | 
				
			||||||
 | 
					func ProcRecive(ctx *context.PrivateContext, opts *private.HookOptions) []private.HockProcReceiveRefResult {
 | 
				
			||||||
 | 
						// TODO: Add more options?
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							topicBranch string
 | 
				
			||||||
 | 
							title       string
 | 
				
			||||||
 | 
							description string
 | 
				
			||||||
 | 
							forcePush   bool
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						results := make([]private.HockProcReceiveRefResult, 0, len(opts.OldCommitIDs))
 | 
				
			||||||
 | 
						repo := ctx.Repo.Repository
 | 
				
			||||||
 | 
						gitRepo := ctx.Repo.GitRepo
 | 
				
			||||||
 | 
						ownerName := ctx.Repo.Repository.OwnerName
 | 
				
			||||||
 | 
						repoName := ctx.Repo.Repository.Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						topicBranch = opts.GitPushOptions["topic"]
 | 
				
			||||||
 | 
						_, forcePush = opts.GitPushOptions["force-push"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := range opts.OldCommitIDs {
 | 
				
			||||||
 | 
							if opts.NewCommitIDs[i] == git.EmptySHA {
 | 
				
			||||||
 | 
								results = append(results, private.HockProcReceiveRefResult{
 | 
				
			||||||
 | 
									OriginalRef: opts.RefFullNames[i],
 | 
				
			||||||
 | 
									OldOID:      opts.OldCommitIDs[i],
 | 
				
			||||||
 | 
									NewOID:      opts.NewCommitIDs[i],
 | 
				
			||||||
 | 
									Err:         "Can't delete not exist branch",
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !strings.HasPrefix(opts.RefFullNames[i], git.PullRequestPrefix) {
 | 
				
			||||||
 | 
								results = append(results, private.HockProcReceiveRefResult{
 | 
				
			||||||
 | 
									IsNotMatched: true,
 | 
				
			||||||
 | 
									OriginalRef:  opts.RefFullNames[i],
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							baseBranchName := opts.RefFullNames[i][len(git.PullRequestPrefix):]
 | 
				
			||||||
 | 
							curentTopicBranch := ""
 | 
				
			||||||
 | 
							if !gitRepo.IsBranchExist(baseBranchName) {
 | 
				
			||||||
 | 
								// try match refs/for/<target-branch>/<topic-branch>
 | 
				
			||||||
 | 
								for p, v := range baseBranchName {
 | 
				
			||||||
 | 
									if v == '/' && gitRepo.IsBranchExist(baseBranchName[:p]) && p != len(baseBranchName)-1 {
 | 
				
			||||||
 | 
										curentTopicBranch = baseBranchName[p+1:]
 | 
				
			||||||
 | 
										baseBranchName = baseBranchName[:p]
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(topicBranch) == 0 && len(curentTopicBranch) == 0 {
 | 
				
			||||||
 | 
								results = append(results, private.HockProcReceiveRefResult{
 | 
				
			||||||
 | 
									OriginalRef: opts.RefFullNames[i],
 | 
				
			||||||
 | 
									OldOID:      opts.OldCommitIDs[i],
 | 
				
			||||||
 | 
									NewOID:      opts.NewCommitIDs[i],
 | 
				
			||||||
 | 
									Err:         "topic-branch is not set",
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							headBranch := ""
 | 
				
			||||||
 | 
							userName := strings.ToLower(opts.UserName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(curentTopicBranch) == 0 {
 | 
				
			||||||
 | 
								curentTopicBranch = topicBranch
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// because different user maybe want to use same topic,
 | 
				
			||||||
 | 
							// So it's better to make sure the topic branch name
 | 
				
			||||||
 | 
							// has user name prefix
 | 
				
			||||||
 | 
							if !strings.HasPrefix(curentTopicBranch, userName+"/") {
 | 
				
			||||||
 | 
								headBranch = userName + "/" + curentTopicBranch
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								headBranch = curentTopicBranch
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pr, err := models.GetUnmergedPullRequest(repo.ID, repo.ID, headBranch, baseBranchName, models.PullRequestFlowAGit)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								if !models.IsErrPullRequestNotExist(err) {
 | 
				
			||||||
 | 
									log.Error("Failed to get unmerged agit flow pull request in repository: %s/%s Error: %v", ownerName, repoName, err)
 | 
				
			||||||
 | 
									ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 | 
										"Err": fmt.Sprintf("Failed to get unmerged agit flow pull request in repository: %s/%s Error: %v", ownerName, repoName, err),
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// create a new pull request
 | 
				
			||||||
 | 
								if len(title) == 0 {
 | 
				
			||||||
 | 
									has := false
 | 
				
			||||||
 | 
									title, has = opts.GitPushOptions["title"]
 | 
				
			||||||
 | 
									if !has || len(title) == 0 {
 | 
				
			||||||
 | 
										commit, err := gitRepo.GetCommit(opts.NewCommitIDs[i])
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											log.Error("Failed to get commit %s in repository: %s/%s Error: %v", opts.NewCommitIDs[i], ownerName, repoName, err)
 | 
				
			||||||
 | 
											ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 | 
												"Err": fmt.Sprintf("Failed to get commit %s in repository: %s/%s Error: %v", opts.NewCommitIDs[i], ownerName, repoName, err),
 | 
				
			||||||
 | 
											})
 | 
				
			||||||
 | 
											return nil
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										title = strings.Split(commit.CommitMessage, "\n")[0]
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									description = opts.GitPushOptions["description"]
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								pusher, err := models.GetUserByID(opts.UserID)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Error("Failed to get user. Error: %v", err)
 | 
				
			||||||
 | 
									ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 | 
										"Err": fmt.Sprintf("Failed to get user. Error: %v", err),
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								prIssue := &models.Issue{
 | 
				
			||||||
 | 
									RepoID:   repo.ID,
 | 
				
			||||||
 | 
									Title:    title,
 | 
				
			||||||
 | 
									PosterID: pusher.ID,
 | 
				
			||||||
 | 
									Poster:   pusher,
 | 
				
			||||||
 | 
									IsPull:   true,
 | 
				
			||||||
 | 
									Content:  description,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								pr := &models.PullRequest{
 | 
				
			||||||
 | 
									HeadRepoID:   repo.ID,
 | 
				
			||||||
 | 
									BaseRepoID:   repo.ID,
 | 
				
			||||||
 | 
									HeadBranch:   headBranch,
 | 
				
			||||||
 | 
									HeadCommitID: opts.NewCommitIDs[i],
 | 
				
			||||||
 | 
									BaseBranch:   baseBranchName,
 | 
				
			||||||
 | 
									HeadRepo:     repo,
 | 
				
			||||||
 | 
									BaseRepo:     repo,
 | 
				
			||||||
 | 
									MergeBase:    "",
 | 
				
			||||||
 | 
									Type:         models.PullRequestGitea,
 | 
				
			||||||
 | 
									Flow:         models.PullRequestFlowAGit,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err := pull_service.NewPullRequest(repo, prIssue, []int64{}, []string{}, pr, []int64{}); err != nil {
 | 
				
			||||||
 | 
									if models.IsErrUserDoesNotHaveAccessToRepo(err) {
 | 
				
			||||||
 | 
										ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error())
 | 
				
			||||||
 | 
										return nil
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									ctx.Error(http.StatusInternalServerError, "NewPullRequest", err.Error())
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								results = append(results, private.HockProcReceiveRefResult{
 | 
				
			||||||
 | 
									Ref:         pr.GetGitRefName(),
 | 
				
			||||||
 | 
									OriginalRef: opts.RefFullNames[i],
 | 
				
			||||||
 | 
									OldOID:      git.EmptySHA,
 | 
				
			||||||
 | 
									NewOID:      opts.NewCommitIDs[i],
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// update exist pull request
 | 
				
			||||||
 | 
							if err := pr.LoadBaseRepo(); err != nil {
 | 
				
			||||||
 | 
								log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
 | 
				
			||||||
 | 
								ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 | 
									"Err": fmt.Sprintf("Unable to load base repository for PR[%d] Error: %v", pr.ID, err),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							oldCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error("Unable to get ref commit id in base repository for PR[%d] Error: %v", pr.ID, err)
 | 
				
			||||||
 | 
								ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 | 
									"Err": fmt.Sprintf("Unable to get ref commit id in base repository for PR[%d] Error: %v", pr.ID, err),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if oldCommitID == opts.NewCommitIDs[i] {
 | 
				
			||||||
 | 
								results = append(results, private.HockProcReceiveRefResult{
 | 
				
			||||||
 | 
									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("rev-list", "--max-count=1", oldCommitID, "^"+opts.NewCommitIDs[i]).RunInDirWithEnv(repo.RepoPath(), os.Environ())
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, opts.NewCommitIDs[i], repo, err)
 | 
				
			||||||
 | 
									ctx.JSON(http.StatusInternalServerError, private.Response{
 | 
				
			||||||
 | 
										Err: fmt.Sprintf("Fail to detect force push: %v", err),
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								} else if len(output) > 0 {
 | 
				
			||||||
 | 
									results = append(results, private.HockProcReceiveRefResult{
 | 
				
			||||||
 | 
										OriginalRef: oldCommitID,
 | 
				
			||||||
 | 
										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(pr); err != nil {
 | 
				
			||||||
 | 
								log.Error("Failed to update pull ref. Error: %v", err)
 | 
				
			||||||
 | 
								ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 | 
									"Err": fmt.Sprintf("Failed to update pull ref. Error: %v", err),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pull_service.AddToTaskQueue(pr)
 | 
				
			||||||
 | 
							pusher, err := models.GetUserByID(opts.UserID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error("Failed to get user. Error: %v", err)
 | 
				
			||||||
 | 
								ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 | 
									"Err": fmt.Sprintf("Failed to get user. Error: %v", err),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err = pr.LoadIssue()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error("Failed to load pull issue. Error: %v", err)
 | 
				
			||||||
 | 
								ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
 | 
				
			||||||
 | 
									"Err": fmt.Sprintf("Failed to load pull issue. Error: %v", err),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							comment, err := models.CreatePushPullComment(pusher, pr, oldCommitID, opts.NewCommitIDs[i])
 | 
				
			||||||
 | 
							if err == nil && comment != nil {
 | 
				
			||||||
 | 
								notification.NotifyPullRequestPushCommits(pusher, pr, comment)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							notification.NotifyPullRequestSynchronized(pusher, pr)
 | 
				
			||||||
 | 
							isForcePush := comment != nil && comment.IsForcePush
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							results = append(results, private.HockProcReceiveRefResult{
 | 
				
			||||||
 | 
								OldOID:      oldCommitID,
 | 
				
			||||||
 | 
								NewOID:      opts.NewCommitIDs[i],
 | 
				
			||||||
 | 
								Ref:         pr.GetGitRefName(),
 | 
				
			||||||
 | 
								OriginalRef: opts.RefFullNames[i],
 | 
				
			||||||
 | 
								IsForcePush: isForcePush,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return results
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UserNameChanged hanle user name change for agit flow pull
 | 
				
			||||||
 | 
					func UserNameChanged(user *models.User, newName string) error {
 | 
				
			||||||
 | 
						pulls, err := models.GetAllUnmergedAgitPullRequestByPoster(user.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						newName = strings.ToLower(newName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, pull := range pulls {
 | 
				
			||||||
 | 
							pull.HeadBranch = strings.TrimPrefix(pull.HeadBranch, user.LowerName+"/")
 | 
				
			||||||
 | 
							pull.HeadBranch = newName + "/" + pull.HeadBranch
 | 
				
			||||||
 | 
							if err = pull.UpdateCols("head_branch"); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -108,13 +108,21 @@ func GetPullRequestCommitStatusState(pr *models.PullRequest) (structs.CommitStat
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	defer headGitRepo.Close()
 | 
						defer headGitRepo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !headGitRepo.IsBranchExist(pr.HeadBranch) {
 | 
						if pr.Flow == models.PullRequestFlowGithub && !headGitRepo.IsBranchExist(pr.HeadBranch) {
 | 
				
			||||||
 | 
							return "", errors.New("Head branch does not exist, can not merge")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if pr.Flow == models.PullRequestFlowAGit && !git.IsReferenceExist(headGitRepo.Path, pr.GetGitRefName()) {
 | 
				
			||||||
		return "", errors.New("Head branch does not exist, can not merge")
 | 
							return "", errors.New("Head branch does not exist, can not merge")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sha, err := headGitRepo.GetBranchCommitID(pr.HeadBranch)
 | 
						var sha string
 | 
				
			||||||
 | 
						if pr.Flow == models.PullRequestFlowGithub {
 | 
				
			||||||
 | 
							sha, err = headGitRepo.GetBranchCommitID(pr.HeadBranch)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							sha, err = headGitRepo.GetRefCommitID(pr.GetGitRefName())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return "", errors.Wrap(err, "GetBranchCommitID")
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := pr.LoadBaseRepo(); err != nil {
 | 
						if err := pr.LoadBaseRepo(); err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,7 +49,12 @@ func NewPullRequest(repo *models.Repository, pull *models.Issue, labelIDs []int6
 | 
				
			|||||||
	pr.Issue = pull
 | 
						pr.Issue = pull
 | 
				
			||||||
	pull.PullRequest = pr
 | 
						pull.PullRequest = pr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := PushToBaseRepo(pr); err != nil {
 | 
						if pr.Flow == models.PullRequestFlowGithub {
 | 
				
			||||||
 | 
							err = PushToBaseRepo(pr)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							err = UpdateRef(pr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -145,7 +150,7 @@ func ChangeTargetBranch(pr *models.PullRequest, doer *models.User, targetBranch
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check if pull request for the new target branch already exists
 | 
						// Check if pull request for the new target branch already exists
 | 
				
			||||||
	existingPr, err := models.GetUnmergedPullRequest(pr.HeadRepoID, pr.BaseRepoID, pr.HeadBranch, targetBranch)
 | 
						existingPr, err := models.GetUnmergedPullRequest(pr.HeadRepoID, pr.BaseRepoID, pr.HeadBranch, targetBranch, models.PullRequestFlowGithub)
 | 
				
			||||||
	if existingPr != nil {
 | 
						if existingPr != nil {
 | 
				
			||||||
		return models.ErrPullRequestAlreadyExists{
 | 
							return models.ErrPullRequestAlreadyExists{
 | 
				
			||||||
			ID:         existingPr.ID,
 | 
								ID:         existingPr.ID,
 | 
				
			||||||
@@ -281,10 +286,14 @@ func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSy
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		for _, pr := range prs {
 | 
							for _, pr := range prs {
 | 
				
			||||||
			log.Trace("Updating PR[%d]: composing new test task", pr.ID)
 | 
								log.Trace("Updating PR[%d]: composing new test task", pr.ID)
 | 
				
			||||||
 | 
								if pr.Flow == models.PullRequestFlowGithub {
 | 
				
			||||||
				if err := PushToBaseRepo(pr); err != nil {
 | 
									if err := PushToBaseRepo(pr); err != nil {
 | 
				
			||||||
					log.Error("PushToBaseRepo: %v", err)
 | 
										log.Error("PushToBaseRepo: %v", err)
 | 
				
			||||||
					continue
 | 
										continue
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			AddToTaskQueue(pr)
 | 
								AddToTaskQueue(pr)
 | 
				
			||||||
			comment, err := models.CreatePushPullComment(doer, pr, oldCommitID, newCommitID)
 | 
								comment, err := models.CreatePushPullComment(doer, pr, oldCommitID, newCommitID)
 | 
				
			||||||
@@ -451,6 +460,22 @@ func pushToBaseRepoHelper(pr *models.PullRequest, prefixHeadBranch string) (err
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateRef update refs/pull/id/head directly for agit flow pull request
 | 
				
			||||||
 | 
					func UpdateRef(pr *models.PullRequest) (err error) {
 | 
				
			||||||
 | 
						log.Trace("UpdateRef[%d]: upgate pull request ref in base repo '%s'", pr.ID, pr.GetGitRefName())
 | 
				
			||||||
 | 
						if err := pr.LoadBaseRepo(); err != nil {
 | 
				
			||||||
 | 
							log.Error("Unable to load base repository for PR[%d] Error: %v", pr.ID, err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = git.NewCommand("update-ref", pr.GetGitRefName(), pr.HeadCommitID).RunInDir(pr.BaseRepo.RepoPath())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("Unable to update ref in base repository for PR[%d] Error: %v", pr.ID, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type errlist []error
 | 
					type errlist []error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (errs errlist) Error() string {
 | 
					func (errs errlist) Error() string {
 | 
				
			||||||
@@ -562,7 +587,17 @@ func GetSquashMergeCommitMessages(pr *models.PullRequest) string {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	defer gitRepo.Close()
 | 
						defer gitRepo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	headCommit, err := gitRepo.GetBranchCommit(pr.HeadBranch)
 | 
						var headCommit *git.Commit
 | 
				
			||||||
 | 
						if pr.Flow == models.PullRequestFlowGithub {
 | 
				
			||||||
 | 
							headCommit, err = gitRepo.GetBranchCommit(pr.HeadBranch)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							pr.HeadCommitID, err = gitRepo.GetRefCommitID(pr.GetGitRefName())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error("Unable to get head commit: %s Error: %v", pr.GetGitRefName(), err)
 | 
				
			||||||
 | 
								return ""
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							headCommit, err = gitRepo.GetCommit(pr.HeadCommitID)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error("Unable to get head commit: %s Error: %v", pr.HeadBranch, err)
 | 
							log.Error("Unable to get head commit: %s Error: %v", pr.HeadBranch, err)
 | 
				
			||||||
		return ""
 | 
							return ""
 | 
				
			||||||
@@ -781,9 +816,20 @@ func IsHeadEqualWithBranch(pr *models.PullRequest, branchName string) (bool, err
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	defer headGitRepo.Close()
 | 
						defer headGitRepo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	headCommit, err := headGitRepo.GetBranchCommit(pr.HeadBranch)
 | 
						var headCommit *git.Commit
 | 
				
			||||||
 | 
						if pr.Flow == models.PullRequestFlowGithub {
 | 
				
			||||||
 | 
							headCommit, err = headGitRepo.GetBranchCommit(pr.HeadBranch)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return false, err
 | 
								return false, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							pr.HeadCommitID, err = baseGitRepo.GetRefCommitID(pr.GetGitRefName())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if headCommit, err = baseGitRepo.GetCommit(pr.HeadCommitID); err != nil {
 | 
				
			||||||
 | 
								return false, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return baseCommit.HasPreviousCommit(headCommit.ID)
 | 
						return baseCommit.HasPreviousCommit(headCommit.ID)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -140,7 +140,15 @@ func createTemporaryRepo(pr *models.PullRequest) (string, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	trackingBranch := "tracking"
 | 
						trackingBranch := "tracking"
 | 
				
			||||||
	// Fetch head branch
 | 
						// Fetch head branch
 | 
				
			||||||
	if err := git.NewCommand("fetch", "--no-tags", remoteRepoName, git.BranchPrefix+pr.HeadBranch+":"+trackingBranch).RunInDirPipeline(tmpBasePath, &outbuf, &errbuf); err != nil {
 | 
						var headBranch string
 | 
				
			||||||
 | 
						if pr.Flow == models.PullRequestFlowGithub {
 | 
				
			||||||
 | 
							headBranch = git.BranchPrefix + pr.HeadBranch
 | 
				
			||||||
 | 
						} else if len(pr.HeadCommitID) == 40 { // for not created pull request
 | 
				
			||||||
 | 
							headBranch = pr.HeadCommitID
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							headBranch = pr.GetGitRefName()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := git.NewCommand("fetch", "--no-tags", remoteRepoName, headBranch+":"+trackingBranch).RunInDirPipeline(tmpBasePath, &outbuf, &errbuf); err != nil {
 | 
				
			||||||
		if err := models.RemoveTemporaryPath(tmpBasePath); err != nil {
 | 
							if err := models.RemoveTemporaryPath(tmpBasePath); err != nil {
 | 
				
			||||||
			log.Error("CreateTempRepo: RemoveTemporaryPath: %s", err)
 | 
								log.Error("CreateTempRepo: RemoveTemporaryPath: %s", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -150,7 +158,7 @@ func createTemporaryRepo(pr *models.PullRequest) (string, error) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		log.Error("Unable to fetch head_repo head branch [%s:%s -> tracking in %s]: %v:\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, tmpBasePath, err, outbuf.String(), errbuf.String())
 | 
							log.Error("Unable to fetch head_repo head branch [%s:%s -> tracking in %s]: %v:\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, tmpBasePath, err, outbuf.String(), errbuf.String())
 | 
				
			||||||
		return "", fmt.Errorf("Unable to fetch head_repo head branch [%s:%s -> tracking in tmpBasePath]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, err, outbuf.String(), errbuf.String())
 | 
							return "", fmt.Errorf("Unable to fetch head_repo head branch [%s:%s -> tracking in tmpBasePath]: %v\n%s\n%s", pr.HeadRepo.FullName(), headBranch, err, outbuf.String(), errbuf.String())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	outbuf.Reset()
 | 
						outbuf.Reset()
 | 
				
			||||||
	errbuf.Reset()
 | 
						errbuf.Reset()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,11 @@ func Update(pull *models.PullRequest, doer *models.User, message string) error {
 | 
				
			|||||||
		BaseBranch: pull.HeadBranch,
 | 
							BaseBranch: pull.HeadBranch,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if pull.Flow == models.PullRequestFlowAGit {
 | 
				
			||||||
 | 
							// TODO: Not support update agit flow pull request's head branch
 | 
				
			||||||
 | 
							return fmt.Errorf("Not support update agit flow pull request's head branch")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := pr.LoadHeadRepo(); err != nil {
 | 
						if err := pr.LoadHeadRepo(); err != nil {
 | 
				
			||||||
		log.Error("LoadHeadRepo: %v", err)
 | 
							log.Error("LoadHeadRepo: %v", err)
 | 
				
			||||||
		return fmt.Errorf("LoadHeadRepo: %v", err)
 | 
							return fmt.Errorf("LoadHeadRepo: %v", err)
 | 
				
			||||||
@@ -48,6 +53,10 @@ func Update(pull *models.PullRequest, doer *models.User, message string) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// IsUserAllowedToUpdate check if user is allowed to update PR with given permissions and branch protections
 | 
					// IsUserAllowedToUpdate check if user is allowed to update PR with given permissions and branch protections
 | 
				
			||||||
func IsUserAllowedToUpdate(pull *models.PullRequest, user *models.User) (bool, error) {
 | 
					func IsUserAllowedToUpdate(pull *models.PullRequest, user *models.User) (bool, error) {
 | 
				
			||||||
 | 
						if pull.Flow == models.PullRequestFlowAGit {
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if user == nil {
 | 
						if user == nil {
 | 
				
			||||||
		return false, nil
 | 
							return false, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -461,13 +461,17 @@
 | 
				
			|||||||
									{{end}}
 | 
														{{end}}
 | 
				
			||||||
								</div>
 | 
													</div>
 | 
				
			||||||
							</div>
 | 
												</div>
 | 
				
			||||||
							<div class="instruct-toggle ml-3">{{$.i18n.Tr "repo.pulls.merge_instruction_hint" | Safe}}</div>
 | 
												<div class="instruct-toggle ml-3"> {{$.i18n.Tr "repo.pulls.merge_instruction_hint" | Safe}} </div>
 | 
				
			||||||
							<div class="instruct-content" style="display:none">
 | 
												<div class="instruct-content" style="display:none">
 | 
				
			||||||
								<div class="ui divider"></div>
 | 
													<div class="ui divider"></div>
 | 
				
			||||||
								<div><h3 class="di">{{$.i18n.Tr "step1"}} </h3>{{$.i18n.Tr "repo.pulls.merge_instruction_step1_desc"}}</div>
 | 
													<div><h3 class="di">{{$.i18n.Tr "step1"}} </h3>{{$.i18n.Tr "repo.pulls.merge_instruction_step1_desc"}}</div>
 | 
				
			||||||
								<div class="ui secondary segment">
 | 
													<div class="ui secondary segment">
 | 
				
			||||||
 | 
														{{if eq .Issue.PullRequest.Flow 0}}
 | 
				
			||||||
										<div>git checkout -b {{if ne .Issue.PullRequest.HeadRepo.ID .Issue.PullRequest.BaseRepo.ID}}{{.Issue.PullRequest.HeadRepo.OwnerName}}-{{end}}{{.Issue.PullRequest.HeadBranch}} {{.Issue.PullRequest.BaseBranch}}</div>
 | 
															<div>git checkout -b {{if ne .Issue.PullRequest.HeadRepo.ID .Issue.PullRequest.BaseRepo.ID}}{{.Issue.PullRequest.HeadRepo.OwnerName}}-{{end}}{{.Issue.PullRequest.HeadBranch}} {{.Issue.PullRequest.BaseBranch}}</div>
 | 
				
			||||||
										<div>git pull {{if ne .Issue.PullRequest.HeadRepo.ID .Issue.PullRequest.BaseRepo.ID}}{{.Issue.PullRequest.HeadRepo.HTMLURL}}{{else}}origin{{end}} {{.Issue.PullRequest.HeadBranch}}</div>
 | 
															<div>git pull {{if ne .Issue.PullRequest.HeadRepo.ID .Issue.PullRequest.BaseRepo.ID}}{{.Issue.PullRequest.HeadRepo.HTMLURL}}{{else}}origin{{end}} {{.Issue.PullRequest.HeadBranch}}</div>
 | 
				
			||||||
 | 
														{{else}}
 | 
				
			||||||
 | 
															<div>git fetch origin {{.Issue.PullRequest.GetGitRefName}}:{{.Issue.PullRequest.HeadBranch}}</div>
 | 
				
			||||||
 | 
														{{end}}
 | 
				
			||||||
								</div>
 | 
													</div>
 | 
				
			||||||
								<div><h3 class="di">{{$.i18n.Tr "step2"}} </h3>{{$.i18n.Tr "repo.pulls.merge_instruction_step2_desc"}}</div>
 | 
													<div><h3 class="di">{{$.i18n.Tr "step2"}} </h3>{{$.i18n.Tr "repo.pulls.merge_instruction_step2_desc"}}</div>
 | 
				
			||||||
								<div class="ui secondary segment">
 | 
													<div class="ui secondary segment">
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user