@@ -466,6 +466,8 @@ pulls.compare_base = base | |||
pulls.compare_compare = compare | |||
pulls.filter_branch = Filter branch | |||
pulls.no_results = No results found. | |||
pulls.nothing_to_compare = There is nothing to compare because base and head branches are even. | |||
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.tab_conversation = Conversation |
@@ -308,18 +308,23 @@ func (err ErrIssueNotExist) Error() string { | |||
// |____| |____/|____/____/____|_ /\___ >__ |____/ \___ >____ > |__| | |||
// \/ \/ |__| \/ \/ | |||
type ErrPullRepoNotExist struct { | |||
ID int64 | |||
PullID int64 | |||
type ErrPullRequestNotExist struct { | |||
ID int64 | |||
PullID int64 | |||
HeadRepoID int64 | |||
BaseRepoID int64 | |||
HeadBarcnh string | |||
BaseBranch string | |||
} | |||
func IsErrPullRepoNotExist(err error) bool { | |||
_, ok := err.(ErrPullRepoNotExist) | |||
func IsErrPullRequestNotExist(err error) bool { | |||
_, ok := err.(ErrPullRequestNotExist) | |||
return ok | |||
} | |||
func (err ErrPullRepoNotExist) Error() string { | |||
return fmt.Sprintf("pull repo does not exist [id: %d, pull_id: %d]", err.ID, err.PullID) | |||
func (err ErrPullRequestNotExist) Error() string { | |||
return fmt.Sprintf("pull request does not exist [id: %d, pull_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]", | |||
err.ID, err.PullID, err.HeadRepoID, err.BaseRepoID, err.HeadBarcnh, err.BaseBranch) | |||
} | |||
// _________ __ |
@@ -46,10 +46,10 @@ type Issue struct { | |||
MilestoneID int64 | |||
Milestone *Milestone `xorm:"-"` | |||
AssigneeID int64 | |||
Assignee *User `xorm:"-"` | |||
IsRead bool `xorm:"-"` | |||
IsPull bool // Indicates whether is a pull request or not. | |||
PullRepo *PullRepo `xorm:"-"` | |||
Assignee *User `xorm:"-"` | |||
IsRead bool `xorm:"-"` | |||
IsPull bool // Indicates whether is a pull request or not. | |||
*PullRequest `xorm:"-"` | |||
IsClosed bool | |||
Content string `xorm:"TEXT"` | |||
RenderedContent string `xorm:"-"` | |||
@@ -96,9 +96,13 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) { | |||
log.Error(3, "GetUserByID[%d]: %v", i.ID, err) | |||
} | |||
case "is_pull": | |||
i.PullRepo, err = GetPullRepoByPullID(i.ID) | |||
if !i.IsPull { | |||
return | |||
} | |||
i.PullRequest, err = GetPullRequestByPullID(i.ID) | |||
if err != nil { | |||
log.Error(3, "GetPullRepoByPullID[%d]: %v", i.ID, err) | |||
log.Error(3, "GetPullRequestByPullID[%d]: %v", i.ID, err) | |||
} | |||
case "created": | |||
i.Created = regulateTimeZone(i.Created) | |||
@@ -844,23 +848,25 @@ const ( | |||
PLLL_ERQUEST_GIT | |||
) | |||
// PullRepo represents relation between pull request and repositories. | |||
type PullRepo struct { | |||
ID int64 `xorm:"pk autoincr"` | |||
PullID int64 `xorm:"INDEX"` | |||
HeadRepoID int64 `xorm:"UNIQUE(s)"` | |||
HeadRepo *Repository `xorm:"-"` | |||
BaseRepoID int64 `xorm:"UNIQUE(s)"` | |||
HeadUserName string | |||
HeadBarcnh string `xorm:"UNIQUE(s)"` | |||
BaseBranch string `xorm:"UNIQUE(s)"` | |||
MergeBase string `xorm:"VARCHAR(40)"` | |||
Type PullRequestType | |||
CanAutoMerge bool | |||
HasMerged bool | |||
} | |||
func (pr *PullRepo) AfterSet(colName string, _ xorm.Cell) { | |||
// PullRequest represents relation between pull request and repositories. | |||
type PullRequest struct { | |||
ID int64 `xorm:"pk autoincr"` | |||
PullID int64 `xorm:"INDEX"` | |||
PullIndex int64 | |||
HeadRepoID int64 `xorm:"UNIQUE(s)"` | |||
HeadRepo *Repository `xorm:"-"` | |||
BaseRepoID int64 `xorm:"UNIQUE(s)"` | |||
HeadUserName string | |||
HeadBarcnh string `xorm:"UNIQUE(s)"` | |||
BaseBranch string `xorm:"UNIQUE(s)"` | |||
MergeBase string `xorm:"VARCHAR(40)"` | |||
MergedCommitID string `xorm:"VARCHAR(40)"` | |||
Type PullRequestType | |||
CanAutoMerge bool | |||
HasMerged bool | |||
} | |||
func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) { | |||
var err error | |||
switch colName { | |||
case "head_repo_id": | |||
@@ -872,24 +878,24 @@ func (pr *PullRepo) AfterSet(colName string, _ xorm.Cell) { | |||
} | |||
// NewPullRequest creates new pull request with labels for repository. | |||
func NewPullRequest(repo *Repository, pr *Issue, labelIDs []int64, uuids []string, pullRepo *PullRepo, patch []byte) (err error) { | |||
func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) { | |||
sess := x.NewSession() | |||
defer sessionRelease(sess) | |||
if err = sess.Begin(); err != nil { | |||
return err | |||
} | |||
if err = newIssue(sess, repo, pr, labelIDs, uuids); err != nil { | |||
if err = newIssue(sess, repo, pull, labelIDs, uuids); err != nil { | |||
return fmt.Errorf("newIssue: %v", err) | |||
} | |||
// Notify watchers. | |||
act := &Action{ | |||
ActUserID: pr.Poster.Id, | |||
ActUserName: pr.Poster.Name, | |||
ActEmail: pr.Poster.Email, | |||
ActUserID: pull.Poster.Id, | |||
ActUserName: pull.Poster.Name, | |||
ActEmail: pull.Poster.Email, | |||
OpType: PULL_REQUEST, | |||
Content: fmt.Sprintf("%d|%s", pr.Index, pr.Name), | |||
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name), | |||
RepoID: repo.ID, | |||
RepoUserName: repo.Owner.Name, | |||
RepoName: repo.Name, | |||
@@ -920,26 +926,46 @@ func NewPullRequest(repo *Repository, pr *Issue, labelIDs []int64, uuids []strin | |||
return fmt.Errorf("git apply --check: %v - %s", err, stderr) | |||
} | |||
} | |||
pullRepo.CanAutoMerge = !strings.Contains(stdout, "error: patch failed:") | |||
pr.CanAutoMerge = !strings.Contains(stdout, "error: patch failed:") | |||
pullRepo.PullID = pr.ID | |||
if _, err = sess.Insert(pullRepo); err != nil { | |||
pr.PullID = pull.ID | |||
pr.PullIndex = pull.Index | |||
if _, err = sess.Insert(pr); err != nil { | |||
return fmt.Errorf("insert pull repo: %v", err) | |||
} | |||
return sess.Commit() | |||
} | |||
// GetPullRepoByPullID returns pull repo by given pull ID. | |||
func GetPullRepoByPullID(pullID int64) (*PullRepo, error) { | |||
pullRepo := new(PullRepo) | |||
has, err := x.Where("pull_id=?", pullID).Get(pullRepo) | |||
// GetPullRequest returnss a pull request by given info. | |||
func GetPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) { | |||
pr := &PullRequest{ | |||
HeadRepoID: headRepoID, | |||
BaseRepoID: baseRepoID, | |||
HeadBarcnh: headBranch, | |||
BaseBranch: baseBranch, | |||
} | |||
has, err := x.Get(pr) | |||
if err != nil { | |||
return nil, err | |||
} else if !has { | |||
return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch} | |||
} | |||
return pr, nil | |||
} | |||
// GetPullRequestByPullID returns pull repo by given pull ID. | |||
func GetPullRequestByPullID(pullID int64) (*PullRequest, error) { | |||
pr := new(PullRequest) | |||
has, err := x.Where("pull_id=?", pullID).Get(pr) | |||
if err != nil { | |||
return nil, err | |||
} else if !has { | |||
return nil, ErrPullRepoNotExist{0, pullID} | |||
return nil, ErrPullRequestNotExist{0, pullID, 0, 0, "", ""} | |||
} | |||
return pullRepo, nil | |||
return pr, nil | |||
} | |||
// .____ ___. .__ |
@@ -79,7 +79,7 @@ func init() { | |||
new(User), new(PublicKey), new(Oauth2), new(AccessToken), | |||
new(Repository), new(DeployKey), new(Collaboration), new(Access), | |||
new(Watch), new(Star), new(Follow), new(Action), | |||
new(Issue), new(PullRepo), new(Comment), new(Attachment), new(IssueUser), | |||
new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser), | |||
new(Label), new(IssueLabel), new(Milestone), | |||
new(Mirror), new(Release), new(LoginSource), new(Webhook), | |||
new(UpdateTask), new(HookTask), |
@@ -317,6 +317,7 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { | |||
return | |||
} | |||
ctx.Data["RepoLink"] = ctx.Repo.RepoLink | |||
ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name | |||
tags, err := ctx.Repo.GitRepo.GetTags() | |||
if err != nil { |
@@ -170,12 +170,12 @@ func checkPullInfo(ctx *middleware.Context) *models.Issue { | |||
func PrepareViewPullInfo(ctx *middleware.Context, pull *models.Issue) *git.PullRequestInfo { | |||
repo := ctx.Repo.Repository | |||
ctx.Data["HeadTarget"] = pull.PullRepo.HeadUserName + "/" + pull.PullRepo.HeadBarcnh | |||
ctx.Data["BaseTarget"] = ctx.Repo.Owner.Name + "/" + pull.PullRepo.BaseBranch | |||
ctx.Data["HeadTarget"] = pull.HeadUserName + "/" + pull.HeadBarcnh | |||
ctx.Data["BaseTarget"] = ctx.Repo.Owner.Name + "/" + pull.BaseBranch | |||
headRepoPath, err := pull.PullRepo.HeadRepo.RepoPath() | |||
headRepoPath, err := pull.HeadRepo.RepoPath() | |||
if err != nil { | |||
ctx.Handle(500, "PullRepo.HeadRepo.RepoPath", err) | |||
ctx.Handle(500, "HeadRepo.RepoPath", err) | |||
return nil | |||
} | |||
@@ -186,7 +186,7 @@ func PrepareViewPullInfo(ctx *middleware.Context, pull *models.Issue) *git.PullR | |||
} | |||
prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(repo.Owner.Name, repo.Name), | |||
pull.PullRepo.BaseBranch, pull.PullRepo.HeadBarcnh) | |||
pull.BaseBranch, pull.HeadBarcnh) | |||
if err != nil { | |||
ctx.Handle(500, "GetPullRequestInfo", err) | |||
return nil | |||
@@ -210,7 +210,10 @@ func ViewPullCommits(ctx *middleware.Context) { | |||
} | |||
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 | |||
ctx.HTML(200, PULL_COMMITS) | |||
} | |||
@@ -226,9 +229,8 @@ func ViewPullFiles(ctx *middleware.Context) { | |||
if ctx.Written() { | |||
return | |||
} | |||
_ = prInfo | |||
headRepoPath := models.RepoPath(pull.PullRepo.HeadUserName, pull.PullRepo.HeadRepo.Name) | |||
headRepoPath := models.RepoPath(pull.HeadUserName, pull.HeadRepo.Name) | |||
headGitRepo, err := git.OpenRepository(headRepoPath) | |||
if err != nil { | |||
@@ -236,7 +238,7 @@ func ViewPullFiles(ctx *middleware.Context) { | |||
return | |||
} | |||
headCommitID, err := headGitRepo.GetCommitIdOfBranch(pull.PullRepo.HeadBarcnh) | |||
headCommitID, err := headGitRepo.GetCommitIdOfBranch(pull.HeadBarcnh) | |||
if err != nil { | |||
ctx.Handle(500, "GetCommitIdOfBranch", err) | |||
return | |||
@@ -257,9 +259,9 @@ func ViewPullFiles(ctx *middleware.Context) { | |||
return | |||
} | |||
headTarget := path.Join(pull.PullRepo.HeadUserName, pull.PullRepo.HeadRepo.Name) | |||
ctx.Data["Username"] = pull.PullRepo.HeadUserName | |||
ctx.Data["Reponame"] = pull.PullRepo.HeadRepo.Name | |||
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) | |||
@@ -348,7 +350,7 @@ func PrepareCompareDiff( | |||
headRepo *models.Repository, | |||
headGitRepo *git.Repository, | |||
prInfo *git.PullRequestInfo, | |||
baseBranch, headBranch string) { | |||
baseBranch, headBranch string) bool { | |||
var ( | |||
repo = ctx.Repo.Repository | |||
@@ -359,21 +361,26 @@ func PrepareCompareDiff( | |||
ctx.Data["CommitRepoLink"], err = headRepo.RepoLink() | |||
if err != nil { | |||
ctx.Handle(500, "RepoLink", err) | |||
return | |||
return false | |||
} | |||
headCommitID, err := headGitRepo.GetCommitIdOfBranch(headBranch) | |||
if err != nil { | |||
ctx.Handle(500, "GetCommitIdOfBranch", err) | |||
return | |||
return false | |||
} | |||
ctx.Data["AfterCommitID"] = headCommitID | |||
if headCommitID == prInfo.MergeBase { | |||
ctx.Data["IsNothingToCompare"] = true | |||
return true | |||
} | |||
diff, err := models.GetDiffRange(models.RepoPath(headUser.Name, headRepo.Name), | |||
prInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines) | |||
if err != nil { | |||
ctx.Handle(500, "GetDiffRange", err) | |||
return | |||
return false | |||
} | |||
ctx.Data["Diff"] = diff | |||
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0 | |||
@@ -381,7 +388,7 @@ func PrepareCompareDiff( | |||
headCommit, err := headGitRepo.GetCommit(headCommitID) | |||
if err != nil { | |||
ctx.Handle(500, "GetCommit", err) | |||
return | |||
return false | |||
} | |||
prInfo.Commits = models.ValidateCommitsWithEmails(prInfo.Commits) | |||
@@ -395,6 +402,7 @@ func PrepareCompareDiff( | |||
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) | |||
return false | |||
} | |||
func CompareAndPullRequest(ctx *middleware.Context) { | |||
@@ -408,17 +416,32 @@ func CompareAndPullRequest(ctx *middleware.Context) { | |||
return | |||
} | |||
PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch) | |||
if ctx.Written() { | |||
pr, err := models.GetPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch) | |||
if err != nil { | |||
if !models.IsErrPullRequestNotExist(err) { | |||
ctx.Handle(500, "HasPullRequest", err) | |||
return | |||
} | |||
} else { | |||
ctx.Data["HasPullRequest"] = true | |||
ctx.Data["PullRequest"] = pr | |||
ctx.HTML(200, COMPARE_PULL) | |||
return | |||
} | |||
// Setup information for new form. | |||
RetrieveRepoMetas(ctx, ctx.Repo.Repository) | |||
nothingToCompare := PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch) | |||
if ctx.Written() { | |||
return | |||
} | |||
if !nothingToCompare { | |||
// Setup information for new form. | |||
RetrieveRepoMetas(ctx, ctx.Repo.Repository) | |||
if ctx.Written() { | |||
return | |||
} | |||
} | |||
ctx.HTML(200, COMPARE_PULL) | |||
} | |||
@@ -458,7 +481,7 @@ func CompareAndPullRequestPost(ctx *middleware.Context, form auth.CreateIssueFor | |||
return | |||
} | |||
pr := &models.Issue{ | |||
pull := &models.Issue{ | |||
RepoID: repo.ID, | |||
Index: int64(repo.NumIssues) + 1, | |||
Name: form.Title, | |||
@@ -469,7 +492,7 @@ func CompareAndPullRequestPost(ctx *middleware.Context, form auth.CreateIssueFor | |||
IsPull: true, | |||
Content: form.Content, | |||
} | |||
if err := models.NewPullRequest(repo, pr, labelIDs, attachments, &models.PullRepo{ | |||
if err := models.NewPullRequest(repo, pull, labelIDs, attachments, &models.PullRequest{ | |||
HeadRepoID: headRepo.ID, | |||
BaseRepoID: repo.ID, | |||
HeadUserName: headUser.Name, | |||
@@ -482,6 +505,6 @@ func CompareAndPullRequestPost(ctx *middleware.Context, form auth.CreateIssueFor | |||
return | |||
} | |||
log.Trace("Pull request created: %d/%d", repo.ID, pr.ID) | |||
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index)) | |||
log.Trace("Pull request created: %d/%d", repo.ID, pull.ID) | |||
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pull.Index)) | |||
} |
@@ -133,7 +133,7 @@ | |||
{{if .Issue.IsPull}} | |||
<div class="comment merge box"> | |||
<a class="avatar text {{if .Issue.IsClosed}}grey{{else if .Issue.PullRepo.CanAutoMerge}}green{{else}}red{{end}}"> | |||
<a class="avatar text {{if .Issue.IsClosed}}grey{{else if .Issue.CanAutoMerge}}green{{else}}red{{end}}"> | |||
<span class="mega-octicon octicon-git-merge"></span> | |||
</a> | |||
<div class="content"> | |||
@@ -142,17 +142,19 @@ | |||
<div class="item text grey"> | |||
{{$.i18n.Tr "repo.pulls.reopen_to_merge"}} | |||
</div> | |||
{{else if .Issue.PullRepo.CanAutoMerge}} | |||
{{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}} | |||
{{else}} | |||
<div class="item text red"> | |||
<span class="octicon octicon-x"></span> |
@@ -45,10 +45,20 @@ | |||
</div> | |||
</div> | |||
</div> | |||
{{template "repo/issue/new_form" .}} | |||
{{if .IsNothingToCompare}} | |||
<div class="ui segment"> | |||
{{.i18n.Tr "repo.pulls.nothing_to_compare"}} | |||
</div> | |||
{{else if .HasPullRequest}} | |||
<div class="ui segment"> | |||
{{.i18n.Tr "repo.pulls.has_pull_request" $.RepoLink $.RepoRelPath .PullRequest.PullIndex | Safe}} | |||
</div> | |||
{{else}} | |||
{{template "repo/issue/new_form" .}} | |||
{{template "repo/commits_table" .}} | |||
{{template "repo/diff_box" .}} | |||
{{end}} | |||
</div> | |||
</div> |