From 104103055a2e100d86620ede579abe6af9d5ef04 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 15 Nov 2022 15:25:09 -0500 Subject: [PATCH 1/5] fix(diff): don't include signoffs when formatting patch Diff tests keep failing bacause I have format.signOff set to true in my gitconfig --- repo_diff.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) From 4b386ea323337a5bcf6fb309b481d99a433081c7 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 15 Nov 2022 16:14:52 -0500 Subject: [PATCH 2/5] feat(grep): search a repository Use `git grep` to search a repository. --- repo_grep.go | 114 ++++++++++++++++++++++++++++++++++++++++++++++ repo_grep_test.go | 99 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 repo_grep.go create mode 100644 repo_grep_test.go diff --git a/repo_grep.go b/repo_grep.go new file mode 100644 index 00000000..1777740f --- /dev/null +++ b/repo_grep.go @@ -0,0 +1,114 @@ +package git + +import ( + "fmt" + "strconv" + "strings" + "time" +) + +type GrepOptions struct { + // TreeID to search in + TreeID string + // Limits the search to files in the specified pathspec + Pathspec string + // Case insensitive search. + IgnoreCase bool + // Match the pattern only at word boundaries. + WordMatch bool + // Whether or not to use extended regular expressions. + ExtendedRegex 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 TreeID of the file that matched. Could be `HEAD` or a tree ID. + TreeID 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 tree ID + r.TreeID = "HEAD" + case 5: + // Tree ID included + r.TreeID = 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, error) { + var opt GrepOptions + if len(opts) > 0 { + opt = opts[0] + } + if opt.TreeID == "" { + opt.TreeID = "HEAD" + } + + cmd := NewCommand("grep"). + AddOptions(opt.CommandOptions). + // Result full-name, line number & column number + AddArgs("--full-name", "--line-number", "--column") + if opt.IgnoreCase { + cmd.AddArgs("-i") + } + if opt.WordMatch { + cmd.AddArgs("-w") + } + if opt.ExtendedRegex { + cmd.AddArgs("-E") + } + cmd.AddArgs(pattern, opt.TreeID) + if opt.Pathspec != "" { + cmd.AddArgs("--", opt.Pathspec) + } + + results := make([]*GrepResult, 0) + stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) + if err == nil && len(stdout) > 0 { + // 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, nil +} diff --git a/repo_grep_test.go b/repo_grep_test.go new file mode 100644 index 00000000..8b74232a --- /dev/null +++ b/repo_grep_test.go @@ -0,0 +1,99 @@ +package git + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRepoGrepSimple(t *testing.T) { + pattern := "programmingPoints" + expect := []GrepResult{ + { + TreeID: "HEAD", Path: "src/Main.groovy", Line: 7, Column: 5, Text: "int programmingPoints = 10", + }, + { + TreeID: "HEAD", Path: "src/Main.groovy", Line: 10, Column: 33, Text: `println "${name} has at least ${programmingPoints} programming points."`, + }, + { + TreeID: "HEAD", Path: "src/Main.groovy", Line: 11, Column: 12, Text: `println "${programmingPoints} squared is ${square(programmingPoints)}"`, + }, + { + TreeID: "HEAD", Path: "src/Main.groovy", Line: 12, Column: 12, Text: `println "${programmingPoints} divided by 2 bonus points is ${divide(programmingPoints, 2)}"`, + }, + { + TreeID: "HEAD", Path: "src/Main.groovy", Line: 13, Column: 12, Text: `println "${programmingPoints} minus 7 bonus points is ${subtract(programmingPoints, 7)}"`, + }, + { + TreeID: "HEAD", Path: "src/Main.groovy", Line: 14, Column: 12, Text: `println "${programmingPoints} plus 3 bonus points is ${sum(programmingPoints, 3)}"`, + }, + } + results, err := testrepo.Grep(pattern) + assert.NoError(t, err) + for i, result := range results { + assert.Equal(t, expect[i], *result) + } +} + +func TestRepoGrepIgnoreCase(t *testing.T) { + pattern := "Hello" + expect := []GrepResult{ + { + TreeID: "HEAD", Path: "README.txt", Line: 9, Column: 36, Text: "* git@github.com:matthewmccullough/hellogitworld.git", + }, + { + TreeID: "HEAD", Path: "README.txt", Line: 10, Column: 38, Text: "* git://github.com/matthewmccullough/hellogitworld.git", + }, + { + TreeID: "HEAD", Path: "README.txt", Line: 11, Column: 58, Text: "* https://matthewmccullough@github.com/matthewmccullough/hellogitworld.git", + }, + { + TreeID: "HEAD", Path: "src/Main.groovy", Line: 9, Column: 10, Text: `println "Hello ${name}"`, + }, + { + TreeID: "HEAD", Path: "src/main/java/com/github/App.java", Line: 4, Column: 4, Text: " * Hello again", + }, + { + TreeID: "HEAD", Path: "src/main/java/com/github/App.java", Line: 5, Column: 4, Text: " * Hello world!", + }, + { + TreeID: "HEAD", Path: "src/main/java/com/github/App.java", Line: 6, Column: 4, Text: " * Hello", + }, + { + TreeID: "HEAD", Path: "src/main/java/com/github/App.java", Line: 13, Column: 30, Text: ` System.out.println( "Hello World!" );`, + }, + } + results, err := testrepo.Grep(pattern, GrepOptions{IgnoreCase: true}) + assert.NoError(t, err) + for i, result := range results { + assert.Equal(t, expect[i], *result) + } +} + +func TestRepoGrepRegex(t *testing.T) { + pattern := "Hello\\sW\\w+" + expect := []GrepResult{ + { + TreeID: "HEAD", Path: "src/main/java/com/github/App.java", Line: 13, Column: 30, Text: ` System.out.println( "Hello World!" );`, + }, + } + results, err := testrepo.Grep(pattern, GrepOptions{ExtendedRegex: true}) + assert.NoError(t, err) + for i, result := range results { + assert.Equal(t, expect[i], *result) + } +} + +func TestRepoGrepWord(t *testing.T) { + pattern := "Hello\\sW\\w+" + expect := []GrepResult{ + { + TreeID: "HEAD", Path: "src/main/java/com/github/App.java", Line: 13, Column: 36, Text: ` System.out.println( "Hello World!" );`, + }, + } + results, err := testrepo.Grep(pattern, GrepOptions{WordMatch: true}) + assert.NoError(t, err) + for i, result := range results { + assert.Equal(t, expect[i], *result) + } +} From 7642a54831a8039a9743377c3017a77b6452111a Mon Sep 17 00:00:00 2001 From: Joe Chen Date: Sat, 11 Feb 2023 21:43:55 +0800 Subject: [PATCH 3/5] Tidy up code --- git_test.go | 3 +- repo_grep.go | 86 +++++++++++---------- repo_grep_test.go | 188 ++++++++++++++++++++++++++++------------------ 3 files changed, 161 insertions(+), 116 deletions(-) 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_grep.go b/repo_grep.go index 1777740f..e0919149 100644 --- a/repo_grep.go +++ b/repo_grep.go @@ -1,3 +1,7 @@ +// 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 ( @@ -7,17 +11,20 @@ import ( "time" ) +// GrepOptions contains optional arguments for grep search over repository files. +// +// Docs: https://git-scm.com/docs/git-grep type GrepOptions struct { - // TreeID to search in - TreeID string - // Limits the search to files in the specified pathspec + // The tree to run the search. Defaults to "HEAD". + Tree string + // Limits the search to files in the specified pathspec. Pathspec string - // Case insensitive search. + // Whether to do case insensitive search. IgnoreCase bool - // Match the pattern only at word boundaries. - WordMatch bool - // Whether or not to use extended regular expressions. - ExtendedRegex 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 @@ -27,8 +34,8 @@ type GrepOptions struct { // GrepResult represents a single result from a grep search. type GrepResult struct { - // The TreeID of the file that matched. Could be `HEAD` or a tree ID. - TreeID string + // 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. @@ -42,15 +49,14 @@ type GrepResult struct { func parseGrepLine(line string) (*GrepResult, error) { r := &GrepResult{} sp := strings.SplitN(line, ":", 5) - var n int switch len(sp) { case 4: - // HEAD tree ID - r.TreeID = "HEAD" + // HEAD + r.Tree = "HEAD" case 5: - // Tree ID included - r.TreeID = sp[0] + // Tree included + r.Tree = sp[0] n++ default: return nil, fmt.Errorf("invalid grep line: %s", line) @@ -62,53 +68,53 @@ func parseGrepLine(line string) (*GrepResult, error) { 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, error) { +func (r *Repository) Grep(pattern string, opts ...GrepOptions) []*GrepResult { var opt GrepOptions if len(opts) > 0 { opt = opts[0] } - if opt.TreeID == "" { - opt.TreeID = "HEAD" + if opt.Tree == "" { + opt.Tree = "HEAD" } cmd := NewCommand("grep"). AddOptions(opt.CommandOptions). - // Result full-name, line number & column number + // Display full-name, line number and column number AddArgs("--full-name", "--line-number", "--column") if opt.IgnoreCase { - cmd.AddArgs("-i") + cmd.AddArgs("--ignore-case") } - if opt.WordMatch { - cmd.AddArgs("-w") + if opt.WordRegexp { + cmd.AddArgs("--word-regexp") } - if opt.ExtendedRegex { - cmd.AddArgs("-E") + if opt.ExtendedRegexp { + cmd.AddArgs("--extended-regexp") } - cmd.AddArgs(pattern, opt.TreeID) + cmd.AddArgs(pattern, opt.Tree) if opt.Pathspec != "" { cmd.AddArgs("--", opt.Pathspec) } - results := make([]*GrepResult, 0) stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path) - if err == nil && len(stdout) > 0 { - // 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) - } - } + if err != nil { + return nil } - return results, 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 index 8b74232a..4f60de8a 100644 --- a/repo_grep_test.go +++ b/repo_grep_test.go @@ -1,3 +1,7 @@ +// 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 ( @@ -6,94 +10,130 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRepoGrepSimple(t *testing.T) { - pattern := "programmingPoints" - expect := []GrepResult{ - { - TreeID: "HEAD", Path: "src/Main.groovy", Line: 7, Column: 5, Text: "int programmingPoints = 10", - }, - { - TreeID: "HEAD", Path: "src/Main.groovy", Line: 10, Column: 33, Text: `println "${name} has at least ${programmingPoints} programming points."`, - }, - { - TreeID: "HEAD", Path: "src/Main.groovy", Line: 11, Column: 12, Text: `println "${programmingPoints} squared is ${square(programmingPoints)}"`, - }, - { - TreeID: "HEAD", Path: "src/Main.groovy", Line: 12, Column: 12, Text: `println "${programmingPoints} divided by 2 bonus points is ${divide(programmingPoints, 2)}"`, - }, - { - TreeID: "HEAD", Path: "src/Main.groovy", Line: 13, Column: 12, Text: `println "${programmingPoints} minus 7 bonus points is ${subtract(programmingPoints, 7)}"`, - }, - { - TreeID: "HEAD", Path: "src/Main.groovy", Line: 14, Column: 12, Text: `println "${programmingPoints} plus 3 bonus points is ${sum(programmingPoints, 3)}"`, +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)}"`, }, } - results, err := testrepo.Grep(pattern) - assert.NoError(t, err) - for i, result := range results { - assert.Equal(t, expect[i], *result) - } + got := testrepo.Grep("programmingPoints") + assert.Equal(t, want, got) } -func TestRepoGrepIgnoreCase(t *testing.T) { - pattern := "Hello" - expect := []GrepResult{ - { - TreeID: "HEAD", Path: "README.txt", Line: 9, Column: 36, Text: "* git@github.com:matthewmccullough/hellogitworld.git", - }, - { - TreeID: "HEAD", Path: "README.txt", Line: 10, Column: 38, Text: "* git://github.com/matthewmccullough/hellogitworld.git", - }, - { - TreeID: "HEAD", Path: "README.txt", Line: 11, Column: 58, Text: "* https://matthewmccullough@github.com/matthewmccullough/hellogitworld.git", - }, - { - TreeID: "HEAD", Path: "src/Main.groovy", Line: 9, Column: 10, Text: `println "Hello ${name}"`, - }, - { - TreeID: "HEAD", Path: "src/main/java/com/github/App.java", Line: 4, Column: 4, Text: " * Hello again", - }, - { - TreeID: "HEAD", Path: "src/main/java/com/github/App.java", Line: 5, Column: 4, Text: " * Hello world!", - }, - { - TreeID: "HEAD", Path: "src/main/java/com/github/App.java", Line: 6, Column: 4, Text: " * Hello", - }, - { - TreeID: "HEAD", Path: "src/main/java/com/github/App.java", Line: 13, Column: 30, Text: ` System.out.println( "Hello World!" );`, +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!" );`, }, } - results, err := testrepo.Grep(pattern, GrepOptions{IgnoreCase: true}) - assert.NoError(t, err) - for i, result := range results { - assert.Equal(t, expect[i], *result) - } + got := testrepo.Grep("Hello", GrepOptions{IgnoreCase: true}) + assert.Equal(t, want, got) } -func TestRepoGrepRegex(t *testing.T) { - pattern := "Hello\\sW\\w+" - expect := []GrepResult{ +func TestRepository_Grep_ExtendedRegexp(t *testing.T) { + want := []*GrepResult{ { - TreeID: "HEAD", Path: "src/main/java/com/github/App.java", Line: 13, Column: 30, Text: ` System.out.println( "Hello World!" );`, + Tree: "HEAD", + Path: "src/main/java/com/github/App.java", + Line: 5, + Column: 4, + Text: ` * Hello world!`, }, } - results, err := testrepo.Grep(pattern, GrepOptions{ExtendedRegex: true}) - assert.NoError(t, err) - for i, result := range results { - assert.Equal(t, expect[i], *result) - } + got := testrepo.Grep(`Hello \w+`, GrepOptions{ExtendedRegexp: true}) + assert.Equal(t, want, got) } -func TestRepoGrepWord(t *testing.T) { - pattern := "Hello\\sW\\w+" - expect := []GrepResult{ +func TestRepository_Grep_WordRegexp(t *testing.T) { + want := []*GrepResult{ { - TreeID: "HEAD", Path: "src/main/java/com/github/App.java", Line: 13, Column: 36, Text: ` System.out.println( "Hello World!" );`, + Tree: "HEAD", + Path: "src/main/java/com/github/App.java", + Line: 5, + Column: 10, + Text: ` * Hello world!`, }, } - results, err := testrepo.Grep(pattern, GrepOptions{WordMatch: true}) - assert.NoError(t, err) - for i, result := range results { - assert.Equal(t, expect[i], *result) - } + got := testrepo.Grep("world", GrepOptions{WordRegexp: true}) + assert.Equal(t, want, got) } From 27f0252761b4d232ba57d1509e593b18edfbb45f Mon Sep 17 00:00:00 2001 From: Joe Chen Date: Sat, 11 Feb 2023 21:56:34 +0800 Subject: [PATCH 4/5] Try again --- repo_grep_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/repo_grep_test.go b/repo_grep_test.go index 4f60de8a..188bd7ea 100644 --- a/repo_grep_test.go +++ b/repo_grep_test.go @@ -115,12 +115,12 @@ func TestRepository_Grep_ExtendedRegexp(t *testing.T) { { Tree: "HEAD", Path: "src/main/java/com/github/App.java", - Line: 5, - Column: 4, - Text: ` * Hello world!`, + Line: 13, + Column: 30, + Text: ` System.out.println( "Hello World!" );`, }, } - got := testrepo.Grep(`Hello \w+`, GrepOptions{ExtendedRegexp: true}) + got := testrepo.Grep(`Hello\sW\w+`, GrepOptions{ExtendedRegexp: true}) assert.Equal(t, want, got) } From c06c98023ded42d399522eac9d386117f37a0f6d Mon Sep 17 00:00:00 2001 From: Joe Chen Date: Sat, 11 Feb 2023 22:03:02 +0800 Subject: [PATCH 5/5] Skip regexp on macOS --- repo_grep_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/repo_grep_test.go b/repo_grep_test.go index 188bd7ea..2c0be390 100644 --- a/repo_grep_test.go +++ b/repo_grep_test.go @@ -5,6 +5,7 @@ package git import ( + "runtime" "testing" "github.com/stretchr/testify/assert" @@ -111,6 +112,10 @@ func TestRepository_Grep_IgnoreCase(t *testing.T) { } func TestRepository_Grep_ExtendedRegexp(t *testing.T) { + if runtime.GOOS == "darwin" { + t.Skip("Skipping testing on macOS") + return + } want := []*GrepResult{ { Tree: "HEAD",