diff --git a/git_test.go b/git_test.go
index f6fcb365..13adc294 100644
--- a/git_test.go
+++ b/git_test.go
@@ -22,10 +22,9 @@ const repoPath = "testdata/testrepo.git"
 var testrepo *Repository
 
 func TestMain(m *testing.M) {
-	verbose := flag.Bool("verbose", false, "")
 	flag.Parse()
 
-	if *verbose {
+	if testing.Verbose() {
 		SetOutput(os.Stdout)
 	}
 
diff --git a/repo_diff.go b/repo_diff.go
index a7550b93..430bf94a 100644
--- a/repo_diff.go
+++ b/repo_diff.go
@@ -126,7 +126,7 @@ func (r *Repository) RawDiff(rev string, diffType RawDiffFormat, w io.Writer, op
 		if commit.ParentsCount() == 0 {
 			cmd = cmd.AddArgs("format-patch").
 				AddOptions(opt.CommandOptions).
-				AddArgs("--full-index", "--no-signature", "--stdout", "--root", rev)
+				AddArgs("--full-index", "--no-signoff", "--no-signature", "--stdout", "--root", rev)
 		} else {
 			c, err := commit.Parent(0)
 			if err != nil {
@@ -134,7 +134,7 @@ func (r *Repository) RawDiff(rev string, diffType RawDiffFormat, w io.Writer, op
 			}
 			cmd = cmd.AddArgs("format-patch").
 				AddOptions(opt.CommandOptions).
-				AddArgs("--full-index", "--no-signature", "--stdout", rev+"..."+c.ID.String())
+				AddArgs("--full-index", "--no-signoff", "--no-signature", "--stdout", rev+"..."+c.ID.String())
 		}
 	default:
 		return fmt.Errorf("invalid diffType: %s", diffType)
diff --git a/repo_grep.go b/repo_grep.go
new file mode 100644
index 00000000..e0919149
--- /dev/null
+++ b/repo_grep.go
@@ -0,0 +1,120 @@
+// Copyright 2022 The Gogs 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 git
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// GrepOptions contains optional arguments for grep search over repository files.
+//
+// Docs: https://git-scm.com/docs/git-grep
+type GrepOptions struct {
+	// The tree to run the search. Defaults to "HEAD".
+	Tree string
+	// Limits the search to files in the specified pathspec.
+	Pathspec string
+	// Whether to do case insensitive search.
+	IgnoreCase bool
+	// Whether to match the pattern only at word boundaries.
+	WordRegexp bool
+	// Whether use extended regular expressions.
+	ExtendedRegexp bool
+	// The timeout duration before giving up for each shell command execution. The
+	// default timeout duration will be used when not supplied.
+	Timeout time.Duration
+	// The additional options to be passed to the underlying git.
+	CommandOptions
+}
+
+// GrepResult represents a single result from a grep search.
+type GrepResult struct {
+	// The tree of the file that matched, e.g. "HEAD".
+	Tree string
+	// The path of the file that matched.
+	Path string
+	// The line number of the match.
+	Line int
+	// The 1-indexed column number of the match.
+	Column int
+	// The text of the line that matched.
+	Text string
+}
+
+func parseGrepLine(line string) (*GrepResult, error) {
+	r := &GrepResult{}
+	sp := strings.SplitN(line, ":", 5)
+	var n int
+	switch len(sp) {
+	case 4:
+		// HEAD
+		r.Tree = "HEAD"
+	case 5:
+		// Tree included
+		r.Tree = sp[0]
+		n++
+	default:
+		return nil, fmt.Errorf("invalid grep line: %s", line)
+	}
+	r.Path = sp[n]
+	n++
+	r.Line, _ = strconv.Atoi(sp[n])
+	n++
+	r.Column, _ = strconv.Atoi(sp[n])
+	n++
+	r.Text = sp[n]
+	return r, nil
+}
+
+// Grep returns the results of a grep search in the repository.
+func (r *Repository) Grep(pattern string, opts ...GrepOptions) []*GrepResult {
+	var opt GrepOptions
+	if len(opts) > 0 {
+		opt = opts[0]
+	}
+	if opt.Tree == "" {
+		opt.Tree = "HEAD"
+	}
+
+	cmd := NewCommand("grep").
+		AddOptions(opt.CommandOptions).
+		// Display full-name, line number and column number
+		AddArgs("--full-name", "--line-number", "--column")
+	if opt.IgnoreCase {
+		cmd.AddArgs("--ignore-case")
+	}
+	if opt.WordRegexp {
+		cmd.AddArgs("--word-regexp")
+	}
+	if opt.ExtendedRegexp {
+		cmd.AddArgs("--extended-regexp")
+	}
+	cmd.AddArgs(pattern, opt.Tree)
+	if opt.Pathspec != "" {
+		cmd.AddArgs("--", opt.Pathspec)
+	}
+
+	stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path)
+	if err != nil {
+		return nil
+	}
+
+	var results []*GrepResult
+	// Normalize line endings
+	lines := strings.Split(strings.ReplaceAll(string(stdout), "\r", ""), "\n")
+	for _, line := range lines {
+		if len(line) == 0 {
+			continue
+		}
+		r, err := parseGrepLine(line)
+		if err == nil {
+			results = append(results, r)
+		}
+	}
+	return results
+}
diff --git a/repo_grep_test.go b/repo_grep_test.go
new file mode 100644
index 00000000..2c0be390
--- /dev/null
+++ b/repo_grep_test.go
@@ -0,0 +1,144 @@
+// Copyright 2022 The Gogs 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 git
+
+import (
+	"runtime"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestRepository_Grep_Simple(t *testing.T) {
+	want := []*GrepResult{
+		{
+			Tree:   "HEAD",
+			Path:   "src/Main.groovy",
+			Line:   7,
+			Column: 5,
+			Text:   "int programmingPoints = 10",
+		}, {
+			Tree:   "HEAD",
+			Path:   "src/Main.groovy",
+			Line:   10,
+			Column: 33,
+			Text:   `println "${name} has at least ${programmingPoints} programming points."`,
+		}, {
+			Tree:   "HEAD",
+			Path:   "src/Main.groovy",
+			Line:   11,
+			Column: 12,
+			Text:   `println "${programmingPoints} squared is ${square(programmingPoints)}"`,
+		}, {
+			Tree:   "HEAD",
+			Path:   "src/Main.groovy",
+			Line:   12,
+			Column: 12,
+			Text:   `println "${programmingPoints} divided by 2 bonus points is ${divide(programmingPoints, 2)}"`,
+		}, {
+			Tree:   "HEAD",
+			Path:   "src/Main.groovy",
+			Line:   13,
+			Column: 12,
+			Text:   `println "${programmingPoints} minus 7 bonus points is ${subtract(programmingPoints, 7)}"`,
+		}, {
+			Tree:   "HEAD",
+			Path:   "src/Main.groovy",
+			Line:   14,
+			Column: 12,
+			Text:   `println "${programmingPoints} plus 3 bonus points is ${sum(programmingPoints, 3)}"`,
+		},
+	}
+	got := testrepo.Grep("programmingPoints")
+	assert.Equal(t, want, got)
+}
+
+func TestRepository_Grep_IgnoreCase(t *testing.T) {
+	want := []*GrepResult{
+		{
+			Tree:   "HEAD",
+			Path:   "README.txt",
+			Line:   9,
+			Column: 36,
+			Text:   "* git@github.com:matthewmccullough/hellogitworld.git",
+		}, {
+			Tree:   "HEAD",
+			Path:   "README.txt",
+			Line:   10,
+			Column: 38,
+			Text:   "* git://github.com/matthewmccullough/hellogitworld.git",
+		}, {
+			Tree:   "HEAD",
+			Path:   "README.txt",
+			Line:   11,
+			Column: 58,
+			Text:   "* https://matthewmccullough@github.com/matthewmccullough/hellogitworld.git",
+		}, {
+			Tree:   "HEAD",
+			Path:   "src/Main.groovy",
+			Line:   9,
+			Column: 10,
+			Text:   `println "Hello ${name}"`,
+		}, {
+			Tree:   "HEAD",
+			Path:   "src/main/java/com/github/App.java",
+			Line:   4,
+			Column: 4,
+			Text:   " * Hello again",
+		}, {
+			Tree:   "HEAD",
+			Path:   "src/main/java/com/github/App.java",
+			Line:   5,
+			Column: 4,
+			Text:   " * Hello world!",
+		}, {
+			Tree:   "HEAD",
+			Path:   "src/main/java/com/github/App.java",
+			Line:   6,
+			Column: 4,
+			Text:   " * Hello",
+		}, {
+			Tree:   "HEAD",
+			Path:   "src/main/java/com/github/App.java",
+			Line:   13,
+			Column: 30,
+			Text:   `        System.out.println( "Hello World!" );`,
+		},
+	}
+	got := testrepo.Grep("Hello", GrepOptions{IgnoreCase: true})
+	assert.Equal(t, want, got)
+}
+
+func TestRepository_Grep_ExtendedRegexp(t *testing.T) {
+	if runtime.GOOS == "darwin" {
+		t.Skip("Skipping testing on macOS")
+		return
+	}
+	want := []*GrepResult{
+		{
+			Tree:   "HEAD",
+			Path:   "src/main/java/com/github/App.java",
+			Line:   13,
+			Column: 30,
+			Text:   `        System.out.println( "Hello World!" );`,
+		},
+	}
+	got := testrepo.Grep(`Hello\sW\w+`, GrepOptions{ExtendedRegexp: true})
+	assert.Equal(t, want, got)
+}
+
+func TestRepository_Grep_WordRegexp(t *testing.T) {
+	want := []*GrepResult{
+		{
+			Tree:   "HEAD",
+			Path:   "src/main/java/com/github/App.java",
+			Line:   5,
+			Column: 10,
+			Text:   ` * Hello world!`,
+		},
+	}
+	got := testrepo.Grep("world", GrepOptions{WordRegexp: true})
+	assert.Equal(t, want, got)
+}