From 311ce2d1d06c26d0d5a3b745493995813e2ea6f2 Mon Sep 17 00:00:00 2001
From: Mario Lubenka <mario.lubenka@googlemail.com>
Date: Fri, 7 Jun 2019 22:29:29 +0200
Subject: [PATCH] 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
---
 models/pull.go                                |   3 +-
 modules/git/repo_commit.go                    |  10 +
 modules/git/{repo_pull.go => repo_compare.go} |  54 +--
 ...repo_pull_test.go => repo_compare_test.go} |   0
 public/css/index.css                          |   1 +
 public/js/index.js                            |   9 +-
 public/less/_repository.less                  |   3 +
 routers/api/v1/repo/pull.go                   |  14 +-
 routers/repo/commit.go                        |  59 +--
 routers/repo/compare.go                       | 337 ++++++++++++++++++
 routers/repo/pull.go                          | 278 +--------------
 routers/routes/routes.go                      |   9 +-
 templates/repo/commit_page.tmpl               |  89 +++++
 templates/repo/commits_table.tmpl             |   8 +-
 templates/repo/diff/compare.tmpl              |  74 ++++
 templates/repo/diff/page.tmpl                 |  94 -----
 templates/repo/pulls/compare.tmpl             |  69 ----
 17 files changed, 584 insertions(+), 527 deletions(-)
 rename modules/git/{repo_pull.go => repo_compare.go} (55%)
 rename modules/git/{repo_pull_test.go => repo_compare_test.go} (100%)
 create mode 100644 routers/repo/compare.go
 create mode 100644 templates/repo/commit_page.tmpl
 create mode 100644 templates/repo/diff/compare.tmpl
 delete mode 100644 templates/repo/diff/page.tmpl
 delete mode 100644 templates/repo/pulls/compare.tmpl

diff --git a/models/pull.go b/models/pull.go
index fe18765fc0..5ac1126314 100644
--- a/models/pull.go
+++ b/models/pull.go
@@ -1144,8 +1144,7 @@ func (pr *PullRequest) UpdatePatch() (err error) {
 	defer func() {
 		headGitRepo.RemoveRemote(tmpRemote)
 	}()
-	remoteBranch := "remotes/" + tmpRemote + "/" + pr.BaseBranch
-	pr.MergeBase, err = headGitRepo.GetMergeBase(remoteBranch, pr.HeadBranch)
+	pr.MergeBase, err = headGitRepo.GetMergeBase(tmpRemote, pr.BaseBranch, pr.HeadBranch)
 	if err != nil {
 		return fmt.Errorf("GetMergeBase: %v", err)
 	} else if err = pr.Update(); err != nil {
diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go
index b631f9341e..501ea88e40 100644
--- a/modules/git/repo_commit.go
+++ b/modules/git/repo_commit.go
@@ -27,6 +27,16 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) {
 	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.
 func (repo *Repository) GetBranchCommitID(name string) (string, error) {
 	return repo.GetRefCommitID(BranchPrefix + name)
diff --git a/modules/git/repo_pull.go b/modules/git/repo_compare.go
similarity index 55%
rename from modules/git/repo_pull.go
rename to modules/git/repo_compare.go
index 65c5414551..e7a1d72a85 100644
--- a/modules/git/repo_pull.go
+++ b/modules/git/repo_compare.go
@@ -1,4 +1,5 @@
 // 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
 // license that can be found in the LICENSE file.
 
@@ -14,55 +15,66 @@ import (
 	"time"
 )
 
-// PullRequestInfo represents needed information for a pull request.
-type PullRequestInfo struct {
+// CompareInfo represents needed information for comparing references.
+type CompareInfo struct {
 	MergeBase string
 	Commits   *list.List
 	NumFiles  int
 }
 
 // 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)
 	return strings.TrimSpace(stdout), err
 }
 
-// GetPullRequestInfo generates and returns pull request information
-// between base and head branches of repositories.
-func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch string) (_ *PullRequestInfo, err error) {
-	var remoteBranch string
+// GetCompareInfo generates and returns compare information between base and head branches of repositories.
+func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string) (_ *CompareInfo, err error) {
+	var (
+		remoteBranch string
+		tmpRemote    string
+	)
 
 	// We don't need a temporary remote for same repository.
 	if repo.Path != basePath {
 		// 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 {
 			return nil, fmt.Errorf("AddRemote: %v", err)
 		}
 		defer repo.RemoveRemote(tmpRemote)
-
-		remoteBranch = "remotes/" + tmpRemote + "/" + baseBranch
-	} else {
-		remoteBranch = baseBranch
 	}
 
-	prInfo := new(PullRequestInfo)
-	prInfo.MergeBase, err = repo.GetMergeBase(remoteBranch, headBranch)
+	compareInfo := new(CompareInfo)
+	compareInfo.MergeBase, err = repo.GetMergeBase(tmpRemote, baseBranch, headBranch)
 	if err == nil {
 		// 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 {
 			return nil, err
 		}
-		prInfo.Commits, err = repo.parsePrettyFormatLogToList(logs)
+		compareInfo.Commits, err = repo.parsePrettyFormatLogToList(logs)
 		if err != nil {
 			return nil, fmt.Errorf("parsePrettyFormatLogToList: %v", err)
 		}
 	} else {
-		prInfo.Commits = list.New()
-		prInfo.MergeBase, err = GetFullCommitID(repo.Path, remoteBranch)
+		compareInfo.Commits = list.New()
+		compareInfo.MergeBase, err = GetFullCommitID(repo.Path, remoteBranch)
 		if err != nil {
-			prInfo.MergeBase = remoteBranch
+			compareInfo.MergeBase = remoteBranch
 		}
 	}
 
@@ -71,9 +83,9 @@ func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch stri
 	if err != nil {
 		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.
diff --git a/modules/git/repo_pull_test.go b/modules/git/repo_compare_test.go
similarity index 100%
rename from modules/git/repo_pull_test.go
rename to modules/git/repo_compare_test.go
diff --git a/public/css/index.css b/public/css/index.css
index 8950cc7038..d192f43d15 100644
--- a/public/css/index.css
+++ b/public/css/index.css
@@ -591,6 +591,7 @@ footer .ui.left,footer .ui.right{line-height:40px}
 .repository .milestone.list>.item .content{padding-top:10px}
 .repository.new.milestone textarea{height:200px}
 .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 .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}
diff --git a/public/js/index.js b/public/js/index.js
index ed3198594a..28023e1061 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -959,8 +959,15 @@ function initRepository() {
     });
 
     // Pull request
