mirror of
https://github.com/golang/go
synced 2024-11-18 08:54:45 -07:00
cmd/gorename: -d flag prints diffs instead of rewriting files
The user may specify the diff tool using the -diffcmd flag. + test. Also: - eliminate redundant DryRun flag - simplify Verbose messages using log.SetPrefix Fixes issue #13355 Change-Id: I917edc73e31ddf0f5d5b9b30c43f826465529da1 Reviewed-on: https://go-review.googlesource.com/18208 Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
parent
a09dffbb6c
commit
108b52ee61
@ -11,8 +11,8 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/buildutil"
|
"golang.org/x/tools/go/buildutil"
|
||||||
"golang.org/x/tools/refactor/rename"
|
"golang.org/x/tools/refactor/rename"
|
||||||
@ -28,25 +28,17 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
|
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
|
||||||
flag.BoolVar(&rename.Force, "force", false, "proceed, even if conflicts were reported")
|
flag.BoolVar(&rename.Force, "force", false, "proceed, even if conflicts were reported")
|
||||||
flag.BoolVar(&rename.DryRun, "dryrun", false, "show the change, but do not apply it")
|
|
||||||
flag.BoolVar(&rename.Verbose, "v", false, "print verbose information")
|
flag.BoolVar(&rename.Verbose, "v", false, "print verbose information")
|
||||||
|
flag.BoolVar(&rename.Diff, "d", false, "display diffs instead of rewriting files")
|
||||||
// If $GOMAXPROCS isn't set, use the full capacity of the machine.
|
flag.StringVar(&rename.DiffCmd, "diffcmd", "diff", "diff command invoked when using -d")
|
||||||
// For small machines, use at least 4 threads.
|
|
||||||
if os.Getenv("GOMAXPROCS") == "" {
|
|
||||||
n := runtime.NumCPU()
|
|
||||||
if n < 4 {
|
|
||||||
n = 4
|
|
||||||
}
|
|
||||||
runtime.GOMAXPROCS(n)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
log.SetPrefix("gorename: ")
|
||||||
|
log.SetFlags(0)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if len(flag.Args()) > 0 {
|
if len(flag.Args()) > 0 {
|
||||||
fmt.Fprintln(os.Stderr, "gorename: surplus arguments.")
|
log.Fatal("surplus arguments")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if *helpFlag || (*offsetFlag == "" && *fromFlag == "" && *toFlag == "") {
|
if *helpFlag || (*offsetFlag == "" && *fromFlag == "" && *toFlag == "") {
|
||||||
@ -56,7 +48,7 @@ func main() {
|
|||||||
|
|
||||||
if err := rename.Main(&build.Default, *offsetFlag, *fromFlag, *toFlag); err != nil {
|
if err := rename.Main(&build.Default, *offsetFlag, *fromFlag, *toFlag); err != nil {
|
||||||
if err != rename.ConflictError {
|
if err != rename.ConflictError {
|
||||||
fmt.Fprintf(os.Stderr, "gorename: %s\n", err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
"go/build"
|
||||||
|
"go/format"
|
||||||
"go/token"
|
"go/token"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@ -321,8 +322,13 @@ func (m *mover) move() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for f := range filesToUpdate {
|
for f := range filesToUpdate {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := format.Node(&buf, m.iprog.Fset, f); err != nil {
|
||||||
|
log.Printf("failed to pretty-print syntax tree: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
tokenFile := m.iprog.Fset.File(f.Pos())
|
tokenFile := m.iprog.Fset.File(f.Pos())
|
||||||
rewriteFile(m.iprog.Fset, f, tokenFile.Name())
|
writeFile(tokenFile.Name(), buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the directories.
|
// Move the directories.
|
||||||
|
@ -5,12 +5,8 @@
|
|||||||
package rename
|
package rename
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
"go/build"
|
||||||
"go/format"
|
|
||||||
"go/token"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -79,12 +75,8 @@ var _ foo.T
|
|||||||
ctxt := test.ctxt
|
ctxt := test.ctxt
|
||||||
|
|
||||||
got := make(map[string]string)
|
got := make(map[string]string)
|
||||||
rewriteFile = func(fset *token.FileSet, f *ast.File, orig string) error {
|
writeFile = func(filename string, content []byte) error {
|
||||||
var out bytes.Buffer
|
got[filename] = string(content)
|
||||||
if err := format.Node(&out, fset, f); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
got[orig] = out.String()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
moveDirectory = func(from, to string) error {
|
moveDirectory = func(from, to string) error {
|
||||||
@ -304,12 +296,8 @@ var _ bar.T
|
|||||||
}
|
}
|
||||||
got[path] = string(bytes)
|
got[path] = string(bytes)
|
||||||
})
|
})
|
||||||
rewriteFile = func(fset *token.FileSet, f *ast.File, orig string) error {
|
writeFile = func(filename string, content []byte) error {
|
||||||
var out bytes.Buffer
|
got[filename] = string(content)
|
||||||
if err := format.Node(&out, fset, f); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
got[orig] = out.String()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
moveDirectory = func(from, to string) error {
|
moveDirectory = func(from, to string) error {
|
||||||
|
@ -16,8 +16,11 @@ import (
|
|||||||
"go/format"
|
"go/format"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -76,7 +79,7 @@ Flags:
|
|||||||
(In due course this bug will be fixed by moving certain
|
(In due course this bug will be fixed by moving certain
|
||||||
analyses into the type-checker.)
|
analyses into the type-checker.)
|
||||||
|
|
||||||
-dryrun causes the tool to report conflicts but not update any files.
|
-d display diffs instead of rewriting files
|
||||||
|
|
||||||
-v enables verbose logging.
|
-v enables verbose logging.
|
||||||
|
|
||||||
@ -137,8 +140,12 @@ var (
|
|||||||
// It may even cause gorename to crash. TODO(adonovan): fix that.
|
// It may even cause gorename to crash. TODO(adonovan): fix that.
|
||||||
Force bool
|
Force bool
|
||||||
|
|
||||||
// DryRun causes the tool to report conflicts but not update any files.
|
// Diff causes the tool to display diffs instead of rewriting files.
|
||||||
DryRun bool
|
Diff bool
|
||||||
|
|
||||||
|
// DiffCmd specifies the diff command used by the -d feature.
|
||||||
|
// (The command must accept a -u flag and two filename arguments.)
|
||||||
|
DiffCmd = "diff"
|
||||||
|
|
||||||
// ConflictError is returned by Main when it aborts the renaming due to conflicts.
|
// ConflictError is returned by Main when it aborts the renaming due to conflicts.
|
||||||
// (It is distinguished because the interesting errors are the conflicts themselves.)
|
// (It is distinguished because the interesting errors are the conflicts themselves.)
|
||||||
@ -148,6 +155,8 @@ var (
|
|||||||
Verbose bool
|
Verbose bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var stdout io.Writer
|
||||||
|
|
||||||
type renamer struct {
|
type renamer struct {
|
||||||
iprog *loader.Program
|
iprog *loader.Program
|
||||||
objsToUpdate map[types.Object]bool
|
objsToUpdate map[types.Object]bool
|
||||||
@ -212,6 +221,11 @@ func Main(ctxt *build.Context, offsetFlag, fromFlag, to string) error {
|
|||||||
return fmt.Errorf("-to %q: not a valid identifier", to)
|
return fmt.Errorf("-to %q: not a valid identifier", to)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Diff {
|
||||||
|
defer func(saved func(string, []byte) error) { writeFile = saved }(writeFile)
|
||||||
|
writeFile = diff
|
||||||
|
}
|
||||||
|
|
||||||
var spec *spec
|
var spec *spec
|
||||||
var err error
|
var err error
|
||||||
if fromFlag != "" {
|
if fromFlag != "" {
|
||||||
@ -248,7 +262,7 @@ func Main(ctxt *build.Context, offsetFlag, fromFlag, to string) error {
|
|||||||
// package defining the object, plus their tests.
|
// package defining the object, plus their tests.
|
||||||
|
|
||||||
if Verbose {
|
if Verbose {
|
||||||
fmt.Fprintln(os.Stderr, "Potentially global renaming; scanning workspace...")
|
log.Print("Potentially global renaming; scanning workspace...")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan the workspace and build the import graph.
|
// Scan the workspace and build the import graph.
|
||||||
@ -329,10 +343,6 @@ func Main(ctxt *build.Context, offsetFlag, fromFlag, to string) error {
|
|||||||
if r.hadConflicts && !Force {
|
if r.hadConflicts && !Force {
|
||||||
return ConflictError
|
return ConflictError
|
||||||
}
|
}
|
||||||
if DryRun {
|
|
||||||
// TODO(adonovan): print the delta?
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return r.update()
|
return r.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,7 +371,7 @@ func loadProgram(ctxt *build.Context, pkgs map[string]bool) (*loader.Program, er
|
|||||||
}
|
}
|
||||||
sort.Strings(list)
|
sort.Strings(list)
|
||||||
for _, pkg := range list {
|
for _, pkg := range list {
|
||||||
fmt.Fprintf(os.Stderr, "Loading package: %s\n", pkg)
|
log.Printf("Loading package: %s", pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,21 +443,30 @@ func (r *renamer) update() error {
|
|||||||
npkgs++
|
npkgs++
|
||||||
first = false
|
first = false
|
||||||
if Verbose {
|
if Verbose {
|
||||||
fmt.Fprintf(os.Stderr, "Updating package %s\n",
|
log.Printf("Updating package %s", info.Pkg.Path())
|
||||||
info.Pkg.Path())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := rewriteFile(r.iprog.Fset, f, tokenFile.Name()); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "gorename: %s\n", err)
|
filename := tokenFile.Name()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := format.Node(&buf, r.iprog.Fset, f); err != nil {
|
||||||
|
log.Printf("failed to pretty-print syntax tree: %v", err)
|
||||||
|
nerrs++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := writeFile(filename, buf.Bytes()); err != nil {
|
||||||
|
log.Print(err)
|
||||||
nerrs++
|
nerrs++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "Renamed %d occurrence%s in %d file%s in %d package%s.\n",
|
if !Diff {
|
||||||
nidents, plural(nidents),
|
fmt.Printf("Renamed %d occurrence%s in %d file%s in %d package%s.\n",
|
||||||
len(filesToUpdate), plural(len(filesToUpdate)),
|
nidents, plural(nidents),
|
||||||
npkgs, plural(npkgs))
|
len(filesToUpdate), plural(len(filesToUpdate)),
|
||||||
|
npkgs, plural(npkgs))
|
||||||
|
}
|
||||||
if nerrs > 0 {
|
if nerrs > 0 {
|
||||||
return fmt.Errorf("failed to rewrite %d file%s", nerrs, plural(nerrs))
|
return fmt.Errorf("failed to rewrite %d file%s", nerrs, plural(nerrs))
|
||||||
}
|
}
|
||||||
@ -461,15 +480,29 @@ func plural(n int) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var rewriteFile = func(fset *token.FileSet, f *ast.File, filename string) (err error) {
|
// writeFile is a seam for testing and for the -d flag.
|
||||||
// TODO(adonovan): print packages and filenames in a form useful
|
var writeFile = reallyWriteFile
|
||||||
// to editors (so they can reload files).
|
|
||||||
if Verbose {
|
func reallyWriteFile(filename string, content []byte) error {
|
||||||
fmt.Fprintf(os.Stderr, "\t%s\n", filename)
|
return ioutil.WriteFile(filename, content, 0644)
|
||||||
}
|
}
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := format.Node(&buf, fset, f); err != nil {
|
func diff(filename string, content []byte) error {
|
||||||
return fmt.Errorf("failed to pretty-print syntax tree: %v", err)
|
renamed := filename + ".renamed"
|
||||||
}
|
if err := ioutil.WriteFile(renamed, content, 0644); err != nil {
|
||||||
return ioutil.WriteFile(filename, buf.Bytes(), 0644)
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(renamed)
|
||||||
|
|
||||||
|
diff, err := exec.Command(DiffCmd, "-u", filename, renamed).CombinedOutput()
|
||||||
|
if len(diff) > 0 {
|
||||||
|
// diff exits with a non-zero status when the files don't match.
|
||||||
|
// Ignore that failure as long as we get output.
|
||||||
|
stdout.Write(diff)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("computing diff: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,9 @@ package rename
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
"go/build"
|
||||||
"go/format"
|
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@ -22,11 +21,11 @@ import (
|
|||||||
// TODO(adonovan): test reported source positions, somehow.
|
// TODO(adonovan): test reported source positions, somehow.
|
||||||
|
|
||||||
func TestConflicts(t *testing.T) {
|
func TestConflicts(t *testing.T) {
|
||||||
defer func(savedDryRun bool, savedReportError func(token.Position, string)) {
|
defer func(savedWriteFile func(string, []byte) error, savedReportError func(token.Position, string)) {
|
||||||
DryRun = savedDryRun
|
writeFile = savedWriteFile
|
||||||
reportError = savedReportError
|
reportError = savedReportError
|
||||||
}(DryRun, reportError)
|
}(writeFile, reportError)
|
||||||
DryRun = true
|
writeFile = func(string, []byte) error { return nil }
|
||||||
|
|
||||||
var ctxt *build.Context
|
var ctxt *build.Context
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
@ -417,9 +416,9 @@ var _ I = E{}
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRewrites(t *testing.T) {
|
func TestRewrites(t *testing.T) {
|
||||||
defer func(savedRewriteFile func(*token.FileSet, *ast.File, string) error) {
|
defer func(savedWriteFile func(string, []byte) error) {
|
||||||
rewriteFile = savedRewriteFile
|
writeFile = savedWriteFile
|
||||||
}(rewriteFile)
|
}(writeFile)
|
||||||
|
|
||||||
var ctxt *build.Context
|
var ctxt *build.Context
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
@ -977,12 +976,8 @@ var _ = I(C(0)).(J)
|
|||||||
}
|
}
|
||||||
|
|
||||||
got := make(map[string]string)
|
got := make(map[string]string)
|
||||||
rewriteFile = func(fset *token.FileSet, f *ast.File, orig string) error {
|
writeFile = func(filename string, content []byte) error {
|
||||||
var out bytes.Buffer
|
got[filepath.ToSlash(filename)] = string(content)
|
||||||
if err := format.Node(&out, fset, f); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
got[filepath.ToSlash(orig)] = out.String()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1017,6 +1012,34 @@ var _ = I(C(0)).(J)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDiff(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
Diff = false
|
||||||
|
stdout = os.Stdout
|
||||||
|
}()
|
||||||
|
Diff = true
|
||||||
|
stdout = new(bytes.Buffer)
|
||||||
|
|
||||||
|
if err := Main(&build.Default, "", `"golang.org/x/tools/refactor/rename".justHereForTestingDiff`, "Foo"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: there are tabs in the string literal!
|
||||||
|
if !strings.Contains(stdout.(fmt.Stringer).String(), `
|
||||||
|
-func justHereForTestingDiff() {
|
||||||
|
- justHereForTestingDiff()
|
||||||
|
+func Foo() {
|
||||||
|
+ Foo()
|
||||||
|
}
|
||||||
|
`) {
|
||||||
|
t.Errorf("unexpected diff:\n<<%s>>", stdout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func justHereForTestingDiff() {
|
||||||
|
justHereForTestingDiff()
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
// Simplifying wrapper around buildutil.FakeContext for packages whose
|
// Simplifying wrapper around buildutil.FakeContext for packages whose
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"go/build"
|
"go/build"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -126,7 +127,7 @@ func parseFromFlag(ctxt *build.Context, fromFlag string) (*spec, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if Verbose {
|
if Verbose {
|
||||||
fmt.Fprintf(os.Stderr, "-from spec: %+v\n", spec)
|
log.Printf("-from spec: %+v", spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &spec, nil
|
return &spec, nil
|
||||||
|
Loading…
Reference in New Issue
Block a user