aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsebastian-sauer <sauer.sebastian@gmail.com>2023-07-28 21:18:12 +0200
committerGitHub <noreply@github.com>2023-07-28 21:18:12 +0200
commit55532061c83d38d33ef48bdc5eeac0f652844e8a (patch)
treed6d21c0afb8b6df197e6eb618274dd16948f3753
parent4971a1054317ae68b1eb59a3dc30f61c7503dadc (diff)
downloadgitea-55532061c83d38d33ef48bdc5eeac0f652844e8a.tar.gz
gitea-55532061c83d38d33ef48bdc5eeac0f652844e8a.zip
Add commits dropdown in PR files view and allow commit by commit review (#25528)
This PR adds a new dropdown to select a commit or a commit range (shift-click like github) of a Pull Request. After selection of a commit only the changes of this commit will be shown. When selecting a range of commits the diff of this range is shown. This allows to review a PR commit by commit or by viewing only commit ranges. The "Show changes since your last review" mechanism github uses is implemented, too. When reviewing a single commit or a commit range the "Viewed" functionality is disabled. ## Screenshots ### The commit dropdown ![image](https://github.com/go-gitea/gitea/assets/51889757/0db3ae62-1272-436c-be64-4730c5d611e3) ### Selecting a commit range ![image](https://github.com/go-gitea/gitea/assets/51889757/ad81eedb-8437-42b0-8073-2d940c25fe8f) ### Show changes of a single commit only ![image](https://github.com/go-gitea/gitea/assets/51889757/6b1a113b-73ef-4ecc-adf6-bc2340bb8f97) ### Show changes of a commit range ![image](https://github.com/go-gitea/gitea/assets/51889757/6401b358-cd66-4c09-8baa-6cf6177f23a7) Fixes https://github.com/go-gitea/gitea/issues/20989 Fixes https://github.com/go-gitea/gitea/issues/19263 --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: KN4CK3R <admin@oldschoolhack.me> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: delvh <dev.lh@web.de>
-rw-r--r--models/fixtures/issue.yml17
-rw-r--r--models/fixtures/pull_request.yml13
-rw-r--r--models/fixtures/repo_unit.yml30
-rw-r--r--models/fixtures/repository.yml31
-rw-r--r--models/fixtures/user.yml2
-rw-r--r--models/issues/issue_test.go2
-rw-r--r--models/issues/review_list.go2
-rw-r--r--models/repo/repo_list_test.go6
-rw-r--r--options/locale/locale_en-US.ini7
-rw-r--r--routers/web/repo/pull.go116
-rw-r--r--routers/web/user/home_test.go2
-rw-r--r--routers/web/web.go10
-rw-r--r--services/pull/pull.go71
-rw-r--r--templates/repo/diff/box.tmpl24
-rw-r--r--templates/repo/diff/new_review.tmpl4
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/HEAD1
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/config4
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/description1
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/info/exclude6
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/info/refs3
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/logs/HEAD1
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/logs/refs/heads/branch11
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/logs/refs/heads/main1
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/0a/6dda431c72a6a4aac05b98e319972a1a55e01cbin0 -> 32 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/0c/396a509b64fd4e2e55649d100b86e8b96cc0e5bin0 -> 32 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/10/0ef49565829e7bd83057d2dab88f58b00db831bin0 -> 271 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/10/9ab1c0b84e088d7edcf018379518b49361f285bin0 -> 32 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/19/78192d98bb1b65e11c2cf37da854fbf94bffd6bin0 -> 162 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/1e/67d753ac1f9097eff26f9d33eb80182344b72cbin0 -> 87 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/23/576dd018294e476c06e569b6b0f170d05587052
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/28/16bffda09c0f23775ea4be279de004d28a3803bin0 -> 245 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/35/f03b5e176ee6d24c86b5cca7009a5b0ba2a026bin0 -> 32 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/38/cdad2e40c989aabab3f2d0a27faf0f7be617d5bin0 -> 167 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/3e/64625bd6eb5bcba69ac97de6c8f507402df861bin0 -> 162 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/4b/860706d3eec5858324d4ba00db0423ca4cbf50bin0 -> 54 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/4c/a8bcaf27e28504df7bf996819665986b01c847bin0 -> 163 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/52/84ca7f5757816e67c098224a8367aa2544222dbin0 -> 193 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/53/9a24812705f77484568e6ad7db84764c1903c8bin0 -> 32 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/70/8605e8984e7fb9be58818e0e6d9f21bcefd63ebin0 -> 33 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/74/7ddb3506a4fa04a7747808eb56ae16f9e933dcbin0 -> 162 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/7e/d1d42eda9110676d5c3a7721965d6ed1afe83cbin0 -> 324 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/81/1d46c7e518f4f180afb862c0db5cb8c80529ce2
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/83/2d33e438d2b4a86fba81cb67b32d1d61a828cbbin0 -> 32 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/83/7d5c8125633d7d258f93b998e867eab01455203
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/87/cdc1333f5f117a92f3cef78ebe0301114b36102
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/92/70b08497106eaa65fce8aa91f37c4780f76909bin0 -> 140 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/96/cef4a7b72b3c208340ae6f0cf55a93e9077c93bin0 -> 163 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/97/0c5deb117526983f554eaaa1b59102d3e3e0f7bin0 -> 32 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/c5/626fc9eff57eb1bb7b796b01d4d0f2f3f792a22
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/c7/04db5794097441aa2d9dd834d5b7e2f8f08108bin0 -> 163 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/cb/ff181af4c9c7fee3cf6c106699e07d9a3f54e6bin0 -> 128 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/d1/8e427f4011e74e96a31823c938be26eebab53bbin0 -> 114 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/d2/5795e38fbc1b4839697e834b957d61c83d994fbin0 -> 32 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/d6/6f456f0813a5841fbc03e5f1c47304dc675695bin0 -> 32 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/e1/7e0fa20f3d2125916f2fb2f51f19240678cb83bin0 -> 219 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/ec/d9fdda5c814055ee619513e1c388ba1bbcb280bin0 -> 32 bytes
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/objects/info/packs1
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/refs/heads/branch11
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/refs/heads/main1
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/refs/heads/master1
-rw-r--r--tests/gitea-repositories-meta/user2/commitsonpr.git/refs/pull/1/head1
-rw-r--r--tests/integration/api_issue_test.go14
-rw-r--r--tests/integration/api_nodeinfo_test.go2
-rw-r--r--tests/integration/api_repo_test.go6
-rw-r--r--tests/integration/issue_test.go14
-rw-r--r--tests/integration/pull_diff_test.go58
-rw-r--r--web_src/css/base.css5
-rw-r--r--web_src/js/components/DiffCommitSelector.vue299
-rw-r--r--web_src/js/features/repo-diff-commitselect.js10
-rw-r--r--web_src/js/features/repo-diff.js2
-rw-r--r--web_src/js/svg.js2
71 files changed, 748 insertions, 35 deletions
diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml
index 174345ff5a..fa72f9b647 100644
--- a/models/fixtures/issue.yml
+++ b/models/fixtures/issue.yml
@@ -304,3 +304,20 @@
created_unix: 946684830
updated_unix: 978307200
is_locked: false
+
+-
+ id: 19
+ repo_id: 58
+ index: 1
+ poster_id: 2
+ original_author_id: 0
+ name: issue for pr
+ content: content
+ milestone_id: 0
+ priority: 0
+ is_closed: false
+ is_pull: true
+ num_comments: 0
+ created_unix: 946684830
+ updated_unix: 978307200
+ is_locked: false
diff --git a/models/fixtures/pull_request.yml b/models/fixtures/pull_request.yml
index 165437f032..e5589ac703 100644
--- a/models/fixtures/pull_request.yml
+++ b/models/fixtures/pull_request.yml
@@ -76,3 +76,16 @@
base_branch: master
merge_base: 2a47ca4b614a9f5a
has_merged: false
+
+-
+ id: 7
+ type: 0 # gitea pull request
+ status: 2 # mergable
+ issue_id: 19
+ index: 1
+ head_repo_id: 58
+ base_repo_id: 58
+ head_branch: branch1
+ base_branch: main
+ merge_base: cbff181af4c9c7fee3cf6c106699e07d9a3f54e6
+ has_merged: false
diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml
index 5bb974a7d7..bb8715a202 100644
--- a/models/fixtures/repo_unit.yml
+++ b/models/fixtures/repo_unit.yml
@@ -607,3 +607,33 @@
repo_id: 52
type: 1
created_unix: 946684810
+
+-
+ id: 91
+ repo_id: 58
+ type: 1
+ created_unix: 946684810
+
+-
+ id: 92
+ repo_id: 58
+ type: 2
+ created_unix: 946684810
+
+-
+ id: 93
+ repo_id: 58
+ type: 3
+ created_unix: 946684810
+
+-
+ id: 94
+ repo_id: 58
+ type: 4
+ created_unix: 946684810
+
+-
+ id: 95
+ repo_id: 58
+ type: 5
+ created_unix: 946684810
diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml
index 050a9e2d06..15668e6cae 100644
--- a/models/fixtures/repository.yml
+++ b/models/fixtures/repository.yml
@@ -1662,3 +1662,34 @@
is_private: false
status: 0
num_issues: 0
+
+-
+ id: 58 # org public repo
+ owner_id: 2
+ owner_name: user2
+ lower_name: commitsonpr
+ name: commitsonpr
+ default_branch: main
+ num_watches: 0
+ num_stars: 0
+ num_forks: 0
+ num_issues: 0
+ num_closed_issues: 0
+ num_pulls: 1
+ num_closed_pulls: 0
+ num_milestones: 0
+ num_closed_milestones: 0
+ num_projects: 0
+ num_closed_projects: 0
+ is_private: false
+ is_empty: false
+ is_archived: false
+ is_mirror: false
+ status: 0
+ is_fork: false
+ fork_id: 0
+ is_template: false
+ template_id: 0
+ size: 0
+ is_fsck_enabled: true
+ close_issues_via_commit_in_any_branch: false
diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml
index 26bb7a9f4b..c7c5c024be 100644
--- a/models/fixtures/user.yml
+++ b/models/fixtures/user.yml
@@ -66,7 +66,7 @@
num_followers: 2
num_following: 1
num_stars: 2
- num_repos: 13
+ num_repos: 14
num_teams: 0
num_members: 0
visibility: 0
diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go
index 7f1eab1971..44e59f5cc4 100644
--- a/models/issues/issue_test.go
+++ b/models/issues/issue_test.go
@@ -538,7 +538,7 @@ func TestCountIssues(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
count, err := issues_model.CountIssues(db.DefaultContext, &issues_model.IssuesOptions{})
assert.NoError(t, err)
- assert.EqualValues(t, 18, count)
+ assert.EqualValues(t, 19, count)
}
func TestIssueLoadAttributes(t *testing.T) {
diff --git a/models/issues/review_list.go b/models/issues/review_list.go
index c044fe915a..9f50d8e09d 100644
--- a/models/issues/review_list.go
+++ b/models/issues/review_list.go
@@ -114,7 +114,7 @@ func FindLatestReviews(ctx context.Context, opts FindReviewOptions) (ReviewList,
}
sess.In("id", builder.
- Select("max ( id ) ").
+ Select("max(id)").
From("review").
Where(cond).
GroupBy("reviewer_id"))
diff --git a/models/repo/repo_list_test.go b/models/repo/repo_list_test.go
index 573281ea0b..e013953c68 100644
--- a/models/repo/repo_list_test.go
+++ b/models/repo/repo_list_test.go
@@ -235,12 +235,12 @@ func TestSearchRepository(t *testing.T) {
{
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse},
- count: 30,
+ count: 31,
},
{
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse},
- count: 35,
+ count: 36,
},
{
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
@@ -255,7 +255,7 @@ func TestSearchRepository(t *testing.T) {
{
name: "AllPublic/PublicRepositoriesOfOrganization",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse},
- count: 30,
+ count: 31,
},
{
name: "AllTemplates",
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index dc88c422b5..cf04830cbb 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1662,6 +1662,13 @@ pulls.switch_comparison_type = Switch comparison type
pulls.switch_head_and_base = Switch head and base
pulls.filter_branch = Filter branch
pulls.no_results = No results found.
+pulls.show_all_commits = Show all commits
+pulls.show_changes_since_your_last_review = Show changes since your last review
+pulls.showing_only_single_commit = Showing only changes of commit %[1]s
+pulls.showing_specified_commit_range = Showing only changes between %[1]s..%[2]s
+pulls.select_commit_hold_shift_for_range = Select commit. Hold shift + click to select a range
+pulls.review_only_possible_for_full_diff = Review is only possible when viewing the full diff
+pulls.filter_changes_by_commit = Filter by commit
pulls.nothing_to_compare = These branches are equal. There is no need to create a pull request.
pulls.nothing_to_compare_and_allow_empty_pr = These branches are equal. This PR will be empty.
pulls.has_pull_request = `A pull request between these branches already exists: <a href="%[1]s">%[2]s#%[3]d</a>`
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index 237e53413f..a24ef43367 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -694,6 +694,42 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
return compareInfo
}
+type pullCommitList struct {
+ Commits []pull_service.CommitInfo `json:"commits"`
+ LastReviewCommitSha string `json:"last_review_commit_sha"`
+ Locale map[string]string `json:"locale"`
+}
+
+// GetPullCommits get all commits for given pull request
+func GetPullCommits(ctx *context.Context) {
+ issue := checkPullInfo(ctx)
+ if ctx.Written() {
+ return
+ }
+ resp := &pullCommitList{}
+
+ commits, lastReviewCommitSha, err := pull_service.GetPullCommits(ctx, issue)
+ if err != nil {
+ ctx.JSON(http.StatusInternalServerError, err)
+ return
+ }
+
+ // Get the needed locale
+ resp.Locale = map[string]string{
+ "lang": ctx.Locale.Language(),
+ "filter_changes_by_commit": ctx.Tr("repo.pulls.filter_changes_by_commit"),
+ "show_all_commits": ctx.Tr("repo.pulls.show_all_commits"),
+ "stats_num_commits": ctx.TrN(len(commits), "repo.activity.git_stats_commit_1", "repo.activity.git_stats_commit_n", len(commits)),
+ "show_changes_since_your_last_review": ctx.Tr("repo.pulls.show_changes_since_your_last_review"),
+ "select_commit_hold_shift_for_range": ctx.Tr("repo.pulls.select_commit_hold_shift_for_range"),
+ }
+
+ resp.Commits = commits
+ resp.LastReviewCommitSha = lastReviewCommitSha
+
+ ctx.JSON(http.StatusOK, resp)
+}
+
// ViewPullCommits show commits for a pull request
func ViewPullCommits(ctx *context.Context) {
ctx.Data["PageIsPullList"] = true
@@ -739,7 +775,7 @@ func ViewPullCommits(ctx *context.Context) {
}
// ViewPullFiles render pull request changed files list page
-func ViewPullFiles(ctx *context.Context) {
+func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommit string, willShowSpecifiedCommitRange, willShowSpecifiedCommit bool) {
ctx.Data["PageIsPullList"] = true
ctx.Data["PageIsPullFiles"] = true
@@ -762,6 +798,33 @@ func ViewPullFiles(ctx *context.Context) {
prInfo = PrepareViewPullInfo(ctx, issue)
}
+ // Validate the given commit sha to show (if any passed)
+ if willShowSpecifiedCommit || willShowSpecifiedCommitRange {
+
+ foundStartCommit := len(specifiedStartCommit) == 0
+ foundEndCommit := len(specifiedEndCommit) == 0
+
+ if !(foundStartCommit && foundEndCommit) {
+ for _, commit := range prInfo.Commits {
+ if commit.ID.String() == specifiedStartCommit {
+ foundStartCommit = true
+ }
+ if commit.ID.String() == specifiedEndCommit {
+ foundEndCommit = true
+ }
+
+ if foundStartCommit && foundEndCommit {
+ break
+ }
+ }
+ }
+
+ if !(foundStartCommit && foundEndCommit) {
+ ctx.NotFound("Given SHA1 not found for this PR", nil)
+ return
+ }
+ }
+
if ctx.Written() {
return
} else if prInfo == nil {
@@ -775,12 +838,30 @@ func ViewPullFiles(ctx *context.Context) {
return
}
- startCommitID = prInfo.MergeBase
- endCommitID = headCommitID
+ ctx.Data["IsShowingOnlySingleCommit"] = willShowSpecifiedCommit
+
+ if willShowSpecifiedCommit || willShowSpecifiedCommitRange {
+ if len(specifiedEndCommit) > 0 {
+ endCommitID = specifiedEndCommit
+ } else {
+ endCommitID = headCommitID
+ }
+ if len(specifiedStartCommit) > 0 {
+ startCommitID = specifiedStartCommit
+ } else {
+ startCommitID = prInfo.MergeBase
+ }
+ ctx.Data["IsShowingAllCommits"] = false
+ } else {
+ endCommitID = headCommitID
+ startCommitID = prInfo.MergeBase
+ ctx.Data["IsShowingAllCommits"] = true
+ }
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
ctx.Data["AfterCommitID"] = endCommitID
+ ctx.Data["BeforeCommitID"] = startCommitID
fileOnly := ctx.FormBool("file-only")
@@ -789,8 +870,8 @@ func ViewPullFiles(ctx *context.Context) {
if fileOnly && (len(files) == 2 || len(files) == 1) {
maxLines, maxFiles = -1, -1
}
+
diffOptions := &gitdiff.DiffOptions{
- BeforeCommitID: startCommitID,
AfterCommitID: endCommitID,
SkipTo: ctx.FormString("skip-to"),
MaxLines: maxLines,
@@ -799,9 +880,18 @@ func ViewPullFiles(ctx *context.Context) {
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)),
}
+ if !willShowSpecifiedCommit {
+ diffOptions.BeforeCommitID = startCommitID
+ }
+
var methodWithError string
var diff *gitdiff.Diff
- if !ctx.IsSigned {
+
+ // if we're not logged in or only a single commit (or commit range) is shown we
+ // have to load only the diff and not get the viewed information
+ // as the viewed information is designed to be loaded only on latest PR
+ // diff and if you're signed in.
+ if !ctx.IsSigned || willShowSpecifiedCommit || willShowSpecifiedCommitRange {
diff, err = gitdiff.GetDiff(gitRepo, diffOptions, files...)
methodWithError = "GetDiff"
} else {
@@ -908,6 +998,22 @@ func ViewPullFiles(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplPullFiles)
}
+func ViewPullFilesForSingleCommit(ctx *context.Context) {
+ viewPullFiles(ctx, "", ctx.Params("sha"), true, true)
+}
+
+func ViewPullFilesForRange(ctx *context.Context) {
+ viewPullFiles(ctx, ctx.Params("shaFrom"), ctx.Params("shaTo"), true, false)
+}
+
+func ViewPullFilesStartingFromCommit(ctx *context.Context) {
+ viewPullFiles(ctx, "", ctx.Params("sha"), true, false)
+}
+
+func ViewPullFilesForAllCommitsOfPr(ctx *context.Context) {
+ viewPullFiles(ctx, "", "", false, false)
+}
+
// UpdatePullRequest merge PR's baseBranch into headBranch
func UpdatePullRequest(ctx *context.Context) {
issue := checkPullInfo(ctx)
diff --git a/routers/web/user/home_test.go b/routers/web/user/home_test.go
index 3a06a38c24..634a91545e 100644
--- a/routers/web/user/home_test.go
+++ b/routers/web/user/home_test.go
@@ -75,7 +75,7 @@ func TestPulls(t *testing.T) {
Pulls(ctx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
- assert.Len(t, ctx.Data["Issues"], 4)
+ assert.Len(t, ctx.Data["Issues"], 5)
}
func TestMilestones(t *testing.T) {
diff --git a/routers/web/web.go b/routers/web/web.go
index 0b51961445..ffc26dc291 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -1279,14 +1279,20 @@ func registerRoutes(m *web.Route) {
m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewIssue)
m.Get(".diff", repo.DownloadPullDiff)
m.Get(".patch", repo.DownloadPullPatch)
- m.Get("/commits", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits)
+ m.Group("/commits", func() {
+ m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits)
+ m.Get("/list", context.RepoRef(), repo.GetPullCommits)
+ m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit)
+ })
m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest)
m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest)
m.Post("/update", repo.UpdatePullRequest)
m.Post("/set_allow_maintainer_edit", web.Bind(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits)
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
m.Group("/files", func() {
- m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFiles)
+ m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr)
+ m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit)
+ m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange)
m.Group("/reviews", func() {
m.Get("/new_comment", repo.RenderNewCodeCommentForm)
m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment)
diff --git a/services/pull/pull.go b/services/pull/pull.go
index 3c6caec882..8c0b65fd8b 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -10,6 +10,7 @@ import (
"os"
"regexp"
"strings"
+ "time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
@@ -17,7 +18,9 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
+ gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/json"
@@ -856,3 +859,71 @@ func IsHeadEqualWithBranch(ctx context.Context, pr *issues_model.PullRequest, br
}
return baseCommit.HasPreviousCommit(headCommit.ID)
}
+
+type CommitInfo struct {
+ Summary string `json:"summary"`
+ CommitterOrAuthorName string `json:"committer_or_author_name"`
+ ID string `json:"id"`
+ ShortSha string `json:"short_sha"`
+ Time string `json:"time"`
+}
+
+// GetPullCommits returns all commits on given pull request and the last review commit sha
+func GetPullCommits(ctx *gitea_context.Context, issue *issues_model.Issue) ([]CommitInfo, string, error) {
+ pull := issue.PullRequest
+
+ baseGitRepo := ctx.Repo.GitRepo
+
+ if err := pull.LoadBaseRepo(ctx); err != nil {
+ return nil, "", err
+ }
+ baseBranch := pull.BaseBranch
+ if pull.HasMerged {
+ baseBranch = pull.MergeBase
+ }
+ prInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(), baseBranch, pull.GetGitRefName(), true, false)
+ if err != nil {
+ return nil, "", err
+ }
+
+ commits := make([]CommitInfo, 0, len(prInfo.Commits))
+
+ for _, commit := range prInfo.Commits {
+ var committerOrAuthorName string
+ var commitTime time.Time
+ if commit.Committer != nil {
+ committerOrAuthorName = commit.Committer.Name
+ commitTime = commit.Committer.When
+ } else {
+ committerOrAuthorName = commit.Author.Name
+ commitTime = commit.Author.When
+ }
+
+ commits = append(commits, CommitInfo{
+ Summary: commit.Summary(),
+ CommitterOrAuthorName: committerOrAuthorName,
+ ID: commit.ID.String(),
+ ShortSha: base.ShortSha(commit.ID.String()),
+ Time: commitTime.Format(time.RFC3339),
+ })
+ }
+
+ var lastReviewCommitID string
+ if ctx.IsSigned {
+ // get last review of current user and store information in context (if available)
+ lastreview, err := issues_model.FindLatestReviews(ctx, issues_model.FindReviewOptions{
+ IssueID: issue.ID,
+ ReviewerID: ctx.Doer.ID,
+ Type: issues_model.ReviewTypeUnknown,
+ })
+
+ if err != nil && !issues_model.IsErrReviewNotExist(err) {
+ return nil, "", err
+ }
+ if len(lastreview) > 0 {
+ lastReviewCommitID = lastreview[0].CommitID
+ }
+ }
+
+ return commits, lastReviewCommitID, nil
+}
diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl
index 72e6731254..c4dd1f658d 100644
--- a/templates/repo/diff/box.tmpl
+++ b/templates/repo/diff/box.tmpl
@@ -31,12 +31,32 @@
{{end}}
{{template "repo/diff/whitespace_dropdown" .}}
{{template "repo/diff/options_dropdown" .}}
+ {{if .PageIsPullFiles}}
+ <div id="diff-commit-select" data-issuelink="{{$.Issue.Link}}" data-queryparams="?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated={{$.ShowOutdatedComments}}">
+ {{/*
+ the following will be replaced by vue component
+ but this avoids any loading artifacts till the vue component is initialized
+ */}}
+ <div class="ui jump dropdown basic button custom">
+ {{svg "octicon-git-commit"}}
+ </div>
+ </div>
+ {{end}}
{{if and .PageIsPullFiles $.SignedUserID (not .IsArchived)}}
{{template "repo/diff/new_review" .}}
{{end}}
</div>
</div>
{{if not .DiffNotAvailable}}
+ {{if and .IsShowingOnlySingleCommit .PageIsPullFiles}}
+ <div class="ui info message">
+ <div>{{.locale.Tr "repo.pulls.showing_only_single_commit" (ShortSha .BeforeCommitID)}} - <a href="{{$.Issue.Link}}/files?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated={{$.ShowOutdatedComments}}">{{.locale.Tr "repo.pulls.show_all_commits"}}</a></div>
+ </div>
+ {{else if and (not .IsShowingAllCommits) .PageIsPullFiles}}
+ <div class="ui info message">
+ <div>{{.locale.Tr "repo.pulls.showing_specified_commit_range" (ShortSha .BeforeCommitID) (ShortSha .AfterCommitID)}} - <a href="{{$.Issue.Link}}/files?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated={{$.ShowOutdatedComments}}">{{.locale.Tr "repo.pulls.show_all_commits"}}</a></div>
+ </div>
+ {{end}}
<script id="diff-data-script" type="module">
const diffDataFiles = [{{range $i, $file := .Diff.Files}}{Name:"{{$file.Name}}",NameHash:"{{$file.NameHash}}",Type:{{$file.Type}},IsBin:{{$file.IsBin}},Addition:{{$file.Addition}},Deletion:{{$file.Deletion}},IsViewed:{{$file.IsViewed}}},{{end}}];
const diffData = {
@@ -81,7 +101,7 @@
{{$isCsv := (call $.IsCsvFile $file)}}
{{$showFileViewToggle := or $isImage (and (not $file.IsIncomplete) $isCsv)}}
{{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}}
- {{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.IsArchived)}}
+ {{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.IsArchived) $.IsShowingAllCommits}}
<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}} gt-mt-0" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}>
<h4 class="diff-file-header sticky-2nd-row ui top attached normal header gt-df gt-ac gt-sb gt-fw">
<div class="diff-file-name gt-df gt-ac gt-gap-2 gt-fw">
@@ -146,7 +166,7 @@
{{end}}
</div>
</h4>
- <div class="diff-file-body ui attached unstackable table segment" {{if $file.IsViewed}}data-folded="true"{{end}}>
+ <div class="diff-file-body ui attached unstackable table segment" {{if and $file.IsViewed $.IsShowingAllCommits}}data-folded="true"{{end}}>
<div id="diff-source-{{$file.NameHash}}" class="file-body file-code unicode-escaped code-diff{{if $.IsSplitStyle}} code-diff-split{{else}} code-diff-unified{{end}}{{if $showFileViewToggle}} gt-hidden{{end}}">
{{if or $file.IsIncomplete $file.IsBin}}
<div class="diff-file-body binary" style="padding: 5px 10px;">
diff --git a/templates/repo/diff/new_review.tmpl b/templates/repo/diff/new_review.tmpl
index 013fdfe2a8..6a3fa7a823 100644
--- a/templates/repo/diff/new_review.tmpl
+++ b/templates/repo/diff/new_review.tmpl
@@ -1,9 +1,10 @@
<div id="review-box">
- <button class="ui tiny green button gt-pr-2 gt-df js-btn-review">
+ <button class="ui tiny green button gt-pr-2 gt-df js-btn-review {{if not $.IsShowingAllCommits}}disabled{{end}}" {{if not $.IsShowingAllCommits}}data-tooltip-content="{{$.locale.Tr "repo.pulls.review_only_possible_for_full_diff"}}"{{end}}>
{{.locale.Tr "repo.diff.review"}}
<span class="ui small label review-comments-counter" data-pending-comment-number="{{.PendingCodeCommentNumber}}">{{.PendingCodeCommentNumber}}</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</button>
+ {{if $.IsShowingAllCommits}}
<div class="review-box-panel tippy-target">
<div class="ui segment">
<form class="ui form form-fetch-action" action="{{.Link}}/reviews/submit" method="post">
@@ -48,4 +49,5 @@
</form>
</div>
</div>
+ {{end}}
</div>
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/HEAD b/tests/gitea-repositories-meta/user2/commitsonpr.git/HEAD
new file mode 100644
index 0000000000..b870d82622
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/main
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/config b/tests/gitea-repositories-meta/user2/commitsonpr.git/config
new file mode 100644
index 0000000000..07d359d07c
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/config
@@ -0,0 +1,4 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/description b/tests/gitea-repositories-meta/user2/commitsonpr.git/description
new file mode 100644
index 0000000000..498b267a8c
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/info/exclude b/tests/gitea-repositories-meta/user2/commitsonpr.git/info/exclude
new file mode 100644
index 0000000000..a5196d1be8
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/info/refs b/tests/gitea-repositories-meta/user2/commitsonpr.git/info/refs
new file mode 100644
index 0000000000..0a1e147ec5
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/info/refs
@@ -0,0 +1,3 @@
+1978192d98bb1b65e11c2cf37da854fbf94bffd6 refs/heads/branch1
+cbff181af4c9c7fee3cf6c106699e07d9a3f54e6 refs/heads/main
+1978192d98bb1b65e11c2cf37da854fbf94bffd6 refs/pull/1/head
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/logs/HEAD b/tests/gitea-repositories-meta/user2/commitsonpr.git/logs/HEAD
new file mode 100644
index 0000000000..913799abd3
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 cbff181af4c9c7fee3cf6c106699e07d9a3f54e6 Gitea <gitea@fake.local> 1688672318 +0200
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/logs/refs/heads/branch1 b/tests/gitea-repositories-meta/user2/commitsonpr.git/logs/refs/heads/branch1
new file mode 100644
index 0000000000..cf96195665
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/logs/refs/heads/branch1
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 1978192d98bb1b65e11c2cf37da854fbf94bffd6 Gitea <gitea@fake.local> 1688672383 +0200 push
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/logs/refs/heads/main b/tests/gitea-repositories-meta/user2/commitsonpr.git/logs/refs/heads/main
new file mode 100644
index 0000000000..a503f2f53d
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/logs/refs/heads/main
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 cbff181af4c9c7fee3cf6c106699e07d9a3f54e6 root <sauer.sebastian@gmail.com> 1688672317 +0200 push
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/0a/6dda431c72a6a4aac05b98e319972a1a55e01c b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/0a/6dda431c72a6a4aac05b98e319972a1a55e01c
new file mode 100644
index 0000000000..f545a47ea8
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/0a/6dda431c72a6a4aac05b98e319972a1a55e01c
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/0c/396a509b64fd4e2e55649d100b86e8b96cc0e5 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/0c/396a509b64fd4e2e55649d100b86e8b96cc0e5
new file mode 100644
index 0000000000..73b080e2fb
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/0c/396a509b64fd4e2e55649d100b86e8b96cc0e5
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/10/0ef49565829e7bd83057d2dab88f58b00db831 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/10/0ef49565829e7bd83057d2dab88f58b00db831
new file mode 100644
index 0000000000..4e0b7d3d9e
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/10/0ef49565829e7bd83057d2dab88f58b00db831
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/10/9ab1c0b84e088d7edcf018379518b49361f285 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/10/9ab1c0b84e088d7edcf018379518b49361f285
new file mode 100644
index 0000000000..1b9636b8f7
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/10/9ab1c0b84e088d7edcf018379518b49361f285
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/19/78192d98bb1b65e11c2cf37da854fbf94bffd6 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/19/78192d98bb1b65e11c2cf37da854fbf94bffd6
new file mode 100644
index 0000000000..5a3f7f7886
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/19/78192d98bb1b65e11c2cf37da854fbf94bffd6
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/1e/67d753ac1f9097eff26f9d33eb80182344b72c b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/1e/67d753ac1f9097eff26f9d33eb80182344b72c
new file mode 100644
index 0000000000..de870a8818
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/1e/67d753ac1f9097eff26f9d33eb80182344b72c
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/23/576dd018294e476c06e569b6b0f170d0558705 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/23/576dd018294e476c06e569b6b0f170d0558705
new file mode 100644
index 0000000000..be4db7cad4
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/23/576dd018294e476c06e569b6b0f170d0558705
@@ -0,0 +1,2 @@
+xA
+0E]$D'If` OevUC\Q;_򐙅%VHDSڻ7/jPJVT $>ԮzCFo1/pSᵍoy><y@HÐ#E8zv?tmйJN \ No newline at end of file
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/28/16bffda09c0f23775ea4be279de004d28a3803 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/28/16bffda09c0f23775ea4be279de004d28a3803
new file mode 100644
index 0000000000..8cc25ecea6
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/28/16bffda09c0f23775ea4be279de004d28a3803
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/35/f03b5e176ee6d24c86b5cca7009a5b0ba2a026 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/35/f03b5e176ee6d24c86b5cca7009a5b0ba2a026
new file mode 100644
index 0000000000..8983aca822
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/35/f03b5e176ee6d24c86b5cca7009a5b0ba2a026
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/38/cdad2e40c989aabab3f2d0a27faf0f7be617d5 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/38/cdad2e40c989aabab3f2d0a27faf0f7be617d5
new file mode 100644
index 0000000000..c9aa9aed79
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/38/cdad2e40c989aabab3f2d0a27faf0f7be617d5
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/3e/64625bd6eb5bcba69ac97de6c8f507402df861 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/3e/64625bd6eb5bcba69ac97de6c8f507402df861
new file mode 100644
index 0000000000..2efd367aa0
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/3e/64625bd6eb5bcba69ac97de6c8f507402df861
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/4b/860706d3eec5858324d4ba00db0423ca4cbf50 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/4b/860706d3eec5858324d4ba00db0423ca4cbf50
new file mode 100644
index 0000000000..164d71da53
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/4b/860706d3eec5858324d4ba00db0423ca4cbf50
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/4c/a8bcaf27e28504df7bf996819665986b01c847 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/4c/a8bcaf27e28504df7bf996819665986b01c847
new file mode 100644
index 0000000000..e491bd2e70
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/4c/a8bcaf27e28504df7bf996819665986b01c847
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/52/84ca7f5757816e67c098224a8367aa2544222d b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/52/84ca7f5757816e67c098224a8367aa2544222d
new file mode 100644
index 0000000000..aef46ae262
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/52/84ca7f5757816e67c098224a8367aa2544222d
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/53/9a24812705f77484568e6ad7db84764c1903c8 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/53/9a24812705f77484568e6ad7db84764c1903c8
new file mode 100644
index 0000000000..f95fa74ccd
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/53/9a24812705f77484568e6ad7db84764c1903c8
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/70/8605e8984e7fb9be58818e0e6d9f21bcefd63e b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/70/8605e8984e7fb9be58818e0e6d9f21bcefd63e
new file mode 100644
index 0000000000..ef14818922
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/70/8605e8984e7fb9be58818e0e6d9f21bcefd63e
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/74/7ddb3506a4fa04a7747808eb56ae16f9e933dc b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/74/7ddb3506a4fa04a7747808eb56ae16f9e933dc
new file mode 100644
index 0000000000..c4bddac2fd
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/74/7ddb3506a4fa04a7747808eb56ae16f9e933dc
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/7e/d1d42eda9110676d5c3a7721965d6ed1afe83c b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/7e/d1d42eda9110676d5c3a7721965d6ed1afe83c
new file mode 100644
index 0000000000..1d56d687d0
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/7e/d1d42eda9110676d5c3a7721965d6ed1afe83c
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/81/1d46c7e518f4f180afb862c0db5cb8c80529ce b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/81/1d46c7e518f4f180afb862c0db5cb8c80529ce
new file mode 100644
index 0000000000..684a008666
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/81/1d46c7e518f4f180afb862c0db5cb8c80529ce
@@ -0,0 +1,2 @@
+xM
+0F]䧝 x`L` OM\c;R`8OԫbVĐg-*LX)q9>>"9c`${eNuj M-_Su@DL \ No newline at end of file
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/83/2d33e438d2b4a86fba81cb67b32d1d61a828cb b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/83/2d33e438d2b4a86fba81cb67b32d1d61a828cb
new file mode 100644
index 0000000000..33d49b7053
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/83/2d33e438d2b4a86fba81cb67b32d1d61a828cb
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/83/7d5c8125633d7d258f93b998e867eab0145520 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/83/7d5c8125633d7d258f93b998e867eab0145520
new file mode 100644
index 0000000000..29f1136177
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/83/7d5c8125633d7d258f93b998e867eab0145520
@@ -0,0 +1,3 @@
+xA
+0E]L'MR=$hZiՇe+NuSSNe(D^ƾpFEF"y˦
+#A+>8Qreԑ9'#G}Le`C7]Z+ ={Dh;[׌WȵM \ No newline at end of file
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/87/cdc1333f5f117a92f3cef78ebe0301114b3610 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/87/cdc1333f5f117a92f3cef78ebe0301114b3610
new file mode 100644
index 0000000000..6b797fecf9
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/87/cdc1333f5f117a92f3cef78ebe0301114b3610
@@ -0,0 +1,2 @@
+x+)JMU067`040031QrutuMa!E~ӏGYM**I-.1+(axsﭘFwS%gS"#"ˬ)BS
+pؙs)"cKS̬kZxv?"<Kf؇Zu"#)2+2`'O3fEs/Z Yť-+w5N߬+B4"sY*KZn)d>+LѲ%Dx,9]K* "K"Y譻A|ĖɉZvۓG \ No newline at end of file
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/92/70b08497106eaa65fce8aa91f37c4780f76909 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/92/70b08497106eaa65fce8aa91f37c4780f76909
new file mode 100644
index 0000000000..5e40c5dbab
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/92/70b08497106eaa65fce8aa91f37c4780f76909
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/96/cef4a7b72b3c208340ae6f0cf55a93e9077c93 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/96/cef4a7b72b3c208340ae6f0cf55a93e9077c93
new file mode 100644
index 0000000000..155c0c947d
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/96/cef4a7b72b3c208340ae6f0cf55a93e9077c93
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/97/0c5deb117526983f554eaaa1b59102d3e3e0f7 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/97/0c5deb117526983f554eaaa1b59102d3e3e0f7
new file mode 100644
index 0000000000..09bea18a37
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/97/0c5deb117526983f554eaaa1b59102d3e3e0f7
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/c5/626fc9eff57eb1bb7b796b01d4d0f2f3f792a2 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/c5/626fc9eff57eb1bb7b796b01d4d0f2f3f792a2
new file mode 100644
index 0000000000..4792994384
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/c5/626fc9eff57eb1bb7b796b01d4d0f2f3f792a2
@@ -0,0 +1,2 @@
+xA
+0E]4I3=$L`4 Oyu`x*%L=ӈAETF)b-:pZP"\GP0ivHc`$/YvҾOUz1:rpFh{G׌:8EL \ No newline at end of file
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/c7/04db5794097441aa2d9dd834d5b7e2f8f08108 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/c7/04db5794097441aa2d9dd834d5b7e2f8f08108
new file mode 100644
index 0000000000..2dfb6fdb00
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/c7/04db5794097441aa2d9dd834d5b7e2f8f08108
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/cb/ff181af4c9c7fee3cf6c106699e07d9a3f54e6 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/cb/ff181af4c9c7fee3cf6c106699e07d9a3f54e6
new file mode 100644
index 0000000000..47d3fac61f
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/cb/ff181af4c9c7fee3cf6c106699e07d9a3f54e6
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/d1/8e427f4011e74e96a31823c938be26eebab53b b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/d1/8e427f4011e74e96a31823c938be26eebab53b
new file mode 100644
index 0000000000..fe9758a16b
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/d1/8e427f4011e74e96a31823c938be26eebab53b
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/d2/5795e38fbc1b4839697e834b957d61c83d994f b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/d2/5795e38fbc1b4839697e834b957d61c83d994f
new file mode 100644
index 0000000000..76878a9624
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/d2/5795e38fbc1b4839697e834b957d61c83d994f
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/d6/6f456f0813a5841fbc03e5f1c47304dc675695 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/d6/6f456f0813a5841fbc03e5f1c47304dc675695
new file mode 100644
index 0000000000..6e8ce4c501
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/d6/6f456f0813a5841fbc03e5f1c47304dc675695
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/e1/7e0fa20f3d2125916f2fb2f51f19240678cb83 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/e1/7e0fa20f3d2125916f2fb2f51f19240678cb83
new file mode 100644
index 0000000000..fbaa98ded2
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/e1/7e0fa20f3d2125916f2fb2f51f19240678cb83
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/ec/d9fdda5c814055ee619513e1c388ba1bbcb280 b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/ec/d9fdda5c814055ee619513e1c388ba1bbcb280
new file mode 100644
index 0000000000..9d5b06f6ba
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/ec/d9fdda5c814055ee619513e1c388ba1bbcb280
Binary files differ
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/info/packs b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/info/packs
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/objects/info/packs
@@ -0,0 +1 @@
+
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/refs/heads/branch1 b/tests/gitea-repositories-meta/user2/commitsonpr.git/refs/heads/branch1
new file mode 100644
index 0000000000..357fc9d6ed
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/refs/heads/branch1
@@ -0,0 +1 @@
+1978192d98bb1b65e11c2cf37da854fbf94bffd6
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/refs/heads/main b/tests/gitea-repositories-meta/user2/commitsonpr.git/refs/heads/main
new file mode 100644
index 0000000000..596912bb8e
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/refs/heads/main
@@ -0,0 +1 @@
+cbff181af4c9c7fee3cf6c106699e07d9a3f54e6
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/refs/heads/master b/tests/gitea-repositories-meta/user2/commitsonpr.git/refs/heads/master
new file mode 100644
index 0000000000..596912bb8e
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/refs/heads/master
@@ -0,0 +1 @@
+cbff181af4c9c7fee3cf6c106699e07d9a3f54e6
diff --git a/tests/gitea-repositories-meta/user2/commitsonpr.git/refs/pull/1/head b/tests/gitea-repositories-meta/user2/commitsonpr.git/refs/pull/1/head
new file mode 100644
index 0000000000..357fc9d6ed
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/commitsonpr.git/refs/pull/1/head
@@ -0,0 +1 @@
+1978192d98bb1b65e11c2cf37da854fbf94bffd6
diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go
index 8b02342d88..5f4c1e6a47 100644
--- a/tests/integration/api_issue_test.go
+++ b/tests/integration/api_issue_test.go
@@ -219,7 +219,7 @@ func TestAPISearchIssues(t *testing.T) {
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadIssue)
// as this API was used in the frontend, it uses UI page size
- expectedIssueCount := 16 // from the fixtures
+ expectedIssueCount := 17 // from the fixtures
if expectedIssueCount > setting.UI.IssuePagingNum {
expectedIssueCount = setting.UI.IssuePagingNum
}
@@ -243,7 +243,7 @@ func TestAPISearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String())
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.Len(t, apiIssues, 9)
+ assert.Len(t, apiIssues, 10)
query.Del("since")
query.Del("before")
@@ -259,15 +259,15 @@ func TestAPISearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String())
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.EqualValues(t, "18", resp.Header().Get("X-Total-Count"))
- assert.Len(t, apiIssues, 18)
+ assert.EqualValues(t, "19", resp.Header().Get("X-Total-Count"))
+ assert.Len(t, apiIssues, 19)
query.Add("limit", "10")
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.EqualValues(t, "18", resp.Header().Get("X-Total-Count"))
+ assert.EqualValues(t, "19", resp.Header().Get("X-Total-Count"))
assert.Len(t, apiIssues, 10)
query = url.Values{"assigned": {"true"}, "state": {"all"}, "token": {token}}
@@ -296,7 +296,7 @@ func TestAPISearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String())
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.Len(t, apiIssues, 7)
+ assert.Len(t, apiIssues, 8)
query = url.Values{"owner": {"user3"}, "token": {token}} // organization
link.RawQuery = query.Encode()
@@ -317,7 +317,7 @@ func TestAPISearchIssuesWithLabels(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// as this API was used in the frontend, it uses UI page size
- expectedIssueCount := 16 // from the fixtures
+ expectedIssueCount := 17 // from the fixtures
if expectedIssueCount > setting.UI.IssuePagingNum {
expectedIssueCount = setting.UI.IssuePagingNum
}
diff --git a/tests/integration/api_nodeinfo_test.go b/tests/integration/api_nodeinfo_test.go
index 158a866091..f8f50bf073 100644
--- a/tests/integration/api_nodeinfo_test.go
+++ b/tests/integration/api_nodeinfo_test.go
@@ -33,7 +33,7 @@ func TestNodeinfo(t *testing.T) {
assert.True(t, nodeinfo.OpenRegistrations)
assert.Equal(t, "gitea", nodeinfo.Software.Name)
assert.Equal(t, 25, nodeinfo.Usage.Users.Total)
- assert.Equal(t, 18, nodeinfo.Usage.LocalPosts)
+ assert.Equal(t, 19, nodeinfo.Usage.LocalPosts)
assert.Equal(t, 2, nodeinfo.Usage.LocalComments)
})
}
diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go
index fae1415568..3933298f23 100644
--- a/tests/integration/api_repo_test.go
+++ b/tests/integration/api_repo_test.go
@@ -93,9 +93,9 @@ func TestAPISearchRepo(t *testing.T) {
}{
{
name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{
- nil: {count: 32},
- user: {count: 32},
- user2: {count: 32},
+ nil: {count: 33},
+ user: {count: 33},
+ user2: {count: 33},
},
},
{
diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go
index ab2986906b..8fecfa61a4 100644
--- a/tests/integration/issue_test.go
+++ b/tests/integration/issue_test.go
@@ -356,7 +356,7 @@ func TestSearchIssues(t *testing.T) {
session := loginUser(t, "user2")
- expectedIssueCount := 16 // from the fixtures
+ expectedIssueCount := 17 // from the fixtures
if expectedIssueCount > setting.UI.IssuePagingNum {
expectedIssueCount = setting.UI.IssuePagingNum
}
@@ -377,7 +377,7 @@ func TestSearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.Len(t, apiIssues, 9)
+ assert.Len(t, apiIssues, 10)
query.Del("since")
query.Del("before")
@@ -393,15 +393,15 @@ func TestSearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.EqualValues(t, "18", resp.Header().Get("X-Total-Count"))
- assert.Len(t, apiIssues, 18)
+ assert.EqualValues(t, "19", resp.Header().Get("X-Total-Count"))
+ assert.Len(t, apiIssues, 19)
query.Add("limit", "5")
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.EqualValues(t, "18", resp.Header().Get("X-Total-Count"))
+ assert.EqualValues(t, "19", resp.Header().Get("X-Total-Count"))
assert.Len(t, apiIssues, 5)
query = url.Values{"assigned": {"true"}, "state": {"all"}}
@@ -430,7 +430,7 @@ func TestSearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.Len(t, apiIssues, 7)
+ assert.Len(t, apiIssues, 8)
query = url.Values{"owner": {"user3"}} // organization
link.RawQuery = query.Encode()
@@ -450,7 +450,7 @@ func TestSearchIssues(t *testing.T) {
func TestSearchIssuesWithLabels(t *testing.T) {
defer tests.PrepareTestEnv(t)()
- expectedIssueCount := 16 // from the fixtures
+ expectedIssueCount := 17 // from the fixtures
if expectedIssueCount > setting.UI.IssuePagingNum {
expectedIssueCount = setting.UI.IssuePagingNum
}
diff --git a/tests/integration/pull_diff_test.go b/tests/integration/pull_diff_test.go
new file mode 100644
index 0000000000..5411250935
--- /dev/null
+++ b/tests/integration/pull_diff_test.go
@@ -0,0 +1,58 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+ "net/http"
+ "testing"
+
+ "code.gitea.io/gitea/tests"
+
+ "github.com/PuerkitoBio/goquery"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPullDiff_CompletePRDiff(t *testing.T) {
+ doTestPRDiff(t, "/user2/commitsonpr/pulls/1/files", false, []string{"test1.txt", "test10.txt", "test2.txt", "test3.txt", "test4.txt", "test5.txt", "test6.txt", "test7.txt", "test8.txt", "test9.txt"})
+}
+
+func TestPullDiff_SingleCommitPRDiff(t *testing.T) {
+ doTestPRDiff(t, "/user2/commitsonpr/pulls/1/commits/c5626fc9eff57eb1bb7b796b01d4d0f2f3f792a2", true, []string{"test3.txt"})
+}
+
+func TestPullDiff_CommitRangePRDiff(t *testing.T) {
+ doTestPRDiff(t, "/user2/commitsonpr/pulls/1/files/4ca8bcaf27e28504df7bf996819665986b01c847..23576dd018294e476c06e569b6b0f170d0558705", true, []string{"test2.txt", "test3.txt", "test4.txt"})
+}
+
+func TestPullDiff_StartingFromBaseToCommitPRDiff(t *testing.T) {
+ doTestPRDiff(t, "/user2/commitsonpr/pulls/1/files/c5626fc9eff57eb1bb7b796b01d4d0f2f3f792a2", true, []string{"test1.txt", "test2.txt", "test3.txt"})
+}
+
+func doTestPRDiff(t *testing.T, prDiffURL string, reviewBtnDisabled bool, expectedFilenames []string) {
+ defer tests.PrepareTestEnv(t)()
+
+ session := loginUser(t, "user2")
+
+ req := NewRequest(t, "GET", "/user2/commitsonpr/pulls")
+ session.MakeRequest(t, req, http.StatusOK)
+
+ // Get the given PR diff url
+ req = NewRequest(t, "GET", prDiffURL)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ doc := NewHTMLParser(t, resp.Body)
+
+ // Assert all files are visible.
+ fileContents := doc.doc.Find(".file-content")
+ numberOfFiles := fileContents.Length()
+
+ assert.Equal(t, len(expectedFilenames), numberOfFiles)
+
+ fileContents.Each(func(i int, s *goquery.Selection) {
+ filename, _ := s.Attr("data-old-filename")
+ assert.Equal(t, expectedFilenames[i], filename)
+ })
+
+ // Ensure the review button is enabled for full PR reviews
+ assert.Equal(t, reviewBtnDisabled, doc.doc.Find(".js-btn-review").HasClass("disabled"))
+}
diff --git a/web_src/css/base.css b/web_src/css/base.css
index f517b6b2ca..213f3f88f2 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -633,6 +633,11 @@ a.label,
color: var(--color-text-light-2);
}
+.ui.dropdown > .text > .description,
+.ui.dropdown .menu > .item > .description {
+ color: var(--color-text-light-2);
+}
+
.ui.list .list > .item .header,
.ui.list > .item .header {
color: var(--color-text-dark);
diff --git a/web_src/js/components/DiffCommitSelector.vue b/web_src/js/components/DiffCommitSelector.vue
new file mode 100644
index 0000000000..a0fc4b2a91
--- /dev/null
+++ b/web_src/js/components/DiffCommitSelector.vue
@@ -0,0 +1,299 @@
+<template>
+ <div class="ui scrolling dropdown custom">
+ <button
+ class="ui basic button"
+ id="diff-commit-list-expand"
+ @click.stop="toggleMenu()"
+ :data-tooltip-content="locale.filter_changes_by_commit"
+ aria-haspopup="true"
+ tabindex="0"
+ aria-controls="diff-commit-selector-menu"
+ :aria-label="locale.filter_changes_by_commit"
+ aria-activedescendant="diff-commit-list-show-all"
+ >
+ <svg-icon name="octicon-git-commit"/>
+ </button>
+ <div class="menu left transition" id="diff-commit-selector-menu" :class="{visible: menuVisible}" v-show="menuVisible" v-cloak :aria-expanded="menuVisible ? 'true': 'false'">
+ <div class="loading-indicator is-loading" v-if="isLoading"/>
+ <div v-if="!isLoading" class="vertical item gt-df gt-fc gt-gap-2" id="diff-commit-list-show-all" role="menuitem" tabindex="-1" @keydown.enter="showAllChanges()" @click="showAllChanges()">
+ <div class="gt-ellipsis">
+ {{ locale.show_all_commits }}
+ </div>
+ <div class="gt-ellipsis text light-2 gt-mb-0">
+ {{ locale.stats_num_commits }}
+ </div>
+ </div>
+ <!-- only show the show changes since last review if there is a review AND we are commits ahead of the last review -->
+ <div
+ v-if="lastReviewCommitSha != null" role="menuitem" tabindex="-1"
+ class="vertical item gt-df gt-fc gt-gap-2 gt-border-secondary-top"
+ :class="{disabled: commitsSinceLastReview === 0}"
+ @keydown.enter="changesSinceLastReviewClick()"
+ @click="changesSinceLastReviewClick()"
+ >
+ <div class="gt-ellipsis">
+ {{ locale.show_changes_since_your_last_review }}
+ </div>
+ <div class="gt-ellipsis text light-2">
+ {{ commitsSinceLastReview }} commits
+ </div>
+ </div>
+ <span v-if="!isLoading" class="info gt-border-secondary-top text light-2">{{ locale.select_commit_hold_shift_for_range }}</span>
+ <template v-for="commit in commits" :key="commit.id">
+ <div
+ class="vertical item gt-df gt-gap-2 gt-border-secondary-top" role="menuitem" tabindex="-1"
+ :class="{selection: commit.selected, hovered: commit.hovered}"
+ @keydown.enter.exact="commitClicked(commit.id)"
+ @keydown.enter.shift.exact="commitClickedShift(commit)"
+ @mouseover.shift="highlight(commit)"
+ @click.exact="commitClicked(commit.id)"
+ @click.ctrl.exact="commitClicked(commit.id, true)"
+ @click.meta.exact="commitClicked(commit.id, true)"
+ @click.shift.exact.stop.prevent="commitClickedShift(commit)"
+ >
+ <div class="gt-f1 gt-df gt-fc gt-gap-2">
+ <div class="gt-ellipsis commit-list-summary">
+ {{ commit.summary }}
+ </div>
+ <div class="gt-ellipsis text light-2">
+ {{ commit.committer_or_author_name }}
+ <span class="text right">
+ <relative-time class="time-since" prefix="" :datetime="commit.time" data-tooltip-content data-tooltip-interactive="true">{{ commit.time }}</relative-time>
+ </span>
+ </div>
+ </div>
+ <div class="gt-mono">
+ {{ commit.short_sha }}
+ </div>
+ </div>
+ </template>
+ </div>
+ </div>
+</template>
+
+<script>
+import {SvgIcon} from '../svg.js';
+
+export default {
+ components: {SvgIcon},
+ data: () => {
+ return {
+ menuVisible: false,
+ isLoading: false,
+ locale: {},
+ commits: [],
+ hoverActivated: false,
+ lastReviewCommitSha: null
+ };
+ },
+ computed: {
+ commitsSinceLastReview() {
+ if (this.lastReviewCommitSha) {
+ return this.commits.length - this.commits.findIndex((x) => x.id === this.lastReviewCommitSha) - 1;
+ }
+ return 0;
+ },
+ queryParams() {
+ return this.$el.parentNode.getAttribute('data-queryparams');
+ },
+ issueLink() {
+ return this.$el.parentNode.getAttribute('data-issuelink');
+ }
+ },
+ mounted() {
+ document.body.addEventListener('click', this.onBodyClick);
+ this.$el.addEventListener('keydown', this.onKeyDown);
+ this.$el.addEventListener('keyup', this.onKeyUp);
+ },
+ unmounted() {
+ document.body.removeEventListener('click', this.onBodyClick);
+ this.$el.removeEventListener('keydown', this.onKeyDown);
+ this.$el.removeEventListener('keyup', this.onKeyUp);
+ },
+ methods: {
+ onBodyClick(event) {
+ // close this menu on click outside of this element when the dropdown is currently visible opened
+ if (this.$el.contains(event.target)) return;
+ if (this.menuVisible) {
+ this.toggleMenu();
+ }
+ },
+ onKeyDown(event) {
+ if (!this.menuVisible) return;
+ const item = document.activeElement;
+ if (!this.$el.contains(item)) return;
+ switch (event.key) {
+ case 'ArrowDown': // select next element
+ event.preventDefault();
+ this.focusElem(item.nextElementSibling, item);
+ break;
+ case 'ArrowUp': // select previous element
+ event.preventDefault();
+ this.focusElem(item.previousElementSibling, item);
+ break;
+ case 'Escape': // close menu
+ event.preventDefault();
+ item.tabIndex = -1;
+ this.toggleMenu();
+ break;
+ }
+ },
+ onKeyUp(event) {
+ if (!this.menuVisible) return;
+ const item = document.activeElement;
+ if (!this.$el.contains(item)) return;
+ if (event.key === 'Shift' && this.hoverActivated) {
+ // shift is not pressed anymore -> deactivate hovering and reset hovered and selected
+ this.hoverActivated = false;
+ for (const commit of this.commits) {
+ commit.hovered = false;
+ commit.selected = false;
+ }
+ }
+ },
+ highlight(commit) {
+ if (!this.hoverActivated) return;
+ const indexSelected = this.commits.findIndex((x) => x.selected);
+ const indexCurrentElem = this.commits.findIndex((x) => x.id === commit.id);
+ for (const [idx, commit] of this.commits.entries()) {
+ commit.hovered = Math.min(indexSelected, indexCurrentElem) <= idx && idx <= Math.max(indexSelected, indexCurrentElem);
+ }
+ },
+ /** Focus given element */
+ focusElem(elem, prevElem) {
+ if (elem) {
+ elem.tabIndex = 0;
+ prevElem.tabIndex = -1;
+ elem.focus();
+ }
+ },
+ /** Opens our menu, loads commits before opening */
+ async toggleMenu() {
+ this.menuVisible = !this.menuVisible;
+ // load our commits when the menu is not yet visible (it'll be toggled after loading)
+ // and we got no commits
+ if (this.commits.length === 0 && this.menuVisible && !this.isLoading) {
+ this.isLoading = true;
+ try {
+ await this.fetchCommits();
+ } finally {
+ this.isLoading = false;
+ }
+ }
+ // set correct tabindex to allow easier navigation
+ this.$nextTick(() => {
+ const expandBtn = this.$el.querySelector('#diff-commit-list-expand');
+ const showAllChanges = this.$el.querySelector('#diff-commit-list-show-all');
+ if (this.menuVisible) {
+ this.focusElem(showAllChanges, expandBtn);
+ } else {
+ this.focusElem(expandBtn, showAllChanges);
+ }
+ });
+ },
+ /** Load the commits to show in this dropdown */
+ async fetchCommits() {
+ const resp = await fetch(`${this.issueLink}/commits/list`);
+ const results = await resp.json();
+ this.commits.push(...results.commits.map((x) => {
+ x.hovered = false;
+ return x;
+ }));
+ this.commits.reverse();
+ this.lastReviewCommitSha = results.last_review_commit_sha || null;
+ if (this.lastReviewCommitSha && this.commits.findIndex((x) => x.id === this.lastReviewCommitSha) === -1) {
+ // the lastReviewCommit is not available (probably due to a force push)
+ // reset the last review commit sha
+ this.lastReviewCommitSha = null;
+ }
+ Object.assign(this.locale, results.locale);
+ },
+ showAllChanges() {
+ window.location = `${this.issueLink}/files${this.queryParams}`;
+ },
+ /** Called when user clicks on since last review */
+ changesSinceLastReviewClick() {
+ window.location = `${this.issueLink}/files/${this.lastReviewCommitSha}..${this.commits.at(-1).id}${this.queryParams}`;
+ },
+ /** Clicking on a single commit opens this specific commit */
+ commitClicked(commitId, newWindow = false) {
+ const url = `${this.issueLink}/commits/${commitId}${this.queryParams}`;
+ if (newWindow) {
+ window.open(url);
+ } else {
+ window.location = url;
+ }
+ },
+ /**
+ * When a commit is clicked with shift this enables the range
+ * selection. Second click (with shift) defines the end of the
+ * range. This opens the diff of this range
+ * Exception: first commit is the first commit of this PR. Then
+ * the diff from beginning of PR up to the second clicked commit is
+ * opened
+ */
+ commitClickedShift(commit) {
+ this.hoverActivated = !this.hoverActivated;
+ commit.selected = true;
+ // Second click -> determine our range and open links accordingly
+ if (!this.hoverActivated) {
+ // find all selected commits and generate a link
+ if (this.commits[0].selected) {
+ // first commit is selected - generate a short url with only target sha
+ const lastCommitIdx = this.commits.findLastIndex((x) => x.selected);
+ if (lastCommitIdx === this.commits.length - 1) {
+ // user selected all commits - just show the normal diff page
+ window.location = `${this.issueLink}/files${this.queryParams}`;
+ } else {
+ window.location = `${this.issueLink}/files/${this.commits[lastCommitIdx].id}${this.queryParams}`;
+ }
+ } else {
+ const start = this.commits[this.commits.findIndex((x) => x.selected) - 1].id;
+ const end = this.commits.findLast((x) => x.selected).id;
+ window.location = `${this.issueLink}/files/${start}..${end}${this.queryParams}`;
+ }
+ }
+ },
+ }
+};
+</script>
+<style scoped>
+ .hovered:not(.selection) {
+ background-color: var(--color-small-accent) !important;
+ }
+ .selection {
+ background-color: var(--color-accent) !important;
+ }
+
+ .info {
+ display: inline-block;
+ padding: 7px 14px !important;
+ line-height: 1.4;
+ width: 100%;
+ }
+
+ #diff-commit-selector-menu {
+ overflow-x: hidden;
+ max-height: 450px;
+ }
+
+ #diff-commit-selector-menu .loading-indicator {
+ height: 200px;
+ width: 350px;
+ }
+
+ #diff-commit-selector-menu .item {
+ flex-direction: row;
+ line-height: 1.4;
+ padding: 7px 14px !important;
+ }
+
+ #diff-commit-selector-menu .item:focus {
+ color: var(--color-text);
+ background: var(--color-hover);
+ }
+
+ #diff-commit-selector-menu .commit-list-summary {
+ max-width: min(380px, 96vw);
+ }
+</style>
diff --git a/web_src/js/features/repo-diff-commitselect.js b/web_src/js/features/repo-diff-commitselect.js
new file mode 100644
index 0000000000..ebac64e855
--- /dev/null
+++ b/web_src/js/features/repo-diff-commitselect.js
@@ -0,0 +1,10 @@
+import {createApp} from 'vue';
+import DiffCommitSelector from '../components/DiffCommitSelector.vue';
+
+export function initDiffCommitSelect() {
+ const el = document.getElementById('diff-commit-select');
+ if (!el) return;
+
+ const commitSelect = createApp(DiffCommitSelector);
+ commitSelect.mount(el);
+}
diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js
index f4bb724fe5..b79ca0f5b1 100644
--- a/web_src/js/features/repo-diff.js
+++ b/web_src/js/features/repo-diff.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import {initCompReactionSelector} from './comp/ReactionSelector.js';
import {initRepoIssueContentHistory} from './repo-issue-content.js';
import {initDiffFileTree} from './repo-diff-filetree.js';
+import {initDiffCommitSelect} from './repo-diff-commitselect.js';
import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.js';
import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js';
import {initImageDiff} from './imagediff.js';
@@ -188,6 +189,7 @@ export function initRepoDiffView() {
const diffFileList = $('#diff-file-list');
if (diffFileList.length === 0) return;
initDiffFileTree();
+ initDiffCommitSelect();
initRepoDiffShowMore();
initRepoDiffReviewButton();
initRepoDiffFileViewToggle();
diff --git a/web_src/js/svg.js b/web_src/js/svg.js
index 2ef839aa21..46372e7d62 100644
--- a/web_src/js/svg.js
+++ b/web_src/js/svg.js
@@ -29,6 +29,7 @@ import octiconFileDirectoryFill from '../../public/assets/img/svg/octicon-file-d
import octiconFilter from '../../public/assets/img/svg/octicon-filter.svg';
import octiconGear from '../../public/assets/img/svg/octicon-gear.svg';
import octiconGitBranch from '../../public/assets/img/svg/octicon-git-branch.svg';
+import octiconGitCommit from '../../public/assets/img/svg/octicon-git-commit.svg';
import octiconGitMerge from '../../public/assets/img/svg/octicon-git-merge.svg';
import octiconGitPullRequest from '../../public/assets/img/svg/octicon-git-pull-request.svg';
import octiconHeading from '../../public/assets/img/svg/octicon-heading.svg';
@@ -99,6 +100,7 @@ const svgs = {
'octicon-filter': octiconFilter,
'octicon-gear': octiconGear,
'octicon-git-branch': octiconGitBranch,
+ 'octicon-git-commit': octiconGitCommit,
'octicon-git-merge': octiconGitMerge,
'octicon-git-pull-request': octiconGitPullRequest,
'octicon-heading': octiconHeading,