mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	basic PR feature
This commit is contained in:
		| @@ -531,6 +531,7 @@ func runWeb(ctx *cli.Context) { | ||||
| 		m.Group("/pulls/:index", func() { | ||||
| 			m.Get("/commits", repo.ViewPullCommits) | ||||
| 			m.Get("/files", repo.ViewPullFiles) | ||||
| 			m.Post("/merge", reqRepoAdmin, repo.MergePullRequest) | ||||
| 		}) | ||||
|  | ||||
| 		m.Group("", func() { | ||||
|   | ||||
| @@ -470,10 +470,14 @@ pulls.nothing_to_compare = There is nothing to compare because base and head bra | ||||
| pulls.has_pull_request = `There is already a pull request between these two targets: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>` | ||||
| pulls.create = Create Pull Request | ||||
| pulls.title_desc = wants to merge %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> | ||||
| pulls.merged_title_desc = merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s | ||||
| pulls.tab_conversation = Conversation | ||||
| pulls.tab_commits = Commits | ||||
| pulls.tab_files = Files changed | ||||
| pulls.reopen_to_merge = Please reopen this pull request to perform merge operation. | ||||
| pulls.merged = Merged | ||||
| pulls.has_merged = This pull request has been merged successfully! | ||||
| pulls.data_borken = Data of this pull request has been borken due to deletion of fork information. | ||||
| pulls.can_auto_merge_desc = You can perform auto-merge operation on this pull request. | ||||
| pulls.cannot_auto_merge_desc = You can't perform auto-merge operation because there are conflicts between commits. | ||||
| pulls.cannot_auto_merge_helper = Please use commond line tool to solve it. | ||||
|   | ||||
							
								
								
									
										107
									
								
								models/issue.go
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								models/issue.go
									
									
									
									
									
								
							| @@ -21,6 +21,7 @@ import ( | ||||
| 	"github.com/go-xorm/xorm" | ||||
|  | ||||
| 	"github.com/gogits/gogs/modules/base" | ||||
| 	"github.com/gogits/gogs/modules/git" | ||||
| 	"github.com/gogits/gogs/modules/log" | ||||
| 	"github.com/gogits/gogs/modules/process" | ||||
| 	"github.com/gogits/gogs/modules/setting" | ||||
| @@ -850,20 +851,24 @@ const ( | ||||
|  | ||||
| // PullRequest represents relation between pull request and repositories. | ||||
| type PullRequest struct { | ||||
| 	ID             int64 `xorm:"pk autoincr"` | ||||
| 	PullID         int64 `xorm:"INDEX"` | ||||
| 	ID             int64  `xorm:"pk autoincr"` | ||||
| 	PullID         int64  `xorm:"INDEX"` | ||||
| 	Pull           *Issue `xorm:"-"` | ||||
| 	PullIndex      int64 | ||||
| 	HeadRepoID     int64       `xorm:"UNIQUE(s)"` | ||||
| 	HeadRepoID     int64 | ||||
| 	HeadRepo       *Repository `xorm:"-"` | ||||
| 	BaseRepoID     int64       `xorm:"UNIQUE(s)"` | ||||
| 	BaseRepoID     int64 | ||||
| 	HeadUserName   string | ||||
| 	HeadBarcnh     string `xorm:"UNIQUE(s)"` | ||||
| 	BaseBranch     string `xorm:"UNIQUE(s)"` | ||||
| 	HeadBarcnh     string | ||||
| 	BaseBranch     string | ||||
| 	MergeBase      string `xorm:"VARCHAR(40)"` | ||||
| 	MergedCommitID string `xorm:"VARCHAR(40)"` | ||||
| 	Type           PullRequestType | ||||
| 	CanAutoMerge   bool | ||||
| 	HasMerged      bool | ||||
| 	Merged         time.Time | ||||
| 	MergerID       int64 | ||||
| 	Merger         *User `xorm:"-"` | ||||
| } | ||||
|  | ||||
| func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) { | ||||
| @@ -874,9 +879,93 @@ func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) { | ||||
| 		if err != nil { | ||||
| 			log.Error(3, "GetRepositoryByID[%d]: %v", pr.ID, err) | ||||
| 		} | ||||
| 	case "merger_id": | ||||
| 		if !pr.HasMerged { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		pr.Merger, err = GetUserByID(pr.MergerID) | ||||
| 		if err != nil { | ||||
| 			if IsErrUserNotExist(err) { | ||||
| 				pr.MergerID = -1 | ||||
| 				pr.Merger = NewFakeUser() | ||||
| 			} else { | ||||
| 				log.Error(3, "GetUserByID[%d]: %v", pr.ID, err) | ||||
| 			} | ||||
| 		} | ||||
| 	case "merged": | ||||
| 		if !pr.HasMerged { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		pr.Merged = regulateTimeZone(pr.Merged) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Merge merges pull request to base repository. | ||||
| func (pr *PullRequest) Merge(baseGitRepo *git.Repository) (err error) { | ||||
| 	sess := x.NewSession() | ||||
| 	defer sessionRelease(sess) | ||||
| 	if err = sess.Begin(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	pr.Pull.IsClosed = true | ||||
| 	if _, err = sess.Id(pr.Pull.ID).AllCols().Update(pr.Pull); err != nil { | ||||
| 		return fmt.Errorf("update pull: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name) | ||||
| 	headGitRepo, err := git.OpenRepository(headRepoPath) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("OpenRepository: %v", err) | ||||
| 	} | ||||
| 	pr.MergedCommitID, err = headGitRepo.GetCommitIdOfBranch(pr.HeadBarcnh) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("GetCommitIdOfBranch: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	pr.HasMerged = true | ||||
| 	if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil { | ||||
| 		return fmt.Errorf("update pull request: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// Clone base repo. | ||||
| 	tmpBasePath := path.Join("data/tmp/repos", com.ToStr(time.Now().Nanosecond())+".git") | ||||
| 	os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm) | ||||
| 	defer os.RemoveAll(path.Dir(tmpBasePath)) | ||||
|  | ||||
| 	var stderr string | ||||
| 	if _, stderr, err = process.ExecTimeout(5*time.Minute, | ||||
| 		fmt.Sprintf("PullRequest.Merge(git clone): %s", tmpBasePath), | ||||
| 		"git", "clone", baseGitRepo.Path, tmpBasePath); err != nil { | ||||
| 		return fmt.Errorf("git clone: %s", stderr) | ||||
| 	} | ||||
|  | ||||
| 	// Check out base branch. | ||||
| 	if _, stderr, err = process.ExecDir(-1, tmpBasePath, | ||||
| 		fmt.Sprintf("PullRequest.Merge(git checkout): %s", tmpBasePath), | ||||
| 		"git", "checkout", pr.BaseBranch); err != nil { | ||||
| 		return fmt.Errorf("git checkout: %s", stderr) | ||||
| 	} | ||||
|  | ||||
| 	// Pull commits. | ||||
| 	if _, stderr, err = process.ExecDir(-1, tmpBasePath, | ||||
| 		fmt.Sprintf("PullRequest.Merge(git pull): %s", tmpBasePath), | ||||
| 		"git", "pull", headRepoPath, pr.HeadBarcnh); err != nil { | ||||
| 		return fmt.Errorf("git pull: %s", stderr) | ||||
| 	} | ||||
|  | ||||
| 	// Push back to upstream. | ||||
| 	if _, stderr, err = process.ExecDir(-1, tmpBasePath, | ||||
| 		fmt.Sprintf("PullRequest.Merge(git push): %s", tmpBasePath), | ||||
| 		"git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil { | ||||
| 		return fmt.Errorf("git push: %s", stderr) | ||||
| 	} | ||||
|  | ||||
| 	return sess.Commit() | ||||
| } | ||||
|  | ||||
| // NewPullRequest creates new pull request with labels for repository. | ||||
| func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) { | ||||
| 	sess := x.NewSession() | ||||
| @@ -937,8 +1026,8 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str | ||||
| 	return sess.Commit() | ||||
| } | ||||
|  | ||||
| // GetPullRequest returnss a pull request by given info. | ||||
| func GetPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) { | ||||
| // GetUnmergedPullRequest returnss a pull request hasn't been merged by given info. | ||||
| func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) { | ||||
| 	pr := &PullRequest{ | ||||
| 		HeadRepoID: headRepoID, | ||||
| 		BaseRepoID: baseRepoID, | ||||
| @@ -946,7 +1035,7 @@ func GetPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) | ||||
| 		BaseBranch: baseBranch, | ||||
| 	} | ||||
|  | ||||
| 	has, err := x.Get(pr) | ||||
| 	has, err := x.Where("has_merged=?", false).Get(pr) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} else if !has { | ||||
|   | ||||
| @@ -469,20 +469,20 @@ func initRepoCommit(tmpPath string, sig *git.Signature) (err error) { | ||||
| 	if _, stderr, err = process.ExecDir(-1, | ||||
| 		tmpPath, fmt.Sprintf("initRepoCommit(git add): %s", tmpPath), | ||||
| 		"git", "add", "--all"); err != nil { | ||||
| 		return errors.New("git add: " + stderr) | ||||
| 		return fmt.Errorf("git add: %s", stderr) | ||||
| 	} | ||||
|  | ||||
| 	if _, stderr, err = process.ExecDir(-1, | ||||
| 		tmpPath, fmt.Sprintf("initRepoCommit(git commit): %s", tmpPath), | ||||
| 		"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), | ||||
| 		"-m", "initial commit"); err != nil { | ||||
| 		return errors.New("git commit: " + stderr) | ||||
| 		return fmt.Errorf("git commit: %s", stderr) | ||||
| 	} | ||||
|  | ||||
| 	if _, stderr, err = process.ExecDir(-1, | ||||
| 		tmpPath, fmt.Sprintf("initRepoCommit(git push): %s", tmpPath), | ||||
| 		"git", "push", "origin", "master"); err != nil { | ||||
| 		return errors.New("git push: " + stderr) | ||||
| 		return fmt.Errorf("git push: %s", stderr) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -1004,6 +1004,8 @@ func DeleteRepository(uid, repoID int64) error { | ||||
| 		return err | ||||
| 	} else if _, err = sess.Delete(&Collaboration{RepoID: repoID}); err != nil { | ||||
| 		return err | ||||
| 	} else if _, err = sess.Delete(&PullRequest{BaseRepoID: repoID}); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Delete comments and attachments. | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -8,6 +8,7 @@ import ( | ||||
| 	"bytes" | ||||
| 	"container/list" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| @@ -138,7 +139,8 @@ func (repo *Repository) GetCommit(commitId string) (*Commit, error) { | ||||
|  | ||||
| func (repo *Repository) commitsCount(id sha1) (int, error) { | ||||
| 	if gitVer.LessThan(MustParseVersion("1.8.0")) { | ||||
| 		stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", "--pretty=format:''", id.String()) | ||||
| 		stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", | ||||
| 			"--pretty=format:''", id.String()) | ||||
| 		if err != nil { | ||||
| 			return 0, errors.New(string(stderr)) | ||||
| 		} | ||||
| @@ -152,6 +154,53 @@ func (repo *Repository) commitsCount(id sha1) (int, error) { | ||||
| 	return com.StrTo(strings.TrimSpace(stdout)).Int() | ||||
| } | ||||
|  | ||||
| func (repo *Repository) CommitsCount(commitId string) (int, error) { | ||||
| 	id, err := NewIdFromString(commitId) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	return repo.commitsCount(id) | ||||
| } | ||||
|  | ||||
| func (repo *Repository) commitsCountBetween(start, end sha1) (int, error) { | ||||
| 	if gitVer.LessThan(MustParseVersion("1.8.0")) { | ||||
| 		stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", | ||||
| 			"--pretty=format:''", start.String()+"..."+end.String()) | ||||
| 		if err != nil { | ||||
| 			return 0, errors.New(string(stderr)) | ||||
| 		} | ||||
| 		return len(bytes.Split(stdout, []byte("\n"))), nil | ||||
| 	} | ||||
|  | ||||
| 	stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count", | ||||
| 		start.String()+"..."+end.String()) | ||||
| 	if err != nil { | ||||
| 		return 0, errors.New(stderr) | ||||
| 	} | ||||
| 	return com.StrTo(strings.TrimSpace(stdout)).Int() | ||||
| } | ||||
|  | ||||
| func (repo *Repository) CommitsCountBetween(startCommitID, endCommitID string) (int, error) { | ||||
| 	start, err := NewIdFromString(startCommitID) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	end, err := NewIdFromString(endCommitID) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	return repo.commitsCountBetween(start, end) | ||||
| } | ||||
|  | ||||
| func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) { | ||||
| 	stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "diff", "--name-only", | ||||
| 		startCommitID+"..."+endCommitID) | ||||
| 	if err != nil { | ||||
| 		return 0, fmt.Errorf("list changed files: %v", concatenateError(err, stderr)) | ||||
| 	} | ||||
| 	return len(strings.Split(stdout, "\n")) - 1, nil | ||||
| } | ||||
|  | ||||
| // used only for single tree, (] | ||||
| func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List, error) { | ||||
| 	l := list.New() | ||||
| @@ -231,14 +280,6 @@ func (repo *Repository) commitsBefore(lock *sync.Mutex, l *list.List, parent *li | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (repo *Repository) CommitsCount(commitId string) (int, error) { | ||||
| 	id, err := NewIdFromString(commitId) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	return repo.commitsCount(id) | ||||
| } | ||||
|  | ||||
| func (repo *Repository) FileCommitsCount(branch, file string) (int, error) { | ||||
| 	stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count", | ||||
| 		branch, "--", file) | ||||
|   | ||||
| @@ -84,3 +84,9 @@ func (repo *Repository) GetPatch(basePath, baseBranch, headBranch string) ([]byt | ||||
|  | ||||
| 	return stdout, nil | ||||
| } | ||||
|  | ||||
| // Merge merges pull request from head repository and branch. | ||||
| func (repo *Repository) Merge(headRepoPath string, baseBranch, headBranch string) error { | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										2
									
								
								public/css/gogs.min.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								public/css/gogs.min.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -114,6 +114,10 @@ pre { | ||||
| 		&.green { | ||||
| 			color: #6cc644!important; | ||||
| 		} | ||||
| 		&.purple { | ||||
| 			color: #6e5494!important; | ||||
| 		} | ||||
|  | ||||
| 		&.left { | ||||
| 			text-align: left!important; | ||||
| 		} | ||||
|   | ||||
| @@ -466,7 +466,12 @@ func ViewIssue(ctx *middleware.Context) { | ||||
|  | ||||
| 	// Get more information if it's a pull request. | ||||
| 	if issue.IsPull { | ||||
| 		PrepareViewPullInfo(ctx, issue) | ||||
| 		if issue.HasMerged { | ||||
| 			ctx.Data["DisableStatusChange"] = issue.HasMerged | ||||
| 			PrepareMergedViewPullInfo(ctx, issue) | ||||
| 		} else { | ||||
| 			PrepareViewPullInfo(ctx, issue) | ||||
| 		} | ||||
| 		if ctx.Written() { | ||||
| 			return | ||||
| 		} | ||||
| @@ -730,7 +735,8 @@ func NewComment(ctx *middleware.Context, form auth.CreateCommentForm) { | ||||
|  | ||||
| 	// Check if issue owner/poster changes the status of issue. | ||||
| 	if (ctx.Repo.IsOwner() || (ctx.IsSigned && issue.IsPoster(ctx.User.Id))) && | ||||
| 		(form.Status == "reopen" || form.Status == "close") { | ||||
| 		(form.Status == "reopen" || form.Status == "close") && | ||||
| 		!(issue.IsPull && issue.HasMerged) { | ||||
| 		issue.Repo = ctx.Repo.Repository | ||||
| 		if err = issue.ChangeStatus(ctx.User, form.Status == "close"); err != nil { | ||||
| 			ctx.Handle(500, "ChangeStatus", err) | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| package repo | ||||
|  | ||||
| import ( | ||||
| 	"container/list" | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| @@ -167,6 +168,26 @@ func checkPullInfo(ctx *middleware.Context) *models.Issue { | ||||
| 	return pull | ||||
| } | ||||
|  | ||||
| func PrepareMergedViewPullInfo(ctx *middleware.Context, pull *models.Issue) { | ||||
| 	ctx.Data["HasMerged"] = true | ||||
|  | ||||
| 	var err error | ||||
|  | ||||
| 	ctx.Data["HeadTarget"] = pull.HeadUserName + "/" + pull.HeadBarcnh | ||||
| 	ctx.Data["BaseTarget"] = ctx.Repo.Owner.Name + "/" + pull.BaseBranch | ||||
|  | ||||
| 	ctx.Data["NumCommits"], err = ctx.Repo.GitRepo.CommitsCountBetween(pull.MergeBase, pull.MergedCommitID) | ||||
| 	if err != nil { | ||||
| 		ctx.Handle(500, "Repo.GitRepo.CommitsCountBetween", err) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Data["NumFiles"], err = ctx.Repo.GitRepo.FilesCountBetween(pull.MergeBase, pull.MergedCommitID) | ||||
| 	if err != nil { | ||||
| 		ctx.Handle(500, "Repo.GitRepo.FilesCountBetween", err) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func PrepareViewPullInfo(ctx *middleware.Context, pull *models.Issue) *git.PullRequestInfo { | ||||
| 	repo := ctx.Repo.Repository | ||||
|  | ||||
| @@ -185,6 +206,14 @@ func PrepareViewPullInfo(ctx *middleware.Context, pull *models.Issue) *git.PullR | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if pull.HeadRepo == nil || !headGitRepo.IsBranchExist(pull.HeadBarcnh) { | ||||
| 		ctx.Data["IsPullReuqestBroken"] = true | ||||
| 		ctx.Data["HeadTarget"] = "deleted" | ||||
| 		ctx.Data["NumCommits"] = 0 | ||||
| 		ctx.Data["NumFiles"] = 0 | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(repo.Owner.Name, repo.Name), | ||||
| 		pull.BaseBranch, pull.HeadBarcnh) | ||||
| 	if err != nil { | ||||
| @@ -203,17 +232,46 @@ func ViewPullCommits(ctx *middleware.Context) { | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	prInfo := PrepareViewPullInfo(ctx, pull) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 	prInfo.Commits = models.ValidateCommitsWithEmails(prInfo.Commits) | ||||
| 	ctx.Data["Commits"] = prInfo.Commits | ||||
| 	ctx.Data["CommitCount"] = prInfo.Commits.Len() | ||||
|  | ||||
| 	ctx.Data["Username"] = pull.HeadUserName | ||||
| 	ctx.Data["Reponame"] = pull.HeadRepo.Name | ||||
|  | ||||
| 	var commits *list.List | ||||
| 	if pull.HasMerged { | ||||
| 		PrepareMergedViewPullInfo(ctx, pull) | ||||
| 		if ctx.Written() { | ||||
| 			return | ||||
| 		} | ||||
| 		startCommit, err := ctx.Repo.GitRepo.GetCommit(pull.MergeBase) | ||||
| 		if err != nil { | ||||
| 			ctx.Handle(500, "Repo.GitRepo.GetCommit", err) | ||||
| 			return | ||||
| 		} | ||||
| 		endCommit, err := ctx.Repo.GitRepo.GetCommit(pull.MergedCommitID) | ||||
| 		if err != nil { | ||||
| 			ctx.Handle(500, "Repo.GitRepo.GetCommit", err) | ||||
| 			return | ||||
| 		} | ||||
| 		commits, err = ctx.Repo.GitRepo.CommitsBetween(endCommit, startCommit) | ||||
| 		if err != nil { | ||||
| 			ctx.Handle(500, "Repo.GitRepo.CommitsBetween", err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 	} else { | ||||
| 		prInfo := PrepareViewPullInfo(ctx, pull) | ||||
| 		if ctx.Written() { | ||||
| 			return | ||||
| 		} else if prInfo == nil { | ||||
| 			ctx.Handle(404, "ViewPullCommits", nil) | ||||
| 			return | ||||
| 		} | ||||
| 		commits = prInfo.Commits | ||||
| 	} | ||||
|  | ||||
| 	commits = models.ValidateCommitsWithEmails(commits) | ||||
| 	ctx.Data["Commits"] = commits | ||||
| 	ctx.Data["CommitCount"] = commits.Len() | ||||
|  | ||||
| 	ctx.HTML(200, PULL_COMMITS) | ||||
| } | ||||
|  | ||||
| @@ -225,27 +283,54 @@ func ViewPullFiles(ctx *middleware.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	prInfo := PrepareViewPullInfo(ctx, pull) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	var ( | ||||
| 		diffRepoPath  string | ||||
| 		startCommitID string | ||||
| 		endCommitID   string | ||||
| 		gitRepo       *git.Repository | ||||
| 	) | ||||
|  | ||||
| 	if pull.HasMerged { | ||||
| 		PrepareMergedViewPullInfo(ctx, pull) | ||||
| 		if ctx.Written() { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		diffRepoPath = ctx.Repo.GitRepo.Path | ||||
| 		startCommitID = pull.MergeBase | ||||
| 		endCommitID = pull.MergedCommitID | ||||
| 		gitRepo = ctx.Repo.GitRepo | ||||
| 	} else { | ||||
| 		prInfo := PrepareViewPullInfo(ctx, pull) | ||||
| 		if ctx.Written() { | ||||
| 			return | ||||
| 		} else if prInfo == nil { | ||||
| 			ctx.Handle(404, "ViewPullFiles", nil) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		headRepoPath := models.RepoPath(pull.HeadUserName, pull.HeadRepo.Name) | ||||
|  | ||||
| 		headGitRepo, err := git.OpenRepository(headRepoPath) | ||||
| 		if err != nil { | ||||
| 			ctx.Handle(500, "OpenRepository", err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		headCommitID, err := headGitRepo.GetCommitIdOfBranch(pull.HeadBarcnh) | ||||
| 		if err != nil { | ||||
| 			ctx.Handle(500, "GetCommitIdOfBranch", err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		diffRepoPath = headRepoPath | ||||
| 		startCommitID = prInfo.MergeBase | ||||
| 		endCommitID = headCommitID | ||||
| 		gitRepo = headGitRepo | ||||
| 	} | ||||
|  | ||||
| 	headRepoPath := models.RepoPath(pull.HeadUserName, pull.HeadRepo.Name) | ||||
|  | ||||
| 	headGitRepo, err := git.OpenRepository(headRepoPath) | ||||
| 	if err != nil { | ||||
| 		ctx.Handle(500, "OpenRepository", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	headCommitID, err := headGitRepo.GetCommitIdOfBranch(pull.HeadBarcnh) | ||||
| 	if err != nil { | ||||
| 		ctx.Handle(500, "GetCommitIdOfBranch", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	diff, err := models.GetDiffRange(headRepoPath, | ||||
| 		prInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines) | ||||
| 	diff, err := models.GetDiffRange(diffRepoPath, | ||||
| 		startCommitID, endCommitID, setting.Git.MaxGitDiffLines) | ||||
| 	if err != nil { | ||||
| 		ctx.Handle(500, "GetDiffRange", err) | ||||
| 		return | ||||
| @@ -253,7 +338,7 @@ func ViewPullFiles(ctx *middleware.Context) { | ||||
| 	ctx.Data["Diff"] = diff | ||||
| 	ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0 | ||||
|  | ||||
| 	headCommit, err := headGitRepo.GetCommit(headCommitID) | ||||
| 	commit, err := gitRepo.GetCommit(endCommitID) | ||||
| 	if err != nil { | ||||
| 		ctx.Handle(500, "GetCommit", err) | ||||
| 		return | ||||
| @@ -262,14 +347,49 @@ func ViewPullFiles(ctx *middleware.Context) { | ||||
| 	headTarget := path.Join(pull.HeadUserName, pull.HeadRepo.Name) | ||||
| 	ctx.Data["Username"] = pull.HeadUserName | ||||
| 	ctx.Data["Reponame"] = pull.HeadRepo.Name | ||||
| 	ctx.Data["IsImageFile"] = headCommit.IsImageFile | ||||
| 	ctx.Data["SourcePath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "src", headCommitID) | ||||
| 	ctx.Data["BeforeSourcePath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "src", prInfo.MergeBase) | ||||
| 	ctx.Data["RawPath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "raw", headCommitID) | ||||
| 	ctx.Data["IsImageFile"] = commit.IsImageFile | ||||
| 	ctx.Data["SourcePath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "src", endCommitID) | ||||
| 	ctx.Data["BeforeSourcePath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "src", startCommitID) | ||||
| 	ctx.Data["RawPath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "raw", endCommitID) | ||||
|  | ||||
| 	ctx.HTML(200, PULL_FILES) | ||||
| } | ||||
|  | ||||
| func MergePullRequest(ctx *middleware.Context) { | ||||
| 	pull := checkPullInfo(ctx) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
| 	if pull.IsClosed { | ||||
| 		ctx.Handle(404, "MergePullRequest", nil) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pr, err := models.GetPullRequestByPullID(pull.ID) | ||||
| 	if err != nil { | ||||
| 		if models.IsErrPullRequestNotExist(err) { | ||||
| 			ctx.Handle(404, "GetPullRequestByPullID", nil) | ||||
| 		} else { | ||||
| 			ctx.Handle(500, "GetPullRequestByPullID", err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if !pr.CanAutoMerge || pr.HasMerged { | ||||
| 		ctx.Handle(404, "MergePullRequest", nil) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pr.Pull = pull | ||||
| 	if err = pr.Merge(ctx.Repo.GitRepo); err != nil { | ||||
| 		ctx.Handle(500, "GetPullRequestByPullID", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	log.Trace("Pull request merged: %d", pr.ID) | ||||
| 	ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.PullIndex)) | ||||
| } | ||||
|  | ||||
| func ParseCompareInfo(ctx *middleware.Context) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) { | ||||
| 	// Get compare branch information. | ||||
| 	infos := strings.Split(ctx.Params("*"), "...") | ||||
| @@ -416,10 +536,10 @@ func CompareAndPullRequest(ctx *middleware.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pr, err := models.GetPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch) | ||||
| 	pr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch) | ||||
| 	if err != nil { | ||||
| 		if !models.IsErrPullRequestNotExist(err) { | ||||
| 			ctx.Handle(500, "HasPullRequest", err) | ||||
| 			ctx.Handle(500, "GetUnmergedPullRequest", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} else { | ||||
|   | ||||
| @@ -11,11 +11,11 @@ | ||||
|             <ul id="repo-file-nav" class="clear menu menu-line"> | ||||
|                 {{if and .IsRepositoryAdmin .Repository.BaseRepo}} | ||||
|                 {{ $baseRepo := .Repository.BaseRepo}} | ||||
|                 <!-- <li> | ||||
|                 <li> | ||||
|                     <a href="{{AppSubUrl}}/{{$baseRepo.Owner.Name}}/{{$baseRepo.Name}}/compare/{{$.BaseDefaultBranch}}...{{$.Owner.Name}}:{{$.BranchName}}"> | ||||
|                         <button class="btn btn-green btn-small btn-radius" id="repo-compare-btn"><i class="octicon octicon-git-compare"></i></button> | ||||
|                     </a> | ||||
|                 </li> --> | ||||
|                 </li> | ||||
|                 {{end}} | ||||
|                 <li id="repo-branch-switch" class="down drop"> | ||||
|                     <a> | ||||
|   | ||||
| @@ -133,28 +133,40 @@ | ||||
|  | ||||
|   		{{if .Issue.IsPull}} | ||||
|   		<div class="comment merge box"> | ||||
| 		    <a class="avatar text {{if .Issue.IsClosed}}grey{{else if .Issue.CanAutoMerge}}green{{else}}red{{end}}"> | ||||
| 		    <a class="avatar text {{if .Issue.HasMerged}}purple{{else if .Issue.IsClosed}}grey{{else if and .Issue.CanAutoMerge (not .IsPullReuqestBroken)}}green{{else}}red{{end}}"> | ||||
| 		      <span class="mega-octicon octicon-git-merge"></span> | ||||
| 		    </a> | ||||
| 		    <div class="content"> | ||||
| 		    	<div class="ui merge segment"> | ||||
| 		    		{{if .Issue.IsClosed}} | ||||
| 		    		{{if .Issue.HasMerged}} | ||||
| 		    		<div class="item text purple"> | ||||
| 		    			{{$.i18n.Tr "repo.pulls.has_merged"}} | ||||
| 		    		</div> | ||||
| 		    		{{else if .Issue.IsClosed}} | ||||
| 		    		<div class="item text grey"> | ||||
| 		    			{{$.i18n.Tr "repo.pulls.reopen_to_merge"}} | ||||
| 		    		</div> | ||||
| 		    		{{else if .IsPullReuqestBroken}} | ||||
| 		    		<div class="item text red"> | ||||
| 		    			<span class="octicon octicon-x"></span> | ||||
| 		    			{{$.i18n.Tr "repo.pulls.data_borken"}} | ||||
| 		    		</div> | ||||
| 		    		{{else if .Issue.CanAutoMerge}} | ||||
| 		    		<div class="item text green"> | ||||
| 		    			<span class="octicon octicon-check"></span> | ||||
| 		    			{{$.i18n.Tr "repo.pulls.can_auto_merge_desc"}} | ||||
| 		    		</div> | ||||
| 		    		{{if .IsRepositoryAdmin}} | ||||
| 		    		<div class="ui divider"></div> | ||||
| 		    		<div> | ||||
| 		    			<button class="ui green button"> | ||||
| 		    				<span class="octicon octicon-git-merge"></span> {{$.i18n.Tr "repo.pulls.merge_pull_request"}} | ||||
| 		    			</button> | ||||
| 		    		</div> | ||||
| 		    		{{end}} | ||||
| 			    		<div class="item text green"> | ||||
| 			    			<span class="octicon octicon-check"></span> | ||||
| 			    			{{$.i18n.Tr "repo.pulls.can_auto_merge_desc"}} | ||||
| 			    		</div> | ||||
| 			    		{{if .IsRepositoryAdmin}} | ||||
| 			    		<div class="ui divider"></div> | ||||
| 			    		<div> | ||||
| 			    			<form class="ui form" action="{{.Link}}/merge" method="post"> | ||||
| 				    			{{.CsrfTokenHtml}} | ||||
| 				    			<button class="ui green button"> | ||||
| 				    				<span class="octicon octicon-git-merge"></span> {{$.i18n.Tr "repo.pulls.merge_pull_request"}} | ||||
| 				    			</button> | ||||
| 			    			</form> | ||||
| 			    		</div> | ||||
| 			    		{{end}} | ||||
| 		    		{{else}} | ||||
| 		    		<div class="item text red"> | ||||
| 		    			<span class="octicon octicon-x"></span> | ||||
| @@ -181,16 +193,16 @@ | ||||
| 						{{.CsrfTokenHtml}} | ||||
| 						<input id="status" name="status" type="hidden"> | ||||
| 			      <div class="text right"> | ||||
| 			      	{{if .IsIssueOwner}} | ||||
| 			      	{{if .Issue.IsClosed}} | ||||
| 							<div id="status-button" class="ui green basic button" data-status="{{.i18n.Tr "repo.issues.reopen_issue"}}" data-status-and-comment="{{.i18n.Tr "repo.issues.reopen_comment_issue"}}" data-status-val="reopen"> | ||||
| 								{{.i18n.Tr "repo.issues.reopen_issue"}} | ||||
| 							</div> | ||||
| 			      	{{else}} | ||||
| 							<div id="status-button" class="ui red basic button" data-status="{{.i18n.Tr "repo.issues.close_issue"}}" data-status-and-comment="{{.i18n.Tr "repo.issues.close_comment_issue"}}" data-status-val="close"> | ||||
| 								{{.i18n.Tr "repo.issues.close_issue"}} | ||||
| 							</div> | ||||
| 							{{end}} | ||||
| 			      	{{if and .IsIssueOwner (not .DisableStatusChange)}} | ||||
| 				      	{{if .Issue.IsClosed}} | ||||
| 								<div id="status-button" class="ui green basic button" data-status="{{.i18n.Tr "repo.issues.reopen_issue"}}" data-status-and-comment="{{.i18n.Tr "repo.issues.reopen_comment_issue"}}" data-status-val="reopen"> | ||||
| 									{{.i18n.Tr "repo.issues.reopen_issue"}} | ||||
| 								</div> | ||||
| 				      	{{else}} | ||||
| 								<div id="status-button" class="ui red basic button" data-status="{{.i18n.Tr "repo.issues.close_issue"}}" data-status-and-comment="{{.i18n.Tr "repo.issues.close_comment_issue"}}" data-status-val="close"> | ||||
| 									{{.i18n.Tr "repo.issues.close_issue"}} | ||||
| 								</div> | ||||
| 								{{end}} | ||||
| 							{{end}} | ||||
| 							<button class="ui green button"> | ||||
| 								{{.i18n.Tr "repo.issues.create_comment"}} | ||||
|   | ||||
| @@ -16,15 +16,23 @@ | ||||
| 		</div> | ||||
| 		{{end}} | ||||
| 	</div> | ||||
| 	{{if .Issue.IsClosed}} | ||||
| 	{{if .HasMerged}} | ||||
| 	<div class="ui purple large label"><i class="octicon octicon-git-pull-request"></i> {{.i18n.Tr "repo.pulls.merged"}}</div> | ||||
| 	{{else if .Issue.IsClosed}} | ||||
| 	<div class="ui red large label"><i class="octicon octicon-issue-closed"></i> {{.i18n.Tr "repo.issues.closed_title"}}</div> | ||||
| 	{{else}} | ||||
| 	<div class="ui green large label"><i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues.open_title"}}</div> | ||||
| 	{{end}} | ||||
|  | ||||
| 	{{if .Issue.IsPull}} | ||||
| 	<a {{if gt .Issue.Poster.Id 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.Name}}</a> | ||||
| 	<span class="pull-desc">{{$.i18n.Tr "repo.pulls.title_desc" .NumCommits .HeadTarget .BaseTarget | Str2html}}</span> | ||||
| 		{{if .Issue.HasMerged}} | ||||
| 		{{ $mergedStr:= TimeSince .Issue.Merged $.Lang }} | ||||
| 		<a {{if gt .Issue.Merger.Id 0}}href="{{.Issue.Merger.HomeLink}}"{{end}}>{{.Issue.Merger.Name}}</a> | ||||
| 		<span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits .HeadTarget .BaseTarget $mergedStr | Safe}}</span> | ||||
| 		{{else}} | ||||
| 		<a {{if gt .Issue.Poster.Id 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.Name}}</a> | ||||
| 		<span class="pull-desc">{{$.i18n.Tr "repo.pulls.title_desc" .NumCommits .HeadTarget .BaseTarget | Str2html}}</span> | ||||
| 		{{end}} | ||||
| 	{{else}} | ||||
| 	{{ $createdStr:= TimeSince .Issue.Created $.Lang }} | ||||
| 	<span class="time-desc"> | ||||
|   | ||||
| @@ -4,14 +4,14 @@ | ||||
|   	{{$.i18n.Tr "repo.pulls.tab_conversation"}} | ||||
|   	<span class="ui label">{{.Issue.NumComments}}</span> | ||||
|   </a> | ||||
|   <a class="item {{if .PageIsPullCommits}}active{{end}}" href="{{.RepoLink}}/pulls/{{.Issue.Index}}/commits"> | ||||
|   <a class="item {{if .PageIsPullCommits}}active{{end}}" {{if .NumCommits}}href="{{.RepoLink}}/pulls/{{.Issue.Index}}/commits"{{end}}> | ||||
|   	<span class="octicon octicon-git-commit"></span> | ||||
|   	{{$.i18n.Tr "repo.pulls.tab_commits"}} | ||||
|   	<span class="ui label">{{.NumCommits}}</span> | ||||
|   	<span class="ui label">{{if .NumCommits}}{{.NumCommits}}{{else}}N/A{{end}}</span> | ||||
|   </a> | ||||
|   <a class="item {{if .PageIsPullFiles}}active{{end}}" href="{{.RepoLink}}/pulls/{{.Issue.Index}}/files"> | ||||
|   <a class="item {{if .PageIsPullFiles}}active{{end}}" {{if .NumFiles}}href="{{.RepoLink}}/pulls/{{.Issue.Index}}/files"{{end}}> | ||||
|   	<span class="octicon octicon-diff"></span> | ||||
|   	{{$.i18n.Tr "repo.pulls.tab_files"}} | ||||
|   	<span class="ui label">{{.NumFiles}}</span> | ||||
|   	<span class="ui label">{{if .NumFiles}}{{.NumFiles}}{{else}}N/A{{end}}</span> | ||||
|   </a> | ||||
| </div> | ||||
		Reference in New Issue
	
	Block a user
	 Unknwon
					Unknwon