Compare branches, commits and tags with each other (#6991)

* Supports tags when comparing commits or branches

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Hide headline when only comparing and don't load unused data

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Merges compare logics to allow comparing branches, commits and tags with eachother

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Display branch or tag instead of commit when used for comparing

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Show pull request form after click on button

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Transfers relevant pull.go changes from master to compare.go

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Fixes error when comparing forks against a commit or tag

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Removes console.log from JavaScript file

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Show icon next to commit reference when comparing branch or tag

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Updates css file

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Fixes import order

* Renames template variable

* Update routers/repo/compare.go

Co-Authored-By: zeripath <art27@cantab.net>

* Update from master

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Allow short-shas in compare

* Renames prInfo to compareInfo

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Check PR permissions only if compare is pull request

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Adjusts comment

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Use compareInfo instead of prInfo
tokarchuk/v1.17
Mario Lubenka 6 years ago committed by techknowlogick
parent bd55f6ff36
commit 311ce2d1d0
  1. 3
      models/pull.go
  2. 10
      modules/git/repo_commit.go
  3. 54
      modules/git/repo_compare.go
  4. 0
      modules/git/repo_compare_test.go
  5. 1
      public/css/index.css
  6. 9
      public/js/index.js
  7. 3
      public/less/_repository.less
  8. 14
      routers/api/v1/repo/pull.go
  9. 59
      routers/repo/commit.go
  10. 337
      routers/repo/compare.go
  11. 278
      routers/repo/pull.go
  12. 9
      routers/routes/routes.go
  13. 89
      templates/repo/commit_page.tmpl
  14. 8
      templates/repo/commits_table.tmpl
  15. 74
      templates/repo/diff/compare.tmpl
  16. 94
      templates/repo/diff/page.tmpl
  17. 69
      templates/repo/pulls/compare.tmpl

@ -1144,8 +1144,7 @@ func (pr *PullRequest) UpdatePatch() (err error) {
defer func() { defer func() {
headGitRepo.RemoveRemote(tmpRemote) headGitRepo.RemoveRemote(tmpRemote)
}() }()
remoteBranch := "remotes/" + tmpRemote + "/" + pr.BaseBranch pr.MergeBase, err = headGitRepo.GetMergeBase(tmpRemote, pr.BaseBranch, pr.HeadBranch)
pr.MergeBase, err = headGitRepo.GetMergeBase(remoteBranch, pr.HeadBranch)
if err != nil { if err != nil {
return fmt.Errorf("GetMergeBase: %v", err) return fmt.Errorf("GetMergeBase: %v", err)
} else if err = pr.Update(); err != nil { } else if err = pr.Update(); err != nil {

@ -27,6 +27,16 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) {
return ref.Hash().String(), nil return ref.Hash().String(), nil
} }
// IsCommitExist returns true if given commit exists in current repository.
func (repo *Repository) IsCommitExist(name string) bool {
hash := plumbing.NewHash(name)
_, err := repo.gogitRepo.CommitObject(hash)
if err != nil {
return false
}
return true
}
// GetBranchCommitID returns last commit ID string of given branch. // GetBranchCommitID returns last commit ID string of given branch.
func (repo *Repository) GetBranchCommitID(name string) (string, error) { func (repo *Repository) GetBranchCommitID(name string) (string, error) {
return repo.GetRefCommitID(BranchPrefix + name) return repo.GetRefCommitID(BranchPrefix + name)

@ -1,4 +1,5 @@
// Copyright 2015 The Gogs Authors. All rights reserved. // Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -14,55 +15,66 @@ import (
"time" "time"
) )
// PullRequestInfo represents needed information for a pull request. // CompareInfo represents needed information for comparing references.
type PullRequestInfo struct { type CompareInfo struct {
MergeBase string MergeBase string
Commits *list.List Commits *list.List
NumFiles int NumFiles int
} }
// GetMergeBase checks and returns merge base of two branches. // GetMergeBase checks and returns merge base of two branches.
func (repo *Repository) GetMergeBase(base, head string) (string, error) { func (repo *Repository) GetMergeBase(tmpRemote string, base, head string) (string, error) {
if tmpRemote == "" {
tmpRemote = "origin"
}
if tmpRemote != "origin" {
tmpBaseName := "refs/remotes/" + tmpRemote + "/tmp_" + base
// Fetch commit into a temporary branch in order to be able to handle commits and tags
_, err := NewCommand("fetch", tmpRemote, base+":"+tmpBaseName).RunInDir(repo.Path)
if err == nil {
base = tmpBaseName
}
}
stdout, err := NewCommand("merge-base", base, head).RunInDir(repo.Path) stdout, err := NewCommand("merge-base", base, head).RunInDir(repo.Path)
return strings.TrimSpace(stdout), err return strings.TrimSpace(stdout), err
} }
// GetPullRequestInfo generates and returns pull request information // GetCompareInfo generates and returns compare information between base and head branches of repositories.
// between base and head branches of repositories. func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string) (_ *CompareInfo, err error) {
func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch string) (_ *PullRequestInfo, err error) { var (
var remoteBranch string remoteBranch string
tmpRemote string
)
// We don't need a temporary remote for same repository. // We don't need a temporary remote for same repository.
if repo.Path != basePath { if repo.Path != basePath {
// Add a temporary remote // Add a temporary remote
tmpRemote := strconv.FormatInt(time.Now().UnixNano(), 10) tmpRemote = strconv.FormatInt(time.Now().UnixNano(), 10)
if err = repo.AddRemote(tmpRemote, basePath, true); err != nil { if err = repo.AddRemote(tmpRemote, basePath, true); err != nil {
return nil, fmt.Errorf("AddRemote: %v", err) return nil, fmt.Errorf("AddRemote: %v", err)
} }
defer repo.RemoveRemote(tmpRemote) defer repo.RemoveRemote(tmpRemote)
remoteBranch = "remotes/" + tmpRemote + "/" + baseBranch
} else {
remoteBranch = baseBranch
} }
prInfo := new(PullRequestInfo) compareInfo := new(CompareInfo)
prInfo.MergeBase, err = repo.GetMergeBase(remoteBranch, headBranch) compareInfo.MergeBase, err = repo.GetMergeBase(tmpRemote, baseBranch, headBranch)
if err == nil { if err == nil {
// We have a common base // We have a common base
logs, err := NewCommand("log", prInfo.MergeBase+"..."+headBranch, prettyLogFormat).RunInDirBytes(repo.Path) logs, err := NewCommand("log", compareInfo.MergeBase+"..."+headBranch, prettyLogFormat).RunInDirBytes(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
prInfo.Commits, err = repo.parsePrettyFormatLogToList(logs) compareInfo.Commits, err = repo.parsePrettyFormatLogToList(logs)
if err != nil { if err != nil {
return nil, fmt.Errorf("parsePrettyFormatLogToList: %v", err) return nil, fmt.Errorf("parsePrettyFormatLogToList: %v", err)
} }
} else { } else {
prInfo.Commits = list.New() compareInfo.Commits = list.New()
prInfo.MergeBase, err = GetFullCommitID(repo.Path, remoteBranch) compareInfo.MergeBase, err = GetFullCommitID(repo.Path, remoteBranch)
if err != nil { if err != nil {
prInfo.MergeBase = remoteBranch compareInfo.MergeBase = remoteBranch
} }
} }
@ -71,9 +83,9 @@ func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch stri
if err != nil { if err != nil {
return nil, err return nil, err
} }
prInfo.NumFiles = len(strings.Split(stdout, "\n")) - 1 compareInfo.NumFiles = len(strings.Split(stdout, "\n")) - 1
return prInfo, nil return compareInfo, nil
} }
// GetPatch generates and returns patch data between given revisions. // GetPatch generates and returns patch data between given revisions.

@ -591,6 +591,7 @@ footer .ui.left,footer .ui.right{line-height:40px}
.repository .milestone.list>.item .content{padding-top:10px} .repository .milestone.list>.item .content{padding-top:10px}
.repository.new.milestone textarea{height:200px} .repository.new.milestone textarea{height:200px}
.repository.new.milestone #deadline{width:150px} .repository.new.milestone #deadline{width:150px}
.repository.compare.pull .show-form-container{text-align:left}
.repository.compare.pull .choose.branch .octicon{padding-right:10px} .repository.compare.pull .choose.branch .octicon{padding-right:10px}
.repository.compare.pull .comment.form .content:after,.repository.compare.pull .comment.form .content:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none} .repository.compare.pull .comment.form .content:after,.repository.compare.pull .comment.form .content:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}
.repository.compare.pull .comment.form .content:before{border-right-color:#d3d3d4;border-width:9px;margin-top:-9px} .repository.compare.pull .comment.form .content:before{border-right-color:#d3d3d4;border-width:9px;margin-top:-9px}

@ -959,8 +959,15 @@ function initRepository() {
}); });
// Pull request // Pull request
if ($('.repository.compare.pull').length > 0) { var $repoComparePull = $('.repository.compare.pull');
if ($repoComparePull.length > 0) {
initFilterSearchDropdown('.choose.branch .dropdown'); initFilterSearchDropdown('.choose.branch .dropdown');
// show pull request form
$repoComparePull.find('button.show-form').on('click', function(e) {
e.preventDefault();
$repoComparePull.find('.pullrequest-form').show();
$(this).parent().hide();
});
} }
// Branches // Branches