-    if ($('.repository.compare.pull').length > 0) {
+    var $repoComparePull = $('.repository.compare.pull');
+    if ($repoComparePull.length > 0) {
         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
diff --git a/public/less/_repository.less b/public/less/_repository.less
index 9956bbce74..fcc153e31e 100644
--- a/public/less/_repository.less
+++ b/public/less/_repository.less
@@ -1109,6 +1109,9 @@
     }
 
     &.compare.pull {
+        .show-form-container {
+            text-align: left;
+        }
         .choose.branch {
             .octicon {
                 padding-right: 10px;
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index f53ab4b8f3..0e1db144b1 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -188,7 +188,7 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
 	)
 
 	// 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() {
 		return
 	}
@@ -240,7 +240,7 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
 		milestoneID = milestone.ID
 	}
 
-	patch, err := headGitRepo.GetPatch(prInfo.MergeBase, headBranch)
+	patch, err := headGitRepo.GetPatch(compareInfo.MergeBase, headBranch)
 	if err != nil {
 		ctx.Error(500, "GetPatch", err)
 		return
@@ -277,7 +277,7 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
 		BaseBranch:   baseBranch,
 		HeadRepo:     headRepo,
 		BaseRepo:     repo,
-		MergeBase:    prInfo.MergeBase,
+		MergeBase:    compareInfo.MergeBase,
 		Type:         models.PullRequestGitea,
 	}
 
@@ -600,7 +600,7 @@ func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) {
 	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
 
 	// Get compared branches information
@@ -712,11 +712,11 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
 		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 {
-		ctx.Error(500, "GetPullRequestInfo", err)
+		ctx.Error(500, "GetCompareInfo", err)
 		return nil, nil, nil, nil, "", ""
 	}
 
-	return headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch
+	return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch
 }
diff --git a/routers/repo/commit.go b/routers/repo/commit.go
index 870ff568f3..dde6d8f321 100644
--- a/routers/repo/commit.go
+++ b/routers/repo/commit.go
@@ -19,9 +19,9 @@ import (
 )
 
 const (
-	tplCommits base.TplName = "repo/commits"
-	tplGraph   base.TplName = "repo/graph"
-	tplDiff    base.TplName = "repo/diff/page"
+	tplCommits    base.TplName = "repo/commits"
+	tplGraph      base.TplName = "repo/graph"
+	tplCommitPage base.TplName = "repo/commit_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["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
@@ -276,54 +276,3 @@ func RawDiff(ctx *context.Context) {
 		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)
-}
diff --git a/routers/repo/compare.go b/routers/repo/compare.go
new file mode 100644
index 0000000000..a85084791d
--- /dev/null
+++ b/routers/repo/compare.go
@@ -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)
+}
diff --git a/routers/repo/pull.go b/routers/repo/pull.go
index 412750dd55..182f715545 100644
--- a/routers/repo/pull.go
+++ b/routers/repo/pull.go
@@ -28,7 +28,7 @@ import (
 
 const (
 	tplFork        base.TplName = "repo/pulls/fork"
-	tplComparePull base.TplName = "repo/pulls/compare"
+	tplCompareDiff base.TplName = "repo/diff/compare"
 	tplPullCommits base.TplName = "repo/pulls/commits"
 	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
-func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullRequestInfo {
+func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
 	pull := issue.PullRequest
 
 	setMergeTarget(ctx, pull)
 	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())
 
 	if err != nil {
@@ -298,7 +298,7 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.P
 			return nil
 		}
 
-		ctx.ServerError("GetPullRequestInfo", err)
+		ctx.ServerError("GetCompareInfo", err)
 		return nil
 	}
 	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
-func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullRequestInfo {
+func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
 	repo := ctx.Repo.Repository
 	pull := issue.PullRequest
 
@@ -336,7 +336,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullReq
 		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)
 	if err != nil {
 		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
 		}
 
-		ctx.ServerError("GetPullRequestInfo", err)
+		ctx.ServerError("GetCompareInfo", err)
 		return nil
 	}
 
