From 5c3dff26d66cadfa7d513eff42d33a783948eff2 Mon Sep 17 00:00:00 2001
From: Gusted <postmaster@gusted.xyz>
Date: Wed, 3 Apr 2024 02:41:57 +0200
Subject: [PATCH] [FEAT] Allow non-explicit push options

- Currently the parsing of the push options require that `=` is present
in the value, however we shouldn't be that strict and assume if that's
not set the value is `true`.
- This allow for more natural commands, so become `-o force-push=true`
simply `-o force-push`.
- Add unit test.

(cherry picked from commit f5ad6d4be58e7499c44f1ec93503920e2828d4e6)
---
 cmd/hook.go                   | 14 ++++++++------
 cmd/hook_test.go              | 15 +++++++++++++++
 tests/integration/git_test.go |  2 +-
 3 files changed, 24 insertions(+), 7 deletions(-)

diff --git a/cmd/hook.go b/cmd/hook.go
index 27efd8538f..a29d15628e 100644
--- a/cmd/hook.go
+++ b/cmd/hook.go
@@ -487,10 +487,11 @@ func pushOptions() map[string]string {
 	if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil {
 		for idx := 0; idx < pushCount; idx++ {
 			opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx))
-			kv := strings.SplitN(opt, "=", 2)
-			if len(kv) == 2 {
-				opts[kv[0]] = kv[1]
+			key, value, found := strings.Cut(opt, "=")
+			if !found {
+				value = "true"
 			}
+			opts[key] = value
 		}
 	}
 	return opts
@@ -630,10 +631,11 @@ Forgejo or set your environment appropriately.`, "")
 				break
 			}
 
-			kv := strings.SplitN(string(rs.Data), "=", 2)
-			if len(kv) == 2 {
-				hookOptions.GitPushOptions[kv[0]] = kv[1]
+			key, value, found := strings.Cut(string(rs.Data), "=")
+			if !found {
+				value = "true"
 			}
+			hookOptions.GitPushOptions[key] = value
 		}
 	}
 
diff --git a/cmd/hook_test.go b/cmd/hook_test.go
index 0c1bee29f4..89dafeaa57 100644
--- a/cmd/hook_test.go
+++ b/cmd/hook_test.go
@@ -15,6 +15,7 @@ import (
 	"testing"
 	"time"
 
+	"code.gitea.io/gitea/modules/private"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/test"
 
@@ -162,3 +163,17 @@ func TestDelayWriter(t *testing.T) {
 		require.Empty(t, out)
 	})
 }
+
+func TestPushOptions(t *testing.T) {
+	require.NoError(t, os.Setenv(private.GitPushOptionCount, "3"))
+	require.NoError(t, os.Setenv("GIT_PUSH_OPTION_0", "force-push"))
+	require.NoError(t, os.Setenv("GIT_PUSH_OPTION_1", "option=value"))
+	require.NoError(t, os.Setenv("GIT_PUSH_OPTION_2", "option-double=another=value"))
+	require.NoError(t, os.Setenv("GIT_PUSH_OPTION_3", "not=valid"))
+
+	assert.Equal(t, map[string]string{
+		"force-push":    "true",
+		"option":        "value",
+		"option-double": "another=value",
+	}, pushOptions())
+}
diff --git a/tests/integration/git_test.go b/tests/integration/git_test.go
index 782390002c..708f9a820d 100644
--- a/tests/integration/git_test.go
+++ b/tests/integration/git_test.go
@@ -1025,7 +1025,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headB
 			t.Run("Succeeds", func(t *testing.T) {
 				defer tests.PrintCurrentTest(t)()
 
-				_, _, gitErr := git.NewCommand(git.DefaultContext, "push", "origin", "-o", "force-push=true").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-force-push").RunStdString(&git.RunOpts{Dir: dstPath})
+				_, _, gitErr := git.NewCommand(git.DefaultContext, "push", "origin", "-o", "force-push").AddDynamicArguments("HEAD:refs/for/master/" + headBranch + "-force-push").RunStdString(&git.RunOpts{Dir: dstPath})
 				assert.NoError(t, gitErr)
 
 				currentHeadCommitID, err := upstreamGitRepo.GetRefCommitID(pr.GetGitRefName())