@ -1109,6 +1109,9 @@
} }
&.compare.pull { &.compare.pull {
.show-form-container {
text-align: left;
}
.choose.branch { .choose.branch {
.octicon { .octicon {
padding-right: 10px; padding-right: 10px;

@ -188,7 +188,7 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
) )
// Get repo/branch information // Get repo/branch information
headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := parseCompareInfo(ctx, form) headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
if ctx.Written() { if ctx.Written() {
return return
} }
@ -240,7 +240,7 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
milestoneID = milestone.ID milestoneID = milestone.ID
} }
patch, err := headGitRepo.GetPatch(prInfo.MergeBase, headBranch) patch, err := headGitRepo.GetPatch(compareInfo.MergeBase, headBranch)
if err != nil { if err != nil {
ctx.Error(500, "GetPatch", err) ctx.Error(500, "GetPatch", err)
return return
@ -277,7 +277,7 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
BaseBranch: baseBranch, BaseBranch: baseBranch,
HeadRepo: headRepo, HeadRepo: headRepo,
BaseRepo: repo, BaseRepo: repo,
MergeBase: prInfo.MergeBase, MergeBase: compareInfo.MergeBase,
Type: models.PullRequestGitea, Type: models.PullRequestGitea,
} }
@ -600,7 +600,7 @@ func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) {
ctx.Status(200) ctx.Status(200)
} }
func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) { func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*models.User, *models.Repository, *git.Repository, *git.CompareInfo, string, string) {
baseRepo := ctx.Repo.Repository baseRepo := ctx.Repo.Repository
// Get compared branches information // Get compared branches information
@ -712,11 +712,11 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
return nil, nil, nil, nil, "", "" return nil, nil, nil, nil, "", ""
} }
prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch) compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
if err != nil { if err != nil {
ctx.Error(500, "GetPullRequestInfo", err) ctx.Error(500, "GetCompareInfo", err)
return nil, nil, nil, nil, "", "" return nil, nil, nil, nil, "", ""
} }
return headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch
} }