@@ -628,266 +628,6 @@ func stopTimerIfAvailable(user *models.User, issue *models.Issue) error {
 	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
 func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm) {
 	ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
@@ -926,7 +666,7 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
 			return
 		}
 
-		ctx.HTML(200, tplComparePull)
+		ctx.HTML(200, tplCompareDiff)
 		return
 	}
 
@@ -936,7 +676,7 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
 			return
 		}
 
-		ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplComparePull, form)
+		ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplCompareDiff, form)
 		return
 	}
 
diff --git a/routers/routes/routes.go b/routers/routes/routes.go
index eb5f73768e..4c736f63ed 100644
--- a/routers/routes/routes.go
+++ b/routers/routes/routes.go
@@ -732,9 +732,9 @@ func RegisterRoutes(m *macaron.Macaron) {
 		m.Group("/milestone", func() {
 			m.Get("/:id", repo.MilestoneIssuesAndPulls)
 		}, reqRepoIssuesOrPullsReader, context.RepoRef())
-		m.Combo("/compare/*", context.RepoMustNotBeArchived(), reqRepoCodeReader, reqRepoPullsReader, repo.MustAllowPulls, repo.SetEditorconfigIfExists).
-			Get(repo.SetDiffViewStyle, repo.CompareAndPullRequest).
-			Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
+		m.Combo("/compare/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.SetEditorconfigIfExists).
+			Get(repo.SetDiffViewStyle, repo.CompareDiff).
+			Post(context.RepoMustNotBeArchived(), reqRepoPullsReader, repo.MustAllowPulls, bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
 
 		m.Group("", func() {
 			m.Group("", func() {
@@ -906,9 +906,6 @@ func RegisterRoutes(m *macaron.Macaron) {
 		}, context.RepoRef(), reqRepoCodeReader)
 		m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)",
 			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())
 	m.Group("/:username/:reponame", func() {
 		m.Get("/stars", repo.Stars)
diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl
new file mode 100644
index 0000000000..0cfdf5156d
--- /dev/null
+++ b/templates/repo/commit_page.tmpl
@@ -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" .}}
diff --git a/templates/repo/commits_table.tmpl b/templates/repo/commits_table.tmpl
index 66bfd0d831..10f4b60483 100644
--- a/templates/repo/commits_table.tmpl
+++ b/templates/repo/commits_table.tmpl
@@ -1,13 +1,13 @@
 <h4 class="ui top attached header">
 	<div class="ui stackable grid">
-		<div class="ten wide column">
+		<div class="five wide column">
 			{{if or .PageIsCommits (gt .CommitCount 0)}}
 				{{.CommitCount}} {{.i18n.Tr "repo.commits.commits"}} {{if .Branch}}({{.Branch}}){{end}}
 			{{else}}
 				{{.i18n.Tr "repo.commits.no_commits" $.BaseBranch $.HeadBranch }} {{if .Branch}}({{.Branch}}){{end}}
 			{{end}}
 		</div>
-		<div class="six wide right aligned column">
+		<div class="eleven wide right aligned column">
 			{{if .PageIsCommits}}
 				<form class="ignore-dirty" action="{{.RepoLink}}/commits/{{.BranchNameSubURL | EscapePound}}/search">
 					<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>
 				</form>
 			{{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}}
 		</div>
 	</div>
diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl
new file mode 100644
index 0000000000..cc727422d7
--- /dev/null
+++ b/templates/repo/diff/compare.tmpl
@@ -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" .}}
diff --git a/templates/repo/diff/page.tmpl b/templates/repo/diff/page.tmpl
deleted file mode 100644
index c35e2a415b..0000000000
--- a/templates/repo/diff/page.tmpl
+++ /dev/null
@@ -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" .}}
diff --git a/templates/repo/pulls/compare.tmpl b/templates/repo/pulls/compare.tmpl
deleted file mode 100644
index 2296acf1df..0000000000
--- a/templates/repo/pulls/compare.tmpl
+++ /dev/null
@@ -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" .}}