1
1
mirror of https://github.com/go-gitea/gitea synced 2024-11-10 12:14:25 +00:00

PR: nothing to commit and has pull request check

This commit is contained in:
Unknwon 2015-09-02 05:09:12 -04:00
parent 37e0cee877
commit 1abfe4e05f
9 changed files with 143 additions and 74 deletions

View File

@ -466,6 +466,8 @@ pulls.compare_base = base
pulls.compare_compare = compare pulls.compare_compare = compare
pulls.filter_branch = Filter branch pulls.filter_branch = Filter branch
pulls.no_results = No results found. 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.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.tab_conversation = Conversation pulls.tab_conversation = Conversation

View File

@ -308,18 +308,23 @@ func (err ErrIssueNotExist) Error() string {
// |____| |____/|____/____/____|_ /\___ >__ |____/ \___ >____ > |__| // |____| |____/|____/____/____|_ /\___ >__ |____/ \___ >____ > |__|
// \/ \/ |__| \/ \/ // \/ \/ |__| \/ \/
type ErrPullRepoNotExist struct { type ErrPullRequestNotExist struct {
ID int64 ID int64
PullID int64 PullID int64
HeadRepoID int64
BaseRepoID int64
HeadBarcnh string
BaseBranch string
} }
func IsErrPullRepoNotExist(err error) bool { func IsErrPullRequestNotExist(err error) bool {
_, ok := err.(ErrPullRepoNotExist) _, ok := err.(ErrPullRequestNotExist)
return ok return ok
} }
func (err ErrPullRepoNotExist) Error() string { func (err ErrPullRequestNotExist) Error() string {
return fmt.Sprintf("pull repo does not exist [id: %d, pull_id: %d]", err.ID, err.PullID) 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)
} }
// _________ __ // _________ __

View File

@ -46,10 +46,10 @@ type Issue struct {
MilestoneID int64 MilestoneID int64
Milestone *Milestone `xorm:"-"` Milestone *Milestone `xorm:"-"`
AssigneeID int64 AssigneeID int64
Assignee *User `xorm:"-"` Assignee *User `xorm:"-"`
IsRead bool `xorm:"-"` IsRead bool `xorm:"-"`
IsPull bool // Indicates whether is a pull request or not. IsPull bool // Indicates whether is a pull request or not.
PullRepo *PullRepo `xorm:"-"` *PullRequest `xorm:"-"`
IsClosed bool IsClosed bool
Content string `xorm:"TEXT"` Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"` RenderedContent string `xorm:"-"`
@ -96,9 +96,13 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
log.Error(3, "GetUserByID[%d]: %v", i.ID, err) log.Error(3, "GetUserByID[%d]: %v", i.ID, err)
} }
case "is_pull": case "is_pull":
i.PullRepo, err = GetPullRepoByPullID(i.ID) if !i.IsPull {
return
}
i.PullRequest, err = GetPullRequestByPullID(i.ID)
if err != nil { if err != nil {
log.Error(3, "GetPullRepoByPullID[%d]: %v", i.ID, err) log.Error(3, "GetPullRequestByPullID[%d]: %v", i.ID, err)
} }
case "created": case "created":
i.Created = regulateTimeZone(i.Created) i.Created = regulateTimeZone(i.Created)
@ -844,23 +848,25 @@ const (
PLLL_ERQUEST_GIT PLLL_ERQUEST_GIT
) )
// PullRepo represents relation between pull request and repositories. // PullRequest represents relation between pull request and repositories.
type PullRepo struct { type PullRequest struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
PullID int64 `xorm:"INDEX"` PullID int64 `xorm:"INDEX"`
HeadRepoID int64 `xorm:"UNIQUE(s)"` PullIndex int64
HeadRepo *Repository `xorm:"-"` HeadRepoID int64 `xorm:"UNIQUE(s)"`
BaseRepoID int64 `xorm:"UNIQUE(s)"` HeadRepo *Repository `xorm:"-"`
HeadUserName string BaseRepoID int64 `xorm:"UNIQUE(s)"`
HeadBarcnh string `xorm:"UNIQUE(s)"` HeadUserName string
BaseBranch string `xorm:"UNIQUE(s)"` HeadBarcnh string `xorm:"UNIQUE(s)"`
MergeBase string `xorm:"VARCHAR(40)"` BaseBranch string `xorm:"UNIQUE(s)"`
Type PullRequestType MergeBase string `xorm:"VARCHAR(40)"`
CanAutoMerge bool MergedCommitID string `xorm:"VARCHAR(40)"`
HasMerged bool Type PullRequestType
CanAutoMerge bool
HasMerged bool
} }
func (pr *PullRepo) AfterSet(colName string, _ xorm.Cell) { func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
var err error var err error
switch colName { switch colName {
case "head_repo_id": 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. // 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() sess := x.NewSession()
defer sessionRelease(sess) defer sessionRelease(sess)
if err = sess.Begin(); err != nil { if err = sess.Begin(); err != nil {
return err 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) return fmt.Errorf("newIssue: %v", err)
} }
// Notify watchers. // Notify watchers.
act := &Action{ act := &Action{
ActUserID: pr.Poster.Id, ActUserID: pull.Poster.Id,
ActUserName: pr.Poster.Name, ActUserName: pull.Poster.Name,
ActEmail: pr.Poster.Email, ActEmail: pull.Poster.Email,
OpType: PULL_REQUEST, OpType: PULL_REQUEST,
Content: fmt.Sprintf("%d|%s", pr.Index, pr.Name), Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
RepoID: repo.ID, RepoID: repo.ID,
RepoUserName: repo.Owner.Name, RepoUserName: repo.Owner.Name,
RepoName: repo.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) 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 pr.PullID = pull.ID
if _, err = sess.Insert(pullRepo); err != nil { pr.PullIndex = pull.Index
if _, err = sess.Insert(pr); err != nil {
return fmt.Errorf("insert pull repo: %v", err) return fmt.Errorf("insert pull repo: %v", err)
} }
return sess.Commit() return sess.Commit()
} }
// GetPullRepoByPullID returns pull repo by given pull ID. // GetPullRequest returnss a pull request by given info.
func GetPullRepoByPullID(pullID int64) (*PullRepo, error) { func GetPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
pullRepo := new(PullRepo) pr := &PullRequest{
has, err := x.Where("pull_id=?", pullID).Get(pullRepo) HeadRepoID: headRepoID,
BaseRepoID: baseRepoID,
HeadBarcnh: headBranch,
BaseBranch: baseBranch,
}
has, err := x.Get(pr)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
return nil, ErrPullRepoNotExist{0, pullID} return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
} }
return pullRepo, nil
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, ErrPullRequestNotExist{0, pullID, 0, 0, "", ""}
}
return pr, nil
} }
// .____ ___. .__ // .____ ___. .__