@ -19,9 +19,9 @@ import (
) )
const ( const (
tplCommits base.TplName = "repo/commits" tplCommits base.TplName = "repo/commits"
tplGraph base.TplName = "repo/graph" tplGraph base.TplName = "repo/graph"
tplDiff base.TplName = "repo/diff/page" tplCommitPage base.TplName = "repo/commit_page"
) )
// RefCommits render commits page // RefCommits render commits page
@ -261,7 +261,7 @@ func Diff(ctx *context.Context) {
} }
ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", "commit", commitID) ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", "commit", commitID)
ctx.Data["BranchName"], err = commit.GetBranchName() ctx.Data["BranchName"], err = commit.GetBranchName()
ctx.HTML(200, tplDiff) ctx.HTML(200, tplCommitPage)
} }
// RawDiff dumps diff results of repository in given commit ID to io.Writer // RawDiff dumps diff results of repository in given commit ID to io.Writer
@ -276,54 +276,3 @@ func RawDiff(ctx *context.Context) {
return return
} }
} }
// CompareDiff show different from one commit to another commit
func CompareDiff(ctx *context.Context) {
ctx.Data["IsRepoToolbarCommits"] = true
ctx.Data["IsDiffCompare"] = true
userName := ctx.Repo.Owner.Name
repoName := ctx.Repo.Repository.Name
beforeCommitID := ctx.Params(":before")
afterCommitID := ctx.Params(":after")
commit, err := ctx.Repo.GitRepo.GetCommit(afterCommitID)
if err != nil {
ctx.NotFound("GetCommit", err)
return
}
diff, err := models.GetDiffRange(models.RepoPath(userName, repoName), beforeCommitID,
afterCommitID, setting.Git.MaxGitDiffLines,
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
if err != nil {
ctx.NotFound("GetDiffRange", err)
return
}
commits, err := commit.CommitsBeforeUntil(beforeCommitID)
if err != nil {
ctx.ServerError("CommitsBeforeUntil", err)
return
}
commits = models.ValidateCommitsWithEmails(commits)
commits = models.ParseCommitsWithSignature(commits)
commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
ctx.Data["CommitRepoLink"] = ctx.Repo.RepoLink
ctx.Data["Commits"] = commits
ctx.Data["CommitCount"] = commits.Len()
ctx.Data["BeforeCommitID"] = beforeCommitID
ctx.Data["AfterCommitID"] = afterCommitID
ctx.Data["Username"] = userName
ctx.Data["Reponame"] = repoName
ctx.Data["IsImageFile"] = commit.IsImageFile
ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + "..." + base.ShortSha(afterCommitID) + " · " + userName + "/" + repoName
ctx.Data["Commit"] = commit
ctx.Data["Diff"] = diff
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
ctx.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", afterCommitID)
ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", beforeCommitID)
ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", "commit", afterCommitID)
ctx.Data["RequireHighlightJS"] = true
ctx.HTML(200, tplDiff)
}

@ -0,0 +1,337 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
"path"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
const (
tplCompare base.TplName = "repo/diff/compare"
)
// ParseCompareInfo parse compare info between two commit for preparing comparing references
func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, *git.Repository, *git.CompareInfo, string, string) {
baseRepo := ctx.Repo.Repository
// Get compared branches information
// format: <base branch>...[<head repo>:]<head branch>
// base<-head: master...head:feature
// same repo: master...feature
var (
headUser *models.User
headBranch string
isSameRepo bool
infoPath string
err error
)
infoPath = ctx.Params("*")
infos := strings.Split(infoPath, "...")
if len(infos) != 2 {
log.Trace("ParseCompareInfo[%d]: not enough compared branches information %s", baseRepo.ID, infos)
ctx.NotFound("CompareAndPullRequest", nil)
return nil, nil, nil, nil, "", ""
}
baseBranch := infos[0]
ctx.Data["BaseBranch"] = baseBranch
// If there is no head repository, it means compare between same repository.
headInfos := strings.Split(infos[1], ":")
if len(headInfos) == 1 {
isSameRepo = true
headUser = ctx.Repo.Owner
headBranch = headInfos[0]
} else if len(headInfos) == 2 {
headUser, err = models.GetUserByName(headInfos[0])
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.NotFound("GetUserByName", nil)
} else {
ctx.ServerError("GetUserByName", err)
}
return nil, nil, nil, nil, "", ""
}
headBranch = headInfos[1]
isSameRepo = headUser.ID == ctx.Repo.Owner.ID
} else {
ctx.NotFound("CompareAndPullRequest", nil)
return nil, nil, nil, nil, "", ""
}
ctx.Data["HeadUser"] = headUser
ctx.Data["HeadBranch"] = headBranch
ctx.Repo.PullRequest.SameRepo = isSameRepo
// Check if base branch is valid.
baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(baseBranch)
baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(baseBranch)
baseIsTag := ctx.Repo.GitRepo.IsTagExist(baseBranch)
if !baseIsCommit && !baseIsBranch && !baseIsTag {
// Check if baseBranch is short sha commit hash
if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(baseBranch); baseCommit != nil {
baseBranch = baseCommit.ID.String()
ctx.Data["BaseBranch"] = baseBranch
baseIsCommit = true
} else {
ctx.NotFound("IsRefExist", nil)
return nil, nil, nil, nil, "", ""
}
}
ctx.Data["BaseIsCommit"] = baseIsCommit
ctx.Data["BaseIsBranch"] = baseIsBranch
ctx.Data["BaseIsTag"] = baseIsTag
// Check if current user has fork of repository or in the same repository.
headRepo, has := models.HasForkedRepo(headUser.ID, baseRepo.ID)
if !has && !isSameRepo {
ctx.Data["PageIsComparePull"] = false
}
var headGitRepo *git.Repository
if isSameRepo {
headRepo = ctx.Repo.Repository
headGitRepo = ctx.Repo.GitRepo
ctx.Data["BaseName"] = headUser.Name
} else {
headGitRepo, err = git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name))
ctx.Data["BaseName"] = baseRepo.OwnerName
if err != nil {
ctx.ServerError("OpenRepository", err)
return nil, nil, nil, nil, "", ""
}
}
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
if !permBase.CanRead(models.UnitTypeCode) {
if log.IsTrace() {
log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in baseRepo has Permissions: %-+v",
ctx.User,
baseRepo,
permBase)
}
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
// user should have permission to read headrepo's codes
permHead, err := models.GetUserRepoPermission(headRepo, ctx.User)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
if !permHead.CanRead(models.UnitTypeCode) {
if log.IsTrace() {
log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v",
ctx.User,
headRepo,
permHead)
}
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
// Check if head branch is valid.
headIsCommit := ctx.Repo.GitRepo.IsCommitExist(headBranch)
headIsBranch := headGitRepo.IsBranchExist(headBranch)
headIsTag := headGitRepo.IsTagExist(headBranch)
if !headIsCommit && !headIsBranch && !headIsTag {
// Check if headBranch is short sha commit hash
if headCommit, _ := ctx.Repo.GitRepo.GetCommit(headBranch); headCommit != nil {
headBranch = headCommit.ID.String()
ctx.Data["HeadBranch"] = headBranch
headIsCommit = true
} else {
ctx.NotFound("IsRefExist", nil)
return nil, nil, nil, nil, "", ""
}
}
ctx.Data["HeadIsCommit"] = headIsCommit
ctx.Data["HeadIsBranch"] = headIsBranch
ctx.Data["HeadIsTag"] = headIsTag
// Treat as pull request if both references are branches
if ctx.Data["PageIsComparePull"] == nil {
ctx.Data["PageIsComparePull"] = headIsBranch && baseIsBranch
}
if ctx.Data["PageIsComparePull"] == true && !permBase.CanReadIssuesOrPulls(true) {
if log.IsTrace() {
log.Trace("Permission Denied: User: %-v cannot create/read pull requests in Repo: %-v\nUser in baseRepo has Permissions: %-+v",
ctx.User,
baseRepo,
permBase)
}
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
if err != nil {
ctx.ServerError("GetCompareInfo", err)
return nil, nil, nil, nil, "", ""
}
ctx.Data["BeforeCommitID"] = compareInfo.MergeBase
return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch
}
// PrepareCompareDiff renders compare diff page
func PrepareCompareDiff(
ctx *context.Context,
headUser *models.User,
headRepo *models.Repository,
headGitRepo *git.Repository,
compareInfo *git.CompareInfo,
baseBranch, headBranch string) bool {
var (
repo = ctx.Repo.Repository
err error
title string
)
// Get diff information.
ctx.Data["CommitRepoLink"] = headRepo.Link()
headCommitID := headBranch
if ctx.Data["HeadIsCommit"] == false {
if ctx.Data["HeadIsTag"] == true {
headCommitID, err = headGitRepo.GetTagCommitID(headBranch)
} else {
headCommitID, err = headGitRepo.GetBranchCommitID(headBranch)
}
if err != nil {
ctx.ServerError("GetRefCommitID", err)
return false
}
}
ctx.Data["AfterCommitID"] = headCommitID
if headCommitID == compareInfo.MergeBase {
ctx.Data["IsNothingToCompare"] = true
return true
}
diff, err := models.GetDiffRange(models.RepoPath(headUser.Name, headRepo.Name),
compareInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines,
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
if err != nil {
ctx.ServerError("GetDiffRange", err)
return false
}
ctx.Data["Diff"] = diff
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
headCommit, err := headGitRepo.GetCommit(headCommitID)
if err != nil {
ctx.ServerError("GetCommit", err)
return false
}
compareInfo.Commits = models.ValidateCommitsWithEmails(compareInfo.Commits)
compareInfo.Commits = models.ParseCommitsWithSignature(compareInfo.Commits)
compareInfo.Commits = models.ParseCommitsWithStatus(compareInfo.Commits, headRepo)
ctx.Data["Commits"] = compareInfo.Commits
ctx.Data["CommitCount"] = compareInfo.Commits.Len()
if ctx.Data["CommitCount"] == 0 {
ctx.Data["PageIsComparePull"] = false
}
if compareInfo.Commits.Len() == 1 {
c := compareInfo.Commits.Front().Value.(models.SignCommitWithStatuses)
title = strings.TrimSpace(c.UserCommit.Summary())
body := strings.Split(strings.TrimSpace(c.UserCommit.Message()), "\n")
if len(body) > 1 {
ctx.Data["content"] = strings.Join(body[1:], "\n")
}
} else {
title = headBranch
}
ctx.Data["title"] = title
ctx.Data["Username"] = headUser.Name
ctx.Data["Reponame"] = headRepo.Name
ctx.Data["IsImageFile"] = headCommit.IsImageFile
headTarget := path.Join(headUser.Name, repo.Name)
ctx.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", "commit", headCommitID)
ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", "commit", compareInfo.MergeBase)
ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", "commit", headCommitID)
return false
}
// CompareDiff show different from one commit to another commit
func CompareDiff(ctx *context.Context) {
headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
if ctx.Written() {
return
}
nothingToCompare := PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch)
if ctx.Written() {
return
}
if ctx.Data["PageIsComparePull"] == true {
pr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch)
if err != nil {
if !models.IsErrPullRequestNotExist(err) {
ctx.ServerError("GetUnmergedPullRequest", err)
return
}
} else {
ctx.Data["HasPullRequest"] = true
ctx.Data["PullRequest"] = pr
ctx.HTML(200, tplCompareDiff)
return
}
if !nothingToCompare {
// Setup information for new form.
RetrieveRepoMetas(ctx, ctx.Repo.Repository)
if ctx.Written() {
return
}
}
headBranches, err := headGitRepo.GetBranches()
if err != nil {
ctx.ServerError("GetBranches", err)
return
}
ctx.Data["HeadBranches"] = headBranches
}
beforeCommitID := ctx.Data["BeforeCommitID"].(string)
afterCommitID := ctx.Data["AfterCommitID"].(string)
ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + "..." + base.ShortSha(afterCommitID)
ctx.Data["IsRepoToolbarCommits"] = true
ctx.Data["IsDiffCompare"] = true
ctx.Data["RequireHighlightJS"] = true
ctx.Data["RequireTribute"] = true
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates)
renderAttachmentSettings(ctx)
ctx.HTML(200, tplCompare)
}

