Skip to content

Commit

Permalink
Add --diff flag (#64)
Browse files Browse the repository at this point in the history
Adds a `--diff` flag to gopatch
that prints a diff of the changes that would be made
without modifying the affected files.

The diff is printed to stdout,
and a description of each applied patch is printed to stderr.

The description of a patch, if specified,
 is a comment right above the first `@@`.

In the future, we'll make two more changes:

- Turn the paths in the generated diff relative
  if the original file paths were not absolute.
- Include file name and line number for each patch description.

Resolves #20
  • Loading branch information
lverma14 authored Aug 23, 2022
1 parent 3851876 commit b81fef5
Show file tree
Hide file tree
Showing 22 changed files with 723 additions and 48 deletions.
194 changes: 177 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ refactoring and restyling.
- [Metavariables](#metavariables)
- [Statements](#statements)
- [Elision](#elision)
- [Comments](#comments)
- [Description comments](#description-comments)
- [Usage in diff mode](#usage-during-diff-mode)
- [Examples](#examples)
- [Project status](#project-status)
- [Goals](#goals)
Expand Down Expand Up @@ -107,23 +110,64 @@ return fmt.Errorf("invalid port: %v", err)

## Apply the patch

To apply the patch, `cd` to your Go project's directory.

```shell
$ cd ~/go/src/example.com/myproject
```

Run `gopatch` on the project, supplying the previously written patch with the
`-p` flag.

```shell
$ gopatch -p ~/s1028.patch ./...
```

This will apply the patch on all Go code in your project.

Check if there were any instances of this issue in your code by running
`git diff`.
- `cd` to your Go project's directory.

```shell
$ cd ~/go/src/example.com/myproject
```

Run `gopatch` on the project, supplying the previously written patch with the
`-p` flag.

```shell
$ gopatch -p ~/s1028.patch ./...
```

This will apply the patch on all Go code in your project.

Check if there were any instances of this issue in your code by running
`git diff`.
- Instead, `cd` to your Go project's directory.

```shell
$ cd ~/go/src/example.com/myproject
```

Run `gopatch` on the project, supplying the previously written patch with the
`-p` flag along with '-d' flag.

```shell
$ gopatch -d -p ~/s1028.patch ./...
```

This will turn on diff mode and will write the diff to stdout instead of modifying all
the Go code in your project. To provide more context on what the patch does, if
there were description comments in the patch, they will also get displayed at
the top. To learn more about description comments jump to section [here](#description-comments)

For example if we applied patch ~/s1028 to our testfile error.go
```shell
$ gopatch -d -p ~/s1028.patch ./testdata/test_files/diff_example/
```
Output would be :
```
This patch replaces instances of fmt.Sprintf()
with fmt.Errorf()
Patch files can be applied to mutiple files
--- gopatch/testdata/test_files/diff_example/error.go
+++ gopatch/testdata/test_files/diff_example/error.go
@@ -7,7 +7,7 @@
func foo() error {
err := errors.New("test")
- return errors.New(fmt.Sprintf("error: %v", err))
+ return fmt.Errorf("error: %v", err)
}
func main() {
```
Note: Only the description comments of patches that actually **apply** are displayed.

## Next steps

Expand Down Expand Up @@ -174,6 +218,22 @@ gopatch supports the following command line options.
+bar
EOF
```
- `-d`, `--diff`
Flag to turn on diff mode. Provide this flag to write the diff to stdout instead
of modifying the file and display applied patches' [description comments](#description-comments) if they exist.
Use in conjunction with -p to provide patch file.
Only need to apply the flag once to turn on diff mode
```shell
$ gopatch -d -p foo.patch -p bar.patch path/to/my/project
```
If this flag is omitted, normal patching occurs which modifies the
file instead.
# Patches
Expand Down Expand Up @@ -452,7 +512,107 @@ if err := foo(); err != nil {
For more on elision, see [Patches in depth/Elision].
[Patches in depth/Elision]: docs/PatchesInDepth.md#elision
## Comments
Patches come with comments to give more context about what they do.
Comments are prefixed by '#'
For example:
```
# This patch replaces instances of time.Now().Sub(x)
# with time.Since(x) where x is an identifier variable
@@
# Var x is in the metavariable section
var x identifier
@@
-time.Now().Sub(x)
+time.Since(x)
# We replace time.Now().Sub(x)
# with time.Since(x)
```
#### Description comments
These are the comments which explain what the patch does. There is
no requirement for the patches to contain them but they are encouraged
to give more context to the user.
The Description comments must occur just before the metavariable
section.
For example:
```
# Description comments
# This patch replaces instances of time.Now().Sub(x)
# with time.Since(x) where x is an identifier variable
@@
# Not a description comment
var x identifier
@@
-time.Now().Sub(x)
+time.Since(x)
# Not a description comment
# Not a description comment
```
Patch files which have multiple patches can have mutiple description
comments. For example
```
# 1st patch Description comment
# This patch replaces instances of fmt.Sprintf()
# with fmt.Errorf()
# Patch files can be applied to mutiple files
@@
@@
# Not a description comment
-import "errors"
-errors.New(fmt.Sprintf(...))
+fmt.Errorf(...)
# Start of 2nd Patch Description comment
# This patch replaces instances of time.Now().Sub(x)
# with time.Since(x) where x is an identifier variable
@@
var x identifier
@@
-time.Now().Sub(x)
+time.Since(x)
# Not a description comment
```
#### Usage during diff mode
When diff mode is turned on by the '-d' flag we also display the
description/title comments of only the applied patches to help
the user understand what the patches do.
```shell
$ gopatch -d -p ~/s1028.patch testdata/test_files/diff_example/error.go
```
```
This patch replaces instances of fmt.Sprintf()
with fmt.Errorf()
Patch files can be applied to mutiple files
--- gopatch/testdata/test_files/diff_example/error.go
+++ gopatch/testdata/test_files/diff_example/error.go
@@ -7,7 +7,7 @@
func foo() error {
err := errors.New("test")
- return errors.New(fmt.Sprintf("error: %v", err))
+ return fmt.Errorf("error: %v", err)
}
func main() {
```
Note: Only the description comments get displayed during diff mode.
Non description comments are ignored.
Moreover, only comments from patches that actually apply on the
target file are shown.
# Examples
This section lists various example patches you can try in your code.
Expand Down
44 changes: 36 additions & 8 deletions e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"bufio"
"bytes"
"fmt"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"path/filepath"
Expand All @@ -32,7 +33,6 @@ import (
"testing"

"github.com/rogpeppe/go-internal/txtar"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/multierr"
)
Expand Down Expand Up @@ -118,11 +118,22 @@ func runIntegrationTest(t *testing.T, testFile string) {
}

filePath := filepath.Join(dir, tt.Give)
if len(tt.WantDiff) > 0 {
args := append([]string{"-d", filePath}, args...)
var stderr, stdout bytes.Buffer
require.NoError(t, run(args, bytes.NewReader(stdin), &stderr, &stdout),
"could not run patch")
wantSplit := strings.Split(string(tt.WantDiff), "\n")
// 1st two lines skipped as they contain absolute path that keeps changing every testcase
gotSplit := strings.Split(stdout.String(), "\n")[2:]
assert.Equal(t, wantSplit, gotSplit)
assert.Equal(t, string(tt.WantComment), stderr.String())

}
args := append([]string{filePath}, args...)
require.NoError(t, run(args, bytes.NewReader(stdin), new(bytes.Buffer)),
require.NoError(t, run(args, bytes.NewReader(stdin), new(bytes.Buffer), new(bytes.Buffer)),
"could not run patch")

got, err := ioutil.ReadFile(filePath)
got, err := os.ReadFile(filePath)
require.NoErrorf(t, err, "failed to read %q", filePath)
assert.Equal(t, string(tt.Want), string(got))
})
Expand Down Expand Up @@ -157,10 +168,12 @@ var testsToSkip = map[string]struct{}{
}

const (
_patch = ".patch"
_in = ".in.go"
_out = ".out.go"
_go = ".go"
_patch = ".patch"
_in = ".in.go"
_out = ".out.go"
_go = ".go"
_diff = ".diff"
_stderr = ".diff.stderr"
)

type testArchive struct {
Expand All @@ -185,6 +198,12 @@ type testFile struct {
// Expected contents of the Go file after the patches have been
// applied.
Want []byte

// Expected diff if tool run in --diff mode
WantDiff []byte

// Expected comment if tool run in --diff mode
WantComment []byte
}

// Loads a test archive in-memory.
Expand Down Expand Up @@ -230,6 +249,15 @@ func loadTestArchive(path string) (*testArchive, error) {
f.Data = singleTrailingNewline(f.Data)

getTestFile(name).Give = f.Name

case strings.HasSuffix(f.Name, _diff):
name := strings.TrimSuffix(f.Name, _diff) // foo.diff => foo
getTestFile(name).WantDiff = singleTrailingNewline(f.Data)

case strings.HasSuffix(f.Name, _stderr):
name := strings.TrimSuffix(f.Name, _stderr)
getTestFile(name).WantComment = singleTrailingNewline(f.Data)

case strings.HasSuffix(f.Name, _out):
name := strings.TrimSuffix(f.Name, _out) // foo.out.go => foo
getTestFile(name).Want = singleTrailingNewline(f.Data)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/google/go-intervals v0.0.2
github.com/jessevdk/go-flags v1.5.0
github.com/kr/pretty v0.3.0
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
github.com/rogpeppe/go-internal v1.8.1
github.com/stretchr/testify v1.8.0
go.uber.org/multierr v1.8.0
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down
2 changes: 2 additions & 0 deletions internal/engine/change.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Change struct {
Name string
Meta *Meta

Comments []string
fset *token.FileSet
matcher FileMatcher
replacer FileReplacer
Expand All @@ -59,6 +60,7 @@ func (c *compiler) compileChange(achange *parse.Change) *Change {
fset: c.fset,
matcher: matcher,
replacer: replacer,
Comments: achange.Comments,
}
}

Expand Down
3 changes: 3 additions & 0 deletions internal/parse/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ type Change struct {

// Patch for this change.
Patch *Patch

// Comments for this change
Comments []string
}

// Meta represents the metavariables section of a change.
Expand Down
1 change: 1 addition & 0 deletions internal/parse/change.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ func (p *parser) parseChange(i int, c *section.Change) (_ *Change, err error) {
return nil, err
}

change.Comments = c.Comments
return &change, nil
}
Loading

0 comments on commit b81fef5

Please sign in to comment.