View File

@ -79,7 +79,7 @@ func init() {
new(User), new(PublicKey), new(Oauth2), new(AccessToken), new(User), new(PublicKey), new(Oauth2), new(AccessToken),
new(Repository), new(DeployKey), new(Collaboration), new(Access), new(Repository), new(DeployKey), new(Collaboration), new(Access),
new(Watch), new(Star), new(Follow), new(Action), 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(Label), new(IssueLabel), new(Milestone),
new(Mirror), new(Release), new(LoginSource), new(Webhook), new(Mirror), new(Release), new(LoginSource), new(Webhook),
new(UpdateTask), new(HookTask), new(UpdateTask), new(HookTask),

File diff suppressed because one or more lines are too long

View File

@ -317,6 +317,7 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
return return
} }
ctx.Data["RepoLink"] = ctx.Repo.RepoLink ctx.Data["RepoLink"] = ctx.Repo.RepoLink
ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
tags, err := ctx.Repo.GitRepo.GetTags() tags, err := ctx.Repo.GitRepo.GetTags()
if err != nil { if err != nil {

View File

@ -170,12 +170,12 @@ func checkPullInfo(ctx *middleware.Context) *models.Issue {
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
ctx.Data["HeadTarget"] = pull.PullRepo.HeadUserName + "/" + pull.PullRepo.HeadBarcnh ctx.Data["HeadTarget"] = pull.HeadUserName + "/" + pull.HeadBarcnh
ctx.Data["BaseTarget"] = ctx.Repo.Owner.Name + "/" + pull.PullRepo.BaseBranch ctx.Data["BaseTarget"] = ctx.Repo.Owner.Name + "/" + pull.BaseBranch
headRepoPath, err := pull.PullRepo.HeadRepo.RepoPath() headRepoPath, err := pull.HeadRepo.RepoPath()
if err != nil { if err != nil {
ctx.Handle(500, "PullRepo.HeadRepo.RepoPath", err) ctx.Handle(500, "HeadRepo.RepoPath", err)
return nil 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), prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(repo.Owner.Name, repo.Name),
pull.PullRepo.BaseBranch, pull.PullRepo.HeadBarcnh) pull.BaseBranch, pull.HeadBarcnh)
if err != nil { if err != nil {
ctx.Handle(500, "GetPullRequestInfo", err) ctx.Handle(500, "GetPullRequestInfo", err)
return nil return nil
@ -210,7 +210,10 @@ func ViewPullCommits(ctx *middleware.Context) {
} }
prInfo.Commits = models.ValidateCommitsWithEmails(prInfo.Commits) prInfo.Commits = models.ValidateCommitsWithEmails(prInfo.Commits)
ctx.Data["Commits"] = 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) ctx.HTML(200, PULL_COMMITS)
} }
@ -226,9 +229,8 @@ func ViewPullFiles(ctx *middleware.Context) {
if ctx.Written() { if ctx.Written() {
return 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) headGitRepo, err := git.OpenRepository(headRepoPath)
if err != nil { if err != nil {
@ -236,7 +238,7 @@ func ViewPullFiles(ctx *middleware.Context) {
return return
} }
headCommitID, err := headGitRepo.GetCommitIdOfBranch(pull.PullRepo.HeadBarcnh) headCommitID, err := headGitRepo.GetCommitIdOfBranch(pull.HeadBarcnh)
if err != nil { if err != nil {
ctx.Handle(500, "GetCommitIdOfBranch", err) ctx.Handle(500, "GetCommitIdOfBranch", err)
return return
@ -257,9 +259,9 @@ func ViewPullFiles(ctx *middleware.Context) {
return return
} }
headTarget := path.Join(pull.PullRepo.HeadUserName, pull.PullRepo.HeadRepo.Name) headTarget := path.Join(pull.HeadUserName, pull.HeadRepo.Name)
ctx.Data["Username"] = pull.PullRepo.HeadUserName ctx.Data["Username"] = pull.HeadUserName
ctx.Data["Reponame"] = pull.PullRepo.HeadRepo.Name ctx.Data["Reponame"] = pull.HeadRepo.Name
ctx.Data["IsImageFile"] = headCommit.IsImageFile ctx.Data["IsImageFile"] = headCommit.IsImageFile
ctx.Data["SourcePath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "src", headCommitID) ctx.Data["SourcePath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "src", headCommitID)
ctx.Data["BeforeSourcePath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "src", prInfo.MergeBase) ctx.Data["BeforeSourcePath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "src", prInfo.MergeBase)
@ -348,7 +350,7 @@ func PrepareCompareDiff(
headRepo *models.Repository, headRepo *models.Repository,
headGitRepo *git.Repository, headGitRepo *git.Repository,
prInfo *git.PullRequestInfo, prInfo *git.PullRequestInfo,
baseBranch, headBranch string) { baseBranch, headBranch string) bool {
var ( var (
repo = ctx.Repo.Repository repo = ctx.Repo.Repository
@ -359,21 +361,26 @@ func PrepareCompareDiff(
ctx.Data["CommitRepoLink"], err = headRepo.RepoLink() ctx.Data["CommitRepoLink"], err = headRepo.RepoLink()
if err != nil { if err != nil {
ctx.Handle(500, "RepoLink", err) ctx.Handle(500, "RepoLink", err)
return return false
} }
headCommitID, err := headGitRepo.GetCommitIdOfBranch(headBranch) headCommitID, err := headGitRepo.GetCommitIdOfBranch(headBranch)
if err != nil { if err != nil {
ctx.Handle(500, "GetCommitIdOfBranch", err) ctx.Handle(500, "GetCommitIdOfBranch", err)
return return false
} }
ctx.Data["AfterCommitID"] = headCommitID ctx.Data["AfterCommitID"] = headCommitID
if headCommitID == prInfo.MergeBase {
ctx.Data["IsNothingToCompare"] = true
return true
}
diff, err := models.GetDiffRange(models.RepoPath(headUser.Name, headRepo.Name), diff, err := models.GetDiffRange(models.RepoPath(headUser.Name, headRepo.Name),
prInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines) prInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines)
if err != nil { if err != nil {
ctx.Handle(500, "GetDiffRange", err) ctx.Handle(500, "GetDiffRange", err)
return return false
} }
ctx.Data["Diff"] = diff ctx.Data["Diff"] = diff
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0 ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
@ -381,7 +388,7 @@ func PrepareCompareDiff(
headCommit, err := headGitRepo.GetCommit(headCommitID) headCommit, err := headGitRepo.GetCommit(headCommitID)
if err != nil { if err != nil {
ctx.Handle(500, "GetCommit", err) ctx.Handle(500, "GetCommit", err)
return return false
} }
prInfo.Commits = models.ValidateCommitsWithEmails(prInfo.Commits) prInfo.Commits = models.ValidateCommitsWithEmails(prInfo.Commits)
@ -395,6 +402,7 @@ func PrepareCompareDiff(
ctx.Data["SourcePath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "src", headCommitID) ctx.Data["SourcePath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "src", headCommitID)
ctx.Data["BeforeSourcePath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "src", prInfo.MergeBase) ctx.Data["BeforeSourcePath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "src", prInfo.MergeBase)
ctx.Data["RawPath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "raw", headCommitID) ctx.Data["RawPath"] = setting.AppSubUrl + "/" + path.Join(headTarget, "raw", headCommitID)
return false
} }
func CompareAndPullRequest(ctx *middleware.Context) { func CompareAndPullRequest(ctx *middleware.Context) {
@ -408,15 +416,30 @@ func CompareAndPullRequest(ctx *middleware.Context) {
return return
} }
PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch) 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
}
nothingToCompare := PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch)
if ctx.Written() { if ctx.Written() {
return return
} }
// Setup information for new form. if !nothingToCompare {
RetrieveRepoMetas(ctx, ctx.Repo.Repository) // Setup information for new form.
if ctx.Written() { RetrieveRepoMetas(ctx, ctx.Repo.Repository)
return if ctx.Written() {
return
}
} }
ctx.HTML(200, COMPARE_PULL) ctx.HTML(200, COMPARE_PULL)
@ -458,7 +481,7 @@ func CompareAndPullRequestPost(ctx *middleware.Context, form auth.CreateIssueFor
return return
} }
pr := &models.Issue{ pull := &models.Issue{
RepoID: repo.ID, RepoID: repo.ID,
Index: int64(repo.NumIssues) + 1, Index: int64(repo.NumIssues) + 1,
Name: form.Title, Name: form.Title,
@ -469,7 +492,7 @@ func CompareAndPullRequestPost(ctx *middleware.Context, form auth.CreateIssueFor
IsPull: true, IsPull: true,
Content: form.Content, 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, HeadRepoID: headRepo.ID,
BaseRepoID: repo.ID, BaseRepoID: repo.ID,
HeadUserName: headUser.Name, HeadUserName: headUser.Name,
@ -482,6 +505,6 @@ func CompareAndPullRequestPost(ctx *middleware.Context, form auth.CreateIssueFor
return return
} }
log.Trace("Pull request created: %d/%d", repo.ID, pr.ID) log.Trace("Pull request created: %d/%d", repo.ID, pull.ID)
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index)) ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pull.Index))
} }

View File

@ -133,7 +133,7 @@
{{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.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> <span class="mega-octicon octicon-git-merge"></span>
</a> </a>
<div class="content"> <div class="content">
@ -142,17 +142,19 @@
<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.PullRepo.CanAutoMerge}} {{else if .Issue.CanAutoMerge}}
<div class="item text green"> <div class="item text green">
<span class="octicon octicon-check"></span> <span class="octicon octicon-check"></span>
{{$.i18n.Tr "repo.pulls.can_auto_merge_desc"}} {{$.i18n.Tr "repo.pulls.can_auto_merge_desc"}}
</div> </div>
{{if .IsRepositoryAdmin}}
<div class="ui divider"></div> <div class="ui divider"></div>
<div> <div>
<button class="ui green button"> <button class="ui green button">
<span class="octicon octicon-git-merge"></span> {{$.i18n.Tr "repo.pulls.merge_pull_request"}} <span class="octicon octicon-git-merge"></span> {{$.i18n.Tr "repo.pulls.merge_pull_request"}}
</button> </button>
</div> </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>

View File

@ -45,10 +45,20 @@
</div> </div>
</div> </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/commits_table" .}}
{{template "repo/diff_box" .}} {{template "repo/diff_box" .}}
{{end}}
</div> </div>
</div> </div>