@ -28,7 +28,7 @@ import (
const ( const (
tplFork base.TplName = "repo/pulls/fork" tplFork base.TplName = "repo/pulls/fork"
tplComparePull base.TplName = "repo/pulls/compare" tplCompareDiff base.TplName = "repo/diff/compare"
tplPullCommits base.TplName = "repo/pulls/commits" tplPullCommits base.TplName = "repo/pulls/commits"
tplPullFiles base.TplName = "repo/pulls/files" tplPullFiles base.TplName = "repo/pulls/files"
@ -280,13 +280,13 @@ func setMergeTarget(ctx *context.Context, pull *models.PullRequest) {
} }
// PrepareMergedViewPullInfo show meta information for a merged pull request view page // PrepareMergedViewPullInfo show meta information for a merged pull request view page
func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullRequestInfo { func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
pull := issue.PullRequest pull := issue.PullRequest
setMergeTarget(ctx, pull) setMergeTarget(ctx, pull)
ctx.Data["HasMerged"] = true ctx.Data["HasMerged"] = true
prInfo, err := ctx.Repo.GitRepo.GetPullRequestInfo(ctx.Repo.Repository.RepoPath(), prInfo, err := ctx.Repo.GitRepo.GetCompareInfo(ctx.Repo.Repository.RepoPath(),
pull.MergeBase, pull.GetGitRefName()) pull.MergeBase, pull.GetGitRefName())
if err != nil { if err != nil {
@ -298,7 +298,7 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.P
return nil return nil
} }
ctx.ServerError("GetPullRequestInfo", err) ctx.ServerError("GetCompareInfo", err)
return nil return nil
} }
ctx.Data["NumCommits"] = prInfo.Commits.Len() ctx.Data["NumCommits"] = prInfo.Commits.Len()
@ -307,7 +307,7 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.P
} }
// PrepareViewPullInfo show meta information for a pull request preview page // PrepareViewPullInfo show meta information for a pull request preview page
func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullRequestInfo { func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
pull := issue.PullRequest pull := issue.PullRequest
@ -336,7 +336,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullReq
return nil return nil
} }
prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(repo.Owner.Name, repo.Name), prInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(repo.Owner.Name, repo.Name),
pull.BaseBranch, pull.HeadBranch) pull.BaseBranch, pull.HeadBranch)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "fatal: Not a valid object name") { if strings.Contains(err.Error(), "fatal: Not a valid object name") {
@ -347,7 +347,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullReq
return nil return nil
} }
ctx.ServerError("GetPullRequestInfo", err) ctx.ServerError("GetCompareInfo", err)
return nil return nil
} }
@ -628,266 +628,6 @@ func stopTimerIfAvailable(user *models.User, issue *models.Issue) error {
return nil return nil
} }
// ParseCompareInfo parse compare info between two commit for preparing pull request
func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) {
baseRepo := ctx.Repo.Repository
// Get compared branches information
// format: <base branch>...[<head repo>:]<head branch>
// base<-head: master...head:feature
// same repo: master...feature
var (
headUser *models.User
headBranch string
isSameRepo bool
infoPath string
err error
)
infoPath = ctx.Params("*")
infos := strings.Split(infoPath, "...")
if len(infos) != 2 {
log.Trace("ParseCompareInfo[%d]: not enough compared branches information %s", baseRepo.ID, infos)
ctx.NotFound("CompareAndPullRequest", nil)
return nil, nil, nil, nil, "", ""
}
baseBranch := infos[0]
ctx.Data["BaseBranch"] = baseBranch
// If there is no head repository, it means pull request between same repository.
headInfos := strings.Split(infos[1], ":")
if len(headInfos) == 1 {
isSameRepo = true
headUser = ctx.Repo.Owner
headBranch = headInfos[0]
} else if len(headInfos) == 2 {
headUser, err = models.GetUserByName(headInfos[0])
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.NotFound("GetUserByName", nil)
} else {
ctx.ServerError("GetUserByName", err)
}
return nil, nil, nil, nil, "", ""
}
headBranch = headInfos[1]
isSameRepo = headUser.ID == ctx.Repo.Owner.ID
} else {
ctx.NotFound("CompareAndPullRequest", nil)
return nil, nil, nil, nil, "", ""
}
ctx.Data["HeadUser"] = headUser
ctx.Data["HeadBranch"] = headBranch
ctx.Repo.PullRequest.SameRepo = isSameRepo
// Check if base branch is valid.
if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) {
ctx.NotFound("IsBranchExist", nil)
return nil, nil, nil, nil, "", ""
}
// Check if current user has fork of repository or in the same repository.
headRepo, has := models.HasForkedRepo(headUser.ID, baseRepo.ID)
if !has && !isSameRepo {
log.Trace("ParseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
var headGitRepo *git.Repository
if isSameRepo {
headRepo = ctx.Repo.Repository
headGitRepo = ctx.Repo.GitRepo
ctx.Data["BaseName"] = headUser.Name
} else {
headGitRepo, err = git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name))
ctx.Data["BaseName"] = baseRepo.OwnerName
if err != nil {
ctx.ServerError("OpenRepository", err)
return nil, nil, nil, nil, "", ""
}
}
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(models.UnitTypeCode) {
if log.IsTrace() {
log.Trace("Permission Denied: User: %-v cannot create/read pull requests or cannot read code in Repo: %-v\nUser in baseRepo has Permissions: %-+v",
ctx.User,
baseRepo,
permBase)
}
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
// user should have permission to read headrepo's codes
permHead, err := models.GetUserRepoPermission(headRepo, ctx.User)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
if !permHead.CanRead(models.UnitTypeCode) {
if log.IsTrace() {
log.Trace("Permission Denied: User: %-v cannot read code requests in Repo: %-v\nUser in headRepo has Permissions: %-+v",
ctx.User,
headRepo,
permHead)
}
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
// Check if head branch is valid.
if !headGitRepo.IsBranchExist(headBranch) {
ctx.NotFound("IsBranchExist", nil)
return nil, nil, nil, nil, "", ""
}
headBranches, err := headGitRepo.GetBranches()
if err != nil {
ctx.ServerError("GetBranches", err)
return nil, nil, nil, nil, "", ""
}
ctx.Data["HeadBranches"] = headBranches
prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
if err != nil {
ctx.ServerError("GetPullRequestInfo", err)
return nil, nil, nil, nil, "", ""
}
ctx.Data["BeforeCommitID"] = prInfo.MergeBase
return headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch
}
// PrepareCompareDiff render pull request preview diff page
func PrepareCompareDiff(
ctx *context.Context,
headUser *models.User,
headRepo *models.Repository,
headGitRepo *git.Repository,
prInfo *git.PullRequestInfo,
baseBranch, headBranch string) bool {
var (
repo = ctx.Repo.Repository
err error
title string
)
// Get diff information.
ctx.Data["CommitRepoLink"] = headRepo.Link()
headCommitID, err := headGitRepo.GetBranchCommitID(headBranch)
if err != nil {
ctx.ServerError("GetBranchCommitID", err)
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,
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
if err != nil {
ctx.ServerError("GetDiffRange", err)
return false
}
ctx.Data["Diff"] = diff
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
headCommit, err := headGitRepo.GetCommit(headCommitID)
if err != nil {
ctx.ServerError("GetCommit", err)
return false
}
prInfo.Commits = models.ValidateCommitsWithEmails(prInfo.Commits)
prInfo.Commits = models.ParseCommitsWithSignature(prInfo.Commits)
prInfo.Commits = models.ParseCommitsWithStatus(prInfo.Commits, headRepo)
ctx.Data["Commits"] = prInfo.Commits
ctx.Data["CommitCount"] = prInfo.Commits.Len()
if prInfo.Commits.Len() == 1 {
c := prInfo.Commits.Front().Value.(models.SignCommitWithStatuses)
title = strings.TrimSpace(c.UserCommit.Summary())
body := strings.Split(strings.TrimSpace(c.UserCommit.Message()), "\n")
if len(body) > 1 {
ctx.Data["content"] = strings.Join(body[1:], "\n")
}
} else {
title = headBranch
}
ctx.Data["title"] = title
ctx.Data["Username"] = headUser.Name
ctx.Data["Reponame"] = headRepo.Name
ctx.Data["IsImageFile"] = headCommit.IsImageFile
headTarget := path.Join(headUser.Name, repo.Name)
ctx.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", "commit", headCommitID)
ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", "commit", prInfo.MergeBase)
ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", "commit", headCommitID)
return false
}
// CompareAndPullRequest render pull request preview page
func CompareAndPullRequest(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
ctx.Data["PageIsComparePull"] = true
ctx.Data["IsDiffCompare"] = true
ctx.Data["RequireHighlightJS"] = true
ctx.Data["RequireTribute"] = true
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates)
renderAttachmentSettings(ctx)
headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
if ctx.Written() {
return
}
pr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch)
if err != nil {
if !models.IsErrPullRequestNotExist(err) {
ctx.ServerError("GetUnmergedPullRequest", err)
return
}
} else {
ctx.Data["HasPullRequest"] = true
ctx.Data["PullRequest"] = pr
ctx.HTML(200, tplComparePull)
return
}
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, tplComparePull)
}
// CompareAndPullRequestPost response for creating pull request // CompareAndPullRequestPost response for creating pull request
func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm) { func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm) {
ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes") ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
@ -926,7 +666,7 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
return return
} }
ctx.HTML(200, tplComparePull) ctx.HTML(200, tplCompareDiff)
return return
} }
@ -936,7 +676,7 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
return return
} }
ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplComparePull, form) ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplCompareDiff, form)
return return
} }

@ -732,9 +732,9 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/milestone", func() { m.Group("/milestone", func() {
m.Get("/:id", repo.MilestoneIssuesAndPulls) m.Get("/:id", repo.MilestoneIssuesAndPulls)
}, reqRepoIssuesOrPullsReader, context.RepoRef()) }, reqRepoIssuesOrPullsReader, context.RepoRef())
m.Combo("/compare/*", context.RepoMustNotBeArchived(), reqRepoCodeReader, reqRepoPullsReader, repo.MustAllowPulls, repo.SetEditorconfigIfExists). m.Combo("/compare/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.SetEditorconfigIfExists).
Get(repo.SetDiffViewStyle, repo.CompareAndPullRequest). Get(repo.SetDiffViewStyle, repo.CompareDiff).
Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost) Post(context.RepoMustNotBeArchived(), reqRepoPullsReader, repo.MustAllowPulls, bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
m.Group("", func() { m.Group("", func() {
m.Group("", func() { m.Group("", func() {
@ -906,9 +906,6 @@ func RegisterRoutes(m *macaron.Macaron) {
}, context.RepoRef(), reqRepoCodeReader) }, context.RepoRef(), reqRepoCodeReader)
m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)",
repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff) repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff)
m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists,
repo.SetDiffViewStyle, repo.MustBeNotEmpty, reqRepoCodeReader, repo.CompareDiff)
}, ignSignIn, context.RepoAssignment(), context.UnitTypes()) }, ignSignIn, context.RepoAssignment(), context.UnitTypes())
m.Group("/:username/:reponame", func() { m.Group("/:username/:reponame", func() {
m.Get("/stars", repo.Stars) m.Get("/stars", repo.Stars)

@ -0,0 +1,89 @@
{{template "base/head" .}}
<div class="repository diff">
{{template "repo/header" .}}
<div class="ui container {{if .IsSplitStyle}}fluid padded{{end}}">
<div class="ui top attached info clearing segment {{if .Commit.Signature}} isSigned {{if .Verification.Verified }} isVerified {{end}}{{end}}">
<a class="ui floated right blue tiny button" href="{{EscapePound .SourcePath}}">
{{.i18n.Tr "repo.diff.browse_source"}}
</a>
<h3 class="has-emoji">{{RenderCommitMessage .Commit.Message $.RepoLink $.Repository.ComposeMetas}}{{template "repo/commit_status" .CommitStatus}}</h3>
{{if IsMultilineCommitMessage .Commit.Message}}
<pre class="commit-body">{{RenderCommitBody .Commit.Message $.RepoLink $.Repository.ComposeMetas}}</pre>
{{end}}
<span class="text grey"><i class="octicon octicon-git-branch"></i>{{.BranchName}}</span>
</div>
<div class="ui attached info segment {{if .Commit.Signature}} isSigned {{if .Verification.Verified }} isVerified {{end}}{{end}}">
<div class="ui stackable grid">
<div class="nine wide column">
{{if .Author}}
<img class="ui avatar image" src="{{.Author.RelAvatarLink}}" />
{{if .Author.FullName}}
<a href="{{.Author.HomeLink}}"><strong>{{.Author.FullName}}</strong></a> {{if .IsSigned}}<{{.Commit.Author.Email}}>{{end}}
{{else}}
<a href="{{.Author.HomeLink}}"><strong>{{.Commit.Author.Name}}</strong></a> {{if .IsSigned}}<{{.Commit.Author.Email}}>{{end}}
{{end}}
{{else}}
<img class="ui avatar image" src="{{AvatarLink .Commit.Author.Email}}" />
<strong>{{.Commit.Author.Name}}</strong>
{{end}}
<span class="text grey" id="authored-time">{{TimeSince .Commit.Author.When $.Lang}}</span>
</div>
<div class="seven wide right aligned column">
<div class="ui horizontal list">
{{if .Parents}}
<div class="item">
{{.i18n.Tr "repo.diff.parent"}}
</div>
<div class="item">
{{range .Parents}}
<a class="ui blue sha label" href="{{$.RepoLink}}/commit/{{.}}">{{ShortSha .}}</a>
{{end}}
</div>
{{end}}
<div class="mobile-only"></div>
<div class="item">{{.i18n.Tr "repo.diff.commit"}}</div>
<div class="item"><span class="ui blue sha label">{{ShortSha .CommitID}}</span></div>
</div>
</div><!-- end column -->
</div><!-- end grid -->
</div>
{{if .Commit.Signature}}
{{if .Verification.Verified }}
<div class="ui bottom attached positive message">
<i class="green lock icon"></i>
<span>{{.i18n.Tr "repo.commits.signed_by"}}:</span>
<a href="{{.Verification.SigningUser.HomeLink}}"><strong>{{.Commit.Committer.Name}}</strong></a> <{{.Commit.Committer.Email}}>
<span class="pull-right"><span>{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> {{.Verification.SigningKey.KeyID}}</span>
</div>
{{else}}
<div class="ui bottom attached message">
<i class="grey unlock icon"></i>
{{.i18n.Tr .Verification.Reason}}
</div>
{{end}}
{{end}}
{{if .Note}}
<div class="ui top attached info segment message git-notes">
<i class="sticky note icon"></i>
{{.i18n.Tr "repo.diff.git-notes"}}:
{{if .NoteAuthor}}
<a href="{{.NoteAuthor.HomeLink}}">
{{if .NoteAuthor.FullName}}
<strong>{{.NoteAuthor.FullName}}</strong>
{{else}}
<strong>{{.NoteCommit.Author.Name}}</strong>
{{end}}
</a>
{{else}}
<strong>{{.NoteCommit.Author.Name}}</strong>
{{end}}
<span class="text grey" id="note-authored-time">{{TimeSince .NoteCommit.Author.When $.Lang}}</span>
</div>
<div class="ui bottom attached info segment git-notes">
<pre class="commit-body">{{RenderNote .Note $.RepoLink $.Repository.ComposeMetas}}</pre>
</div>
{{end}}
{{template "repo/diff/box" .}}
</div>
</div>
{{template "base/footer" .}}

@ -1,13 +1,13 @@
<h4 class="ui top attached header"> <h4 class="ui top attached header">
<div class="ui stackable grid"> <div class="ui stackable grid">
<div class="ten wide column"> <div class="five wide column">
{{if or .PageIsCommits (gt .CommitCount 0)}} {{if or .PageIsCommits (gt .CommitCount 0)}}
{{.CommitCount}} {{.i18n.Tr "repo.commits.commits"}} {{if .Branch}}({{.Branch}}){{end}} {{.CommitCount}} {{.i18n.Tr "repo.commits.commits"}} {{if .Branch}}({{.Branch}}){{end}}
{{else}} {{else}}
{{.i18n.Tr "repo.commits.no_commits" $.BaseBranch $.HeadBranch }} {{if .Branch}}({{.Branch}}){{end}} {{.i18n.Tr "repo.commits.no_commits" $.BaseBranch $.HeadBranch }} {{if .Branch}}({{.Branch}}){{end}}
{{end}} {{end}}
</div> </div>
<div class="six wide right aligned column"> <div class="eleven wide right aligned column">
{{if .PageIsCommits}} {{if .PageIsCommits}}
<form class="ignore-dirty" action="{{.RepoLink}}/commits/{{.BranchNameSubURL | EscapePound}}/search"> <form class="ignore-dirty" action="{{.RepoLink}}/commits/{{.BranchNameSubURL | EscapePound}}/search">
<div class="ui tiny search input"> <div class="ui tiny search input">
@ -21,7 +21,9 @@
<button class="ui black tiny button" data-panel="#add-deploy-key-panel" data-tooltip={{.i18n.Tr "repo.commits.search.tooltip"}}>{{.i18n.Tr "repo.commits.find"}}</button> <button class="ui black tiny button" data-panel="#add-deploy-key-panel" data-tooltip={{.i18n.Tr "repo.commits.search.tooltip"}}>{{.i18n.Tr "repo.commits.find"}}</button>
</form> </form>
{{else if .IsDiffCompare}} {{else if .IsDiffCompare}}
<a href="{{$.CommitRepoLink}}/commit/{{.BeforeCommitID}}" class="ui green sha label">{{ShortSha .BeforeCommitID}}</a> ... <a href="{{$.CommitRepoLink}}/commit/{{.AfterCommitID}}" class="ui green sha label">{{ShortSha .AfterCommitID}}</a> <a href="{{$.CommitRepoLink}}/commit/{{.BeforeCommitID}}" class="ui green sha label">{{if not .BaseIsCommit}}{{if .BaseIsBranch}}<i class="octicon octicon-git-branch"></i>{{else if .BaseIsTag}}<i class="octicon octicon-tag"></i>{{end}}{{.BaseBranch}}{{else}}{{ShortSha .BaseBranch}}{{end}}</a>
...
<a href="{{$.CommitRepoLink}}/commit/{{.AfterCommitID}}" class="ui green sha label">{{if not .HeadIsCommit}}{{if .HeadIsBranch}}<i class="octicon octicon-git-branch"></i>{{else if .HeadIsTag}}<i class="octicon octicon-tag"></i>{{end}}{{.HeadBranch}}{{else}}{{ShortSha .HeadBranch}}{{end}}</a>
{{end}} {{end}}
</div> </div>
</div> </div>

@ -0,0 +1,74 @@
{{template "base/head" .}}
<div class="repository diff {{if .PageIsComparePull}}compare pull{{end}}">
{{template "repo/header" .}}
<div class="ui container {{if .IsSplitStyle}}fluid padded{{end}}">
{{if .PageIsComparePull}}
<h2 class="ui header">
{{.i18n.Tr "repo.pulls.compare_changes"}}
<div class="sub header">{{.i18n.Tr "repo.pulls.compare_changes_desc"}}</div>
</h2>
<div class="ui segment choose branch">
<span class="octicon octicon-git-compare"></span>
<div class="ui floating filter dropdown" data-no-results="{{.i18n.Tr "repo.pulls.no_results"}}">
<div class="ui basic small button">
<span class="text">{{.i18n.Tr "repo.pulls.compare_base"}}: {{$.BaseName}}:{{$.BaseBranch}}</span>
<i class="dropdown icon"></i>
</div>
<div class="menu">
<div class="ui icon search input">
<i class="filter icon"></i>
<input name="search" placeholder="{{.i18n.Tr "repo.pulls.filter_branch"}}...">
</div>
<div class="scrolling menu">
{{range .Branches}}
<div class="item {{if eq $.BaseBranch .}}selected{{end}}" data-url="{{$.RepoLink}}/compare/{{EscapePound .}}...{{if not $.PullRequestCtx.SameRepo}}{{$.HeadUser.Name}}:{{end}}{{EscapePound $.HeadBranch}}">{{$.BaseName}}:{{.}}</div>
{{end}}
</div>
</div>
</div>
...
<div class="ui floating filter dropdown">
<div class="ui basic small button">
<span class="text">{{.i18n.Tr "repo.pulls.compare_compare"}}: {{$.HeadUser.Name}}:{{$.HeadBranch}}</span>
<i class="dropdown icon"></i>
</div>
<div class="menu">
<div class="ui icon search input">
<i class="filter icon"></i>
<input name="search" placeholder="{{.i18n.Tr "repo.pulls.filter_branch"}}...">
</div>
<div class="scrolling menu">
{{range .HeadBranches}}
<div class="{{if eq $.HeadBranch .}}selected{{end}} item" data-url="{{$.RepoLink}}/compare/{{EscapePound $.BaseBranch}}...{{if not $.PullRequestCtx.SameRepo}}{{$.HeadUser.Name}}:{{end}}{{EscapePound .}}">{{$.HeadUser.Name}}:{{.}}</div>
{{end}}
</div>
</div>
</div>
</div>
{{end}}
{{if .IsNothingToCompare}}
<div class="ui segment">{{.i18n.Tr "repo.pulls.nothing_to_compare"}}</div>
{{else if .PageIsComparePull}}
{{if .HasPullRequest}}
<div class="ui segment">
{{.i18n.Tr "repo.pulls.has_pull_request" $.RepoLink $.RepoRelPath .PullRequest.Index | Safe}}
</div>
{{else}}
<div class="ui info message show-form-container">
<button class="ui button green show-form">{{.i18n.Tr "repo.pulls.new"}}</button>
</div>
<div class="pullrequest-form" style="display: none">
{{template "repo/issue/new_form" .}}
</div>
{{template "repo/commits_table" .}}
{{template "repo/diff/box" .}}
{{end}}
{{else}}
{{template "repo/commits_table" .}}
{{template "repo/diff/box" .}}
{{end}}
</div>
</div>
{{template "base/footer" .}}

@ -1,94 +0,0 @@
{{template "base/head" .}}
<div class="repository diff">
{{template "repo/header" .}}
<div class="ui container {{if .IsSplitStyle}}fluid padded{{end}}">
{{if .IsDiffCompare }}
{{template "repo/commits_table" .}}
{{else}}
<div class="ui top attached info clearing segment {{if .Commit.Signature}} isSigned {{if .Verification.Verified }} isVerified {{end}}{{end}}">
<a class="ui floated right blue tiny button" href="{{EscapePound .SourcePath}}">
{{.i18n.Tr "repo.diff.browse_source"}}
</a>
<h3 class="has-emoji">{{RenderCommitMessage .Commit.Message $.RepoLink $.Repository.ComposeMetas}}{{template "repo/commit_status" .CommitStatus}}</h3>
{{if IsMultilineCommitMessage .Commit.Message}}
<pre class="commit-body">{{RenderCommitBody .Commit.Message $.RepoLink $.Repository.ComposeMetas}}</pre>
{{end}}
<span class="text grey"><i class="octicon octicon-git-branch"></i>{{.BranchName}}</span>
</div>
<div class="ui attached info segment {{if .Commit.Signature}} isSigned {{if .Verification.Verified }} isVerified {{end}}{{end}}">
<div class="ui stackable grid">
<div class="nine wide column">
{{if .Author}}
<img class="ui avatar image" src="{{.Author.RelAvatarLink}}" />
{{if .Author.FullName}}
<a href="{{.Author.HomeLink}}"><strong>{{.Author.FullName}}</strong></a> {{if .IsSigned}}<{{.Commit.Author.Email}}>{{end}}
{{else}}
<a href="{{.Author.HomeLink}}"><strong>{{.Commit.Author.Name}}</strong></a> {{if .IsSigned}}<{{.Commit.Author.Email}}>{{end}}
{{end}}
{{else}}
<img class="ui avatar image" src="{{AvatarLink .Commit.Author.Email}}" />
<strong>{{.Commit.Author.Name}}</strong>
{{end}}
<span class="text grey" id="authored-time">{{TimeSince .Commit.Author.When $.Lang}}</span>
</div>
<div class="seven wide right aligned column">
<div class="ui horizontal list">
{{if .Parents}}
<div class="item">
{{.i18n.Tr "repo.diff.parent"}}
</div>
<div class="item">
{{range .Parents}}
<a class="ui blue sha label" href="{{$.RepoLink}}/commit/{{.}}">{{ShortSha .}}</a>
{{end}}
</div>
{{end}}
<div class="mobile-only"></div>
<div class="item">{{.i18n.Tr "repo.diff.commit"}}</div>
<div class="item"><span class="ui blue sha label">{{ShortSha .CommitID}}</span></div>
</div>
</div><!-- end column -->
</div><!-- end grid -->
</div>
{{if .Commit.Signature}}
{{if .Verification.Verified }}
<div class="ui bottom attached positive message">
<i class="green lock icon"></i>
<span>{{.i18n.Tr "repo.commits.signed_by"}}:</span>
<a href="{{.Verification.SigningUser.HomeLink}}"><strong>{{.Commit.Committer.Name}}</strong></a> <{{.Commit.Committer.Email}}>
<span class="pull-right"><span>{{.i18n.Tr "repo.commits.gpg_key_id"}}:</span> {{.Verification.SigningKey.KeyID}}</span>
</div>
{{else}}
<div class="ui bottom attached message">
<i class="grey unlock icon"></i>
{{.i18n.Tr .Verification.Reason}}
</div>
{{end}}
{{end}}
{{if .Note}}
<div class="ui top attached info segment message git-notes">
<i class="sticky note icon"></i>
{{.i18n.Tr "repo.diff.git-notes"}}:
{{if .NoteAuthor}}
<a href="{{.NoteAuthor.HomeLink}}">
{{if .NoteAuthor.FullName}}
<strong>{{.NoteAuthor.FullName}}</strong>
{{else}}
<strong>{{.NoteCommit.Author.Name}}</strong>
{{end}}
</a>
{{else}}
<strong>{{.NoteCommit.Author.Name}}</strong>
{{end}}
<span class="text grey" id="note-authored-time">{{TimeSince .NoteCommit.Author.When $.Lang}}</span>
</div>
<div class="ui bottom attached info segment git-notes">
<pre class="commit-body">{{RenderNote .Note $.RepoLink $.Repository.ComposeMetas}}</pre>
</div>
{{end}}
{{end}}
{{template "repo/diff/box" .}}
</div>
</div>
{{template "base/footer" .}}

@ -1,69 +0,0 @@
{{template "base/head" .}}
<div class="repository compare pull diff">
{{template "repo/header" .}}
<div class="ui container">
<div class="sixteen wide column page grid">
<h2 class="ui header">
{{.i18n.Tr "repo.pulls.compare_changes"}}
<div class="sub header">{{.i18n.Tr "repo.pulls.compare_changes_desc"}}</div>
</h2>
<div class="ui segment choose branch">
<span class="octicon octicon-git-compare"></span>
<div class="ui floating filter dropdown" data-no-results="{{.i18n.Tr "repo.pulls.no_results"}}">
<div class="ui basic small button">
<span class="text">{{.i18n.Tr "repo.pulls.compare_base"}}: {{$.BaseName}}:{{$.BaseBranch}}</span>
<i class="dropdown icon"></i>
</div>
<div class="menu">
<div class="ui icon search input">
<i class="filter icon"></i>
<input name="search" placeholder="{{.i18n.Tr "repo.pulls.filter_branch"}}...">
</div>
<div class="scrolling menu">
{{range .Branches}}
<div class="item {{if eq $.BaseBranch .}}selected{{end}}" data-url="{{$.RepoLink}}/compare/{{EscapePound .}}...{{if not $.PullRequestCtx.SameRepo}}{{$.HeadUser.Name}}:{{end}}{{EscapePound $.HeadBranch}}">{{$.BaseName}}:{{.}}</div>
{{end}}
</div>
</div>
</div>
...
<div class="ui floating filter dropdown">
<div class="ui basic small button">
<span class="text">{{.i18n.Tr "repo.pulls.compare_compare"}}: {{$.HeadUser.Name}}:{{$.HeadBranch}}</span>
<i class="dropdown icon"></i>
</div>
<div class="menu">
<div class="ui icon search input">
<i class="filter icon"></i>
<input name="search" placeholder="{{.i18n.Tr "repo.pulls.filter_branch"}}...">
</div>
<div class="scrolling menu">
{{range .HeadBranches}}
<div class="{{if eq $.HeadBranch .}}selected{{end}} item" data-url="{{$.RepoLink}}/compare/{{EscapePound $.BaseBranch}}...{{if not $.PullRequestCtx.SameRepo}}{{$.HeadUser.Name}}:{{end}}{{EscapePound .}}">{{$.HeadUser.Name}}:{{.}}</div>
{{end}}
</div>
</div>
</div>
</div>
{{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.Index | Safe}}
</div>
{{else if eq .CommitCount 0 }}
{{template "repo/commits_table" .}}
{{template "repo/diff/box" .}}
{{else}}
{{template "repo/issue/new_form" .}}
{{template "repo/commits_table" .}}
{{template "repo/diff/box" .}}
{{end}}
</div>
</div>
</div>
{{template "base/footer" .}}
Loading…
Cancel
Save