From f766b002938b5c81e343c81fda3c0669fa09809f Mon Sep 17 00:00:00 2001
From: Matthew Walowski <mattwalowski@gmail.com>
Date: Sat, 29 Apr 2023 05:34:14 -0700
Subject: [PATCH] Add ability to specify '--not' from GetAllCommits (#24409)

For my specific use case, I'd like to get all commits that are on one
branch but NOT on the other branch.

For instance, I'd like to get all the commits on `Branch1` that are not
also on `master` (I.e. all commits that were made after `Branch1` was
created).

This PR adds a `not` query param that gets passed down to the `git log`
command to allow the user to exclude items from `GetAllCommits`.

See [git
documentation](https://git-scm.com/docs/git-log#Documentation/git-log.txt---not)

---------

Co-authored-by: Giteabot <teabot@gitea.io>
---
 modules/git/commit.go          |  4 ++--
 modules/git/repo_commit.go     | 18 +++++++++++++-----
 routers/api/v1/repo/commits.go |  7 ++++++-
 routers/web/feed/branch.go     |  2 +-
 routers/web/repo/commit.go     |  2 +-
 templates/swagger/v1_json.tmpl |  6 ++++++
 6 files changed, 29 insertions(+), 10 deletions(-)

diff --git a/modules/git/commit.go b/modules/git/commit.go
index 610d27c68ac..f28c315cb57 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -187,8 +187,8 @@ func (c *Commit) CommitsCount() (int64, error) {
 }
 
 // CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
-func (c *Commit) CommitsByRange(page, pageSize int) ([]*Commit, error) {
-	return c.repo.commitsByRange(c.ID, page, pageSize)
+func (c *Commit) CommitsByRange(page, pageSize int, not string) ([]*Commit, error) {
+	return c.repo.commitsByRange(c.ID, page, pageSize, not)
 }
 
 // CommitsBefore returns all the commits before current revision
diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go
index 153a116b061..30a82eb2970 100644
--- a/modules/git/repo_commit.go
+++ b/modules/git/repo_commit.go
@@ -90,14 +90,22 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
 	return commits[0], nil
 }
 
-func (repo *Repository) commitsByRange(id SHA1, page, pageSize int) ([]*Commit, error) {
-	stdout, _, err := NewCommand(repo.Ctx, "log").
-		AddOptionFormat("--skip=%d", (page-1)*pageSize).AddOptionFormat("--max-count=%d", pageSize).AddArguments(prettyLogFormat).
-		AddDynamicArguments(id.String()).
-		RunStdBytes(&RunOpts{Dir: repo.Path})
+func (repo *Repository) commitsByRange(id SHA1, page, pageSize int, not string) ([]*Commit, error) {
+	cmd := NewCommand(repo.Ctx, "log").
+		AddOptionFormat("--skip=%d", (page-1)*pageSize).
+		AddOptionFormat("--max-count=%d", pageSize).
+		AddArguments(prettyLogFormat).
+		AddDynamicArguments(id.String())
+
+	if not != "" {
+		cmd.AddOptionValues("--not", not)
+	}
+
+	stdout, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
 	if err != nil {
 		return nil, err
 	}
+
 	return repo.parsePrettyFormatLogToList(stdout)
 }
 
diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go
index 22b013e7dc3..401403c83de 100644
--- a/routers/api/v1/repo/commits.go
+++ b/routers/api/v1/repo/commits.go
@@ -115,6 +115,10 @@ func GetAllCommits(ctx *context.APIContext) {
 	//   in: query
 	//   description: page size of results (ignored if used with 'path')
 	//   type: integer
+	// - name: not
+	//   in: query
+	//   description: commits that match the given specifier will not be listed.
+	//   type: string
 	// responses:
 	//   "200":
 	//     "$ref": "#/responses/CommitList"
@@ -181,7 +185,8 @@ func GetAllCommits(ctx *context.APIContext) {
 		}
 
 		// Query commits
-		commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize)
+		not := ctx.FormString("not")
+		commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not)
 		if err != nil {
 			ctx.Error(http.StatusInternalServerError, "CommitsByRange", err)
 			return
diff --git a/routers/web/feed/branch.go b/routers/web/feed/branch.go
index 22b6e2f14b0..f13038ff9b4 100644
--- a/routers/web/feed/branch.go
+++ b/routers/web/feed/branch.go
@@ -16,7 +16,7 @@ import (
 
 // ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed
 func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) {
-	commits, err := ctx.Repo.Commit.CommitsByRange(0, 10)
+	commits, err := ctx.Repo.Commit.CommitsByRange(0, 10, "")
 	if err != nil {
 		ctx.ServerError("ShowBranchFeed", err)
 		return
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index 7439c2411b9..93294f8dddb 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -70,7 +70,7 @@ func Commits(ctx *context.Context) {
 	}
 
 	// Both `git log branchName` and `git log commitId` work.
-	commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize)
+	commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "")
 	if err != nil {
 		ctx.ServerError("CommitsByRange", err)
 		return
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 2db950b57a7..51d123866de 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -3803,6 +3803,12 @@
             "description": "page size of results (ignored if used with 'path')",
             "name": "limit",
             "in": "query"
+          },
+          {
+            "type": "string",
+            "description": "commits that match the given specifier will not be listed.",
+            "name": "not",
+            "in": "query"
           }
         ],
         "responses": {