From f4d86b4ab0fac0444df1a084fb69b4d0ea95ae73 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rados=C5=82aw=20Piliszek?= <radek@piliszek.it>
Date: Mon, 12 Aug 2024 14:05:01 +0200
Subject: [PATCH] git-grep: fix for initial dashes in expressions

There is no reason to reject initial dashes in git-grep
expressions... other than the code not supporting it previously.
A new method is introduced to relax the security checks.
---
 modules/git/command.go      | 12 ++++++++++++
 modules/git/command_test.go |  7 +++++++
 modules/git/grep.go         |  2 +-
 modules/git/grep_test.go    | 25 +++++++++++++++++++++++++
 4 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/modules/git/command.go b/modules/git/command.go
index 22cb275ab2..a3d43aaec6 100644
--- a/modules/git/command.go
+++ b/modules/git/command.go
@@ -153,6 +153,18 @@ func (c *Command) AddOptionValues(opt internal.CmdArg, args ...string) *Command
 	return c
 }
 
+// AddGitGrepExpression adds an expression option (-e) to git-grep command
+// It is different from AddOptionValues in that it allows the actual expression
+// to not be filtered out for leading dashes (which is otherwise a security feature
+// of AddOptionValues).
+func (c *Command) AddGitGrepExpression(exp string) *Command {
+	if c.args[len(globalCommandArgs)] != "grep" {
+		panic("function called on a non-grep git program: " + c.args[0])
+	}
+	c.args = append(c.args, "-e", exp)
+	return c
+}
+
 // AddOptionFormat adds a new option with a format string and arguments
 // For example: AddOptionFormat("--opt=%s %s", val1, val2) means 1 argument: {"--opt=val1 val2"}.
 func (c *Command) AddOptionFormat(opt string, args ...any) *Command {
diff --git a/modules/git/command_test.go b/modules/git/command_test.go
index 1d8d3bc12b..d3b8338d02 100644
--- a/modules/git/command_test.go
+++ b/modules/git/command_test.go
@@ -61,3 +61,10 @@ func TestCommandString(t *testing.T) {
 	cmd = NewCommandContextNoGlobals(context.Background(), "url: https://a:b@c/")
 	assert.EqualValues(t, cmd.prog+` "url: https://sanitized-credential@c/"`, cmd.toString(true))
 }
+
+func TestGrepOnlyFunction(t *testing.T) {
+	cmd := NewCommand(context.Background(), "anything-but-grep")
+	assert.Panics(t, func() {
+		cmd.AddGitGrepExpression("whatever")
+	})
+}
diff --git a/modules/git/grep.go b/modules/git/grep.go
index 6449b465b9..30969619ae 100644
--- a/modules/git/grep.go
+++ b/modules/git/grep.go
@@ -76,7 +76,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
 		words = strings.Fields(search)
 	}
 	for _, word := range words {
-		cmd.AddOptionValues("-e", strings.TrimLeft(word, "-"))
+		cmd.AddGitGrepExpression(word)
 	}
 
 	// pathspec
diff --git a/modules/git/grep_test.go b/modules/git/grep_test.go
index 5d0d343ac4..3ba7a6efcb 100644
--- a/modules/git/grep_test.go
+++ b/modules/git/grep_test.go
@@ -98,6 +98,31 @@ func TestGrepSearch(t *testing.T) {
 	assert.Empty(t, res)
 }
 
+func TestGrepDashesAreFine(t *testing.T) {
+	tmpDir := t.TempDir()
+
+	err := InitRepository(DefaultContext, tmpDir, false, Sha1ObjectFormat.Name())
+	require.NoError(t, err)
+
+	gitRepo, err := openRepositoryWithDefaultContext(tmpDir)
+	require.NoError(t, err)
+	defer gitRepo.Close()
+
+	require.NoError(t, os.WriteFile(path.Join(tmpDir, "with-dashes"), []byte("--"), 0o666))
+	require.NoError(t, os.WriteFile(path.Join(tmpDir, "without-dashes"), []byte(".."), 0o666))
+
+	err = AddChanges(tmpDir, true)
+	require.NoError(t, err)
+
+	err = CommitChanges(tmpDir, CommitChangesOptions{Message: "Dashes are cool sometimes"})
+	require.NoError(t, err)
+
+	res, err := GrepSearch(context.Background(), gitRepo, "--", GrepOptions{})
+	require.NoError(t, err)
+	assert.Len(t, res, 1)
+	assert.Equal(t, "with-dashes", res[0].Filename)
+}
+
 func TestGrepNoBinary(t *testing.T) {
 	tmpDir := t.TempDir()