Selaa lähdekoodia

basic PR feature

Unknwon 9 vuotta sitten
vanhempi
commit
953bb06857

+ 1 - 0
cmd/web.go

@@ -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() {

+ 4 - 0
conf/locale/locale_en-US.ini

@@ -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.

+ 98 - 9
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,7 +879,91 @@ 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.
@@ -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 {

+ 5 - 3
models/repo.go

@@ -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.

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
modules/bindata/bindata.go


+ 50 - 9
modules/git/repo_commit.go

@@ -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)

+ 6 - 0
modules/git/repo_pull.go

@@ -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
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
public/css/gogs.min.css


+ 4 - 0
public/less/_base.less

@@ -114,6 +114,10 @@ pre {
 		&.green {
 			color: #6cc644!important;
 		}
+		&.purple {
+			color: #6e5494!important;
+		}
+
 		&.left {
 			text-align: left!important;
 		}

+ 8 - 2
routers/repo/issue.go

@@ -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)

+ 151 - 31
routers/repo/pull.go

@@ -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
 	}
+	ctx.Data["Username"] = pull.HeadUserName
+	ctx.Data["Reponame"] = pull.HeadRepo.Name
 
-	prInfo := PrepareViewPullInfo(ctx, pull)
-	if ctx.Written() {
-		return
+	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
 	}
-	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
+	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
+		}
 
-	headRepoPath := models.RepoPath(pull.HeadUserName, pull.HeadRepo.Name)
+		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
+		}
 
-	headGitRepo, err := git.OpenRepository(headRepoPath)
-	if err != nil {
-		ctx.Handle(500, "OpenRepository", err)
-		return
-	}
+		headRepoPath := models.RepoPath(pull.HeadUserName, pull.HeadRepo.Name)
 
-	headCommitID, err := headGitRepo.GetCommitIdOfBranch(pull.HeadBarcnh)
-	if err != nil {
-		ctx.Handle(500, "GetCommitIdOfBranch", err)
-		return
+		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
 	}
 
-	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 {

+ 2 - 2
templates/repo/home.tmpl

@@ -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>

+ 36 - 24
templates/repo/issue/view_content.tmpl

@@ -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 .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>
+		    		{{else if .IsPullReuqestBroken}}
+		    		<div class="item text red">
+		    			<span class="octicon octicon-x"></span>
+		    			{{$.i18n.Tr "repo.pulls.data_borken"}}
 		    		</div>
-		    		{{end}}
+		    		{{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>
+			    			<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"}}

+ 11 - 3
templates/repo/issue/view_title.tmpl

@@ -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 - 4
templates/repo/pulls/tab_menu.tmpl

@@ -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>

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä