m.Group("/pulls/:index", func() { | m.Group("/pulls/:index", func() { | ||||
m.Get("/commits", repo.ViewPullCommits) | m.Get("/commits", repo.ViewPullCommits) | ||||
m.Get("/files", repo.ViewPullFiles) | m.Get("/files", repo.ViewPullFiles) | ||||
m.Post("/merge", reqRepoAdmin, repo.MergePullRequest) | |||||
}) | }) | ||||
m.Group("", func() { | m.Group("", func() { |
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.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.create = Create Pull Request | ||||
pulls.title_desc = wants to merge %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> | 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_conversation = Conversation | ||||
pulls.tab_commits = Commits | pulls.tab_commits = Commits | ||||
pulls.tab_files = Files changed | pulls.tab_files = Files changed | ||||
pulls.reopen_to_merge = Please reopen this pull request to perform merge operation. | 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.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_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. | pulls.cannot_auto_merge_helper = Please use commond line tool to solve it. |
"github.com/go-xorm/xorm" | "github.com/go-xorm/xorm" | ||||
"github.com/gogits/gogs/modules/base" | "github.com/gogits/gogs/modules/base" | ||||
"github.com/gogits/gogs/modules/git" | |||||
"github.com/gogits/gogs/modules/log" | "github.com/gogits/gogs/modules/log" | ||||
"github.com/gogits/gogs/modules/process" | "github.com/gogits/gogs/modules/process" | ||||
"github.com/gogits/gogs/modules/setting" | "github.com/gogits/gogs/modules/setting" | ||||
// 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"` | |||||
PullID int64 `xorm:"INDEX"` | |||||
ID int64 `xorm:"pk autoincr"` | |||||
PullID int64 `xorm:"INDEX"` | |||||
Pull *Issue `xorm:"-"` | |||||
PullIndex int64 | PullIndex int64 | ||||
HeadRepoID int64 `xorm:"UNIQUE(s)"` | |||||
HeadRepoID int64 | |||||
HeadRepo *Repository `xorm:"-"` | HeadRepo *Repository `xorm:"-"` | ||||
BaseRepoID int64 `xorm:"UNIQUE(s)"` | |||||
BaseRepoID int64 | |||||
HeadUserName string | HeadUserName string | ||||
HeadBarcnh string `xorm:"UNIQUE(s)"` | |||||
BaseBranch string `xorm:"UNIQUE(s)"` | |||||
HeadBarcnh string | |||||
BaseBranch string | |||||
MergeBase string `xorm:"VARCHAR(40)"` | MergeBase string `xorm:"VARCHAR(40)"` | ||||
MergedCommitID string `xorm:"VARCHAR(40)"` | MergedCommitID string `xorm:"VARCHAR(40)"` | ||||
Type PullRequestType | Type PullRequestType | ||||
CanAutoMerge bool | CanAutoMerge bool | ||||
HasMerged bool | HasMerged bool | ||||
Merged time.Time | |||||
MergerID int64 | |||||
Merger *User `xorm:"-"` | |||||
} | } | ||||
func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) { | func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) { | ||||
if err != nil { | if err != nil { | ||||
log.Error(3, "GetRepositoryByID[%d]: %v", pr.ID, err) | 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. | // NewPullRequest creates new pull request with labels for repository. | ||||
return sess.Commit() | 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{ | pr := &PullRequest{ | ||||
HeadRepoID: headRepoID, | HeadRepoID: headRepoID, | ||||
BaseRepoID: baseRepoID, | BaseRepoID: baseRepoID, | ||||
BaseBranch: baseBranch, | BaseBranch: baseBranch, | ||||
} | } | ||||
has, err := x.Get(pr) | |||||
has, err := x.Where("has_merged=?", false).Get(pr) | |||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
} else if !has { | } else if !has { |
if _, stderr, err = process.ExecDir(-1, | if _, stderr, err = process.ExecDir(-1, | ||||
tmpPath, fmt.Sprintf("initRepoCommit(git add): %s", tmpPath), | tmpPath, fmt.Sprintf("initRepoCommit(git add): %s", tmpPath), | ||||
"git", "add", "--all"); err != nil { | "git", "add", "--all"); err != nil { | ||||
return errors.New("git add: " + stderr) | |||||
return fmt.Errorf("git add: %s", stderr) | |||||
} | } | ||||
if _, stderr, err = process.ExecDir(-1, | if _, stderr, err = process.ExecDir(-1, | ||||
tmpPath, fmt.Sprintf("initRepoCommit(git commit): %s", tmpPath), | tmpPath, fmt.Sprintf("initRepoCommit(git commit): %s", tmpPath), | ||||
"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), | "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), | ||||
"-m", "initial commit"); err != nil { | "-m", "initial commit"); err != nil { | ||||
return errors.New("git commit: " + stderr) | |||||
return fmt.Errorf("git commit: %s", stderr) | |||||
} | } | ||||
if _, stderr, err = process.ExecDir(-1, | if _, stderr, err = process.ExecDir(-1, | ||||
tmpPath, fmt.Sprintf("initRepoCommit(git push): %s", tmpPath), | tmpPath, fmt.Sprintf("initRepoCommit(git push): %s", tmpPath), | ||||
"git", "push", "origin", "master"); err != nil { | "git", "push", "origin", "master"); err != nil { | ||||
return errors.New("git push: " + stderr) | |||||
return fmt.Errorf("git push: %s", stderr) | |||||
} | } | ||||
return nil | return nil | ||||
} | } | ||||
return err | return err | ||||
} else if _, err = sess.Delete(&Collaboration{RepoID: repoID}); err != nil { | } else if _, err = sess.Delete(&Collaboration{RepoID: repoID}); err != nil { | ||||
return err | return err | ||||
} else if _, err = sess.Delete(&PullRequest{BaseRepoID: repoID}); err != nil { | |||||
return err | |||||
} | } | ||||
// Delete comments and attachments. | // Delete comments and attachments. |
"bytes" | "bytes" | ||||
"container/list" | "container/list" | ||||
"errors" | "errors" | ||||
"fmt" | |||||
"strings" | "strings" | ||||
"sync" | "sync" | ||||
func (repo *Repository) commitsCount(id sha1) (int, error) { | func (repo *Repository) commitsCount(id sha1) (int, error) { | ||||
if gitVer.LessThan(MustParseVersion("1.8.0")) { | 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 { | if err != nil { | ||||
return 0, errors.New(string(stderr)) | return 0, errors.New(string(stderr)) | ||||
} | } | ||||
return com.StrTo(strings.TrimSpace(stdout)).Int() | 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, (] | // used only for single tree, (] | ||||
func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List, error) { | func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List, error) { | ||||
l := list.New() | l := list.New() | ||||
return nil | 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) { | func (repo *Repository) FileCommitsCount(branch, file string) (int, error) { | ||||
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count", | stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count", | ||||
branch, "--", file) | branch, "--", file) |
return stdout, nil | return stdout, nil | ||||
} | } | ||||
// Merge merges pull request from head repository and branch. | |||||
func (repo *Repository) Merge(headRepoPath string, baseBranch, headBranch string) error { | |||||
return nil | |||||
} |
&.green { | &.green { | ||||
color: #6cc644!important; | color: #6cc644!important; | ||||
} | } | ||||
&.purple { | |||||
color: #6e5494!important; | |||||
} | |||||
&.left { | &.left { | ||||
text-align: left!important; | text-align: left!important; | ||||
} | } |
// Get more information if it's a pull request. | // Get more information if it's a pull request. | ||||
if issue.IsPull { | if issue.IsPull { | ||||
PrepareViewPullInfo(ctx, issue) | |||||
if issue.HasMerged { | |||||
ctx.Data["DisableStatusChange"] = issue.HasMerged | |||||
PrepareMergedViewPullInfo(ctx, issue) | |||||
} else { | |||||
PrepareViewPullInfo(ctx, issue) | |||||
} | |||||
if ctx.Written() { | if ctx.Written() { | ||||
return | return | ||||
} | } | ||||
// Check if issue owner/poster changes the status of issue. | // Check if issue owner/poster changes the status of issue. | ||||
if (ctx.Repo.IsOwner() || (ctx.IsSigned && issue.IsPoster(ctx.User.Id))) && | 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 | issue.Repo = ctx.Repo.Repository | ||||
if err = issue.ChangeStatus(ctx.User, form.Status == "close"); err != nil { | if err = issue.ChangeStatus(ctx.User, form.Status == "close"); err != nil { | ||||
ctx.Handle(500, "ChangeStatus", err) | ctx.Handle(500, "ChangeStatus", err) |
package repo | package repo | ||||
import ( | import ( | ||||
"container/list" | |||||
"path" | "path" | ||||
"strings" | "strings" | ||||
return pull | 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 { | func PrepareViewPullInfo(ctx *middleware.Context, pull *models.Issue) *git.PullRequestInfo { | ||||
repo := ctx.Repo.Repository | repo := ctx.Repo.Repository | ||||
return nil | 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), | prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(repo.Owner.Name, repo.Name), | ||||
pull.BaseBranch, pull.HeadBarcnh) | pull.BaseBranch, pull.HeadBarcnh) | ||||
if err != nil { | if err != nil { | ||||
if ctx.Written() { | if ctx.Written() { | ||||
return | 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) | ctx.HTML(200, PULL_COMMITS) | ||||
} | } | ||||
return | 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 { | if err != nil { | ||||
ctx.Handle(500, "GetDiffRange", err) | ctx.Handle(500, "GetDiffRange", err) | ||||
return | return | ||||
ctx.Data["Diff"] = diff | ctx.Data["Diff"] = diff | ||||
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0 | ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0 | ||||
headCommit, err := headGitRepo.GetCommit(headCommitID) | |||||
commit, err := gitRepo.GetCommit(endCommitID) | |||||
if err != nil { | if err != nil { | ||||
ctx.Handle(500, "GetCommit", err) | ctx.Handle(500, "GetCommit", err) | ||||
return | return | ||||
headTarget := path.Join(pull.HeadUserName, pull.HeadRepo.Name) | headTarget := path.Join(pull.HeadUserName, pull.HeadRepo.Name) | ||||
ctx.Data["Username"] = pull.HeadUserName | ctx.Data["Username"] = pull.HeadUserName | ||||
ctx.Data["Reponame"] = pull.HeadRepo.Name | 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) | 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) { | func ParseCompareInfo(ctx *middleware.Context) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) { | ||||
// Get compare branch information. | // Get compare branch information. | ||||
infos := strings.Split(ctx.Params("*"), "...") | infos := strings.Split(ctx.Params("*"), "...") | ||||
return | 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 err != nil { | ||||
if !models.IsErrPullRequestNotExist(err) { | if !models.IsErrPullRequestNotExist(err) { | ||||
ctx.Handle(500, "HasPullRequest", err) | |||||
ctx.Handle(500, "GetUnmergedPullRequest", err) | |||||
return | return | ||||
} | } | ||||
} else { | } else { |
<ul id="repo-file-nav" class="clear menu menu-line"> | <ul id="repo-file-nav" class="clear menu menu-line"> | ||||
{{if and .IsRepositoryAdmin .Repository.BaseRepo}} | {{if and .IsRepositoryAdmin .Repository.BaseRepo}} | ||||
{{ $baseRepo := .Repository.BaseRepo}} | {{ $baseRepo := .Repository.BaseRepo}} | ||||
<!-- <li> | |||||
<li> | |||||
<a href="{{AppSubUrl}}/{{$baseRepo.Owner.Name}}/{{$baseRepo.Name}}/compare/{{$.BaseDefaultBranch}}...{{$.Owner.Name}}:{{$.BranchName}}"> | <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> | <button class="btn btn-green btn-small btn-radius" id="repo-compare-btn"><i class="octicon octicon-git-compare"></i></button> | ||||
</a> | </a> | ||||
</li> --> | |||||
</li> | |||||
{{end}} | {{end}} | ||||
<li id="repo-branch-switch" class="down drop"> | <li id="repo-branch-switch" class="down drop"> | ||||
<a> | <a> |
{{if .Issue.IsPull}} | {{if .Issue.IsPull}} | ||||
<div class="comment merge box"> | <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> | <span class="mega-octicon octicon-git-merge"></span> | ||||
</a> | </a> | ||||
<div class="content"> | <div class="content"> | ||||
<div class="ui merge segment"> | <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"> | <div class="item text grey"> | ||||
{{$.i18n.Tr "repo.pulls.reopen_to_merge"}} | {{$.i18n.Tr "repo.pulls.reopen_to_merge"}} | ||||
</div> | </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> | </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}} | {{else}} | ||||
<div class="item text red"> | <div class="item text red"> | ||||
<span class="octicon octicon-x"></span> | <span class="octicon octicon-x"></span> | ||||
{{.CsrfTokenHtml}} | {{.CsrfTokenHtml}} | ||||
<input id="status" name="status" type="hidden"> | <input id="status" name="status" type="hidden"> | ||||
<div class="text right"> | <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}} | {{end}} | ||||
<button class="ui green button"> | <button class="ui green button"> | ||||
{{.i18n.Tr "repo.issues.create_comment"}} | {{.i18n.Tr "repo.issues.create_comment"}} |
</div> | </div> | ||||
{{end}} | {{end}} | ||||
</div> | </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> | <div class="ui red large label"><i class="octicon octicon-issue-closed"></i> {{.i18n.Tr "repo.issues.closed_title"}}</div> | ||||
{{else}} | {{else}} | ||||
<div class="ui green large label"><i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues.open_title"}}</div> | <div class="ui green large label"><i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues.open_title"}}</div> | ||||
{{end}} | {{end}} | ||||
{{if .Issue.IsPull}} | {{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}} | {{else}} | ||||
{{ $createdStr:= TimeSince .Issue.Created $.Lang }} | {{ $createdStr:= TimeSince .Issue.Created $.Lang }} | ||||
<span class="time-desc"> | <span class="time-desc"> |
{{$.i18n.Tr "repo.pulls.tab_conversation"}} | {{$.i18n.Tr "repo.pulls.tab_conversation"}} | ||||
<span class="ui label">{{.Issue.NumComments}}</span> | <span class="ui label">{{.Issue.NumComments}}</span> | ||||
</a> | </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> | <span class="octicon octicon-git-commit"></span> | ||||
{{$.i18n.Tr "repo.pulls.tab_commits"}} | {{$.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> | ||||
<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> | <span class="octicon octicon-diff"></span> | ||||
{{$.i18n.Tr "repo.pulls.tab_files"}} | {{$.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> | </a> | ||||
</div> | </div> |