From: Russ Cox Date: Thu, 5 Nov 2009 17:27:19 +0000 (-0800) Subject: new command hgpatch, for use by codereview extension X-Git-Tag: weekly.2009-11-06~51 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=1329012a235067c057726341f369b6960682dae9;p=gostls13.git new command hgpatch, for use by codereview extension R=r http://go/go-review/1018059 --- diff --git a/src/cmd/clean.bash b/src/cmd/clean.bash index e18c600381..3237d4c965 100644 --- a/src/cmd/clean.bash +++ b/src/cmd/clean.bash @@ -3,7 +3,7 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -for i in cc 6l 6a 6c 8l 8a 8c 8g 5l 5a 5c 5g gc 6g gopack nm cgo cov ebnflint godefs godoc gofmt prof gotest goyacc +for i in cc 6l 6a 6c 8l 8a 8c 8g 5l 5a 5c 5g gc 6g gopack nm cgo cov ebnflint godefs godoc gofmt gotest goyacc hgpatch prof do cd $i make clean diff --git a/src/cmd/hgpatch/Makefile b/src/cmd/hgpatch/Makefile new file mode 100644 index 0000000000..85f0722c8d --- /dev/null +++ b/src/cmd/hgpatch/Makefile @@ -0,0 +1,11 @@ +# Copyright 2009 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +include $(GOROOT)/src/Make.$(GOARCH) + +TARG=hgpatch +GOFILES=\ + main.go\ + +include $(GOROOT)/src/Make.cmd diff --git a/src/cmd/hgpatch/doc.go b/src/cmd/hgpatch/doc.go new file mode 100644 index 0000000000..5d7674acf6 --- /dev/null +++ b/src/cmd/hgpatch/doc.go @@ -0,0 +1,19 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* + +Hgpatch reads a patch, which should have been generated by +a version control system like CVS, GIT, Mercurial, or Subversion, +from a file (or standard input) and applies that patch to the local +Mercurial repository. If successful, it writes a list of affected +files to standard output. + +Hgpatch is meant to be used by the Mercurial codereview extension. + +Usage: + hgpatch [patchfile] + +*/ +package documentation diff --git a/src/cmd/hgpatch/main.go b/src/cmd/hgpatch/main.go new file mode 100644 index 0000000000..d4d024083b --- /dev/null +++ b/src/cmd/hgpatch/main.go @@ -0,0 +1,394 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes"; + "container/vector"; + "exec"; + "flag"; + "fmt"; + "io"; + "os"; + "patch"; + "path"; + "sort"; + "strings"; +) + +func usage() { + fmt.Fprintf(os.Stderr, "usage: hgpatch [patchfile]\n"); + os.Exit(2); +} + +func main() { + flag.Usage = usage; + flag.Parse(); + + args := flag.Args(); + var data []byte; + var err os.Error; + switch len(args) { + case 0: + data, err = io.ReadAll(os.Stdin); + case 1: + data, err = io.ReadFile(args[0]); + default: + usage(); + } + chk(err); + + pset, err := patch.Parse(data); + chk(err); + + // Change to hg root directory, because + // patch paths are relative to root. + root, err := hgRoot(); + chk(err); + chk(os.Chdir(root)); + + op, err := pset.Apply(io.ReadFile); + chk(err); + + // Make sure there are no pending changes on the server. + if hgIncoming() { + fmt.Fprintf(os.Stderr, "incoming changes waiting; run hg sync first\n"); + os.Exit(2); + } + + // Make sure we won't be editing files with local pending changes. + dirtylist, err := hgModified(); + chk(err); + dirty := make(map[string]int); + for _, f := range dirtylist { + dirty[f] = 1; + } + conflict := make(map[string]int); + for i := range op { + o := &op[i]; + if o.Verb == patch.Delete || o.Verb == patch.Rename { + if _, ok := dirty[o.Src]; ok { + conflict[o.Src] = 1; + } + } + if o.Verb != patch.Delete { + if _, ok := dirty[o.Dst]; ok { + conflict[o.Dst] = 1; + } + } + } + if len(conflict) > 0 { + fmt.Fprintf(os.Stderr, "cannot apply patch to locally modified files:\n"); + for name := range conflict { + fmt.Fprintf(os.Stderr, "\t%s\n", name); + } + os.Exit(2); + } + + // Apply to local copy: order of commands matters. + // Accumulate undo log as we go, in case there is an error. + // Also accumulate list of modified files to print at end. + changed := make(map[string]int); + + // Copy, Rename create the destination file, so they + // must happen before we write the data out. + // A single patch may have a Copy and a Rename + // with the same source, so we have to run all the + // Copy in one pass, then all the Rename. + for i := range op { + o := &op[i]; + if o.Verb == patch.Copy { + makeParent(o.Dst); + chk(hgCopy(o.Dst, o.Src)); + undoRevert(o.Dst); + changed[o.Dst] = 1; + } + } + for i := range op { + o := &op[i]; + if o.Verb == patch.Rename { + makeParent(o.Dst); + chk(hgRename(o.Dst, o.Src)); + undoRevert(o.Dst); + undoRevert(o.Src); + changed[o.Src] = 1; + changed[o.Dst] = 1; + } + } + + // Run Delete before writing to files in case one of the + // deleted paths is becoming a directory. + for i := range op { + o := &op[i]; + if o.Verb == patch.Delete { + chk(hgRemove(o.Src)); + undoRevert(o.Src); + changed[o.Src] = 1; + } + } + + // Write files. + for i := range op { + o := &op[i]; + if o.Verb == patch.Delete { + continue; + } + if o.Verb == patch.Add { + makeParent(o.Dst); + changed[o.Dst] = 1; + } + if o.Data != nil { + chk(io.WriteFile(o.Dst, o.Data, 0644)); + if o.Verb == patch.Add { + undoRm(o.Dst); + } else { + undoRevert(o.Dst); + } + changed[o.Dst] = 1; + } + if o.Mode != 0 { + chk(os.Chmod(o.Dst, o.Mode & 0755)); + undoRevert(o.Dst); + changed[o.Dst] = 1; + } + } + + // hg add looks at the destination file, so it must happen + // after we write the data out. + for i := range op { + o := &op[i]; + if o.Verb == patch.Add { + chk(hgAdd(o.Dst)); + undoRevert(o.Dst); + changed[o.Dst] = 1; + } + } + + // Finished editing files. Write the list of changed files to stdout. + list := make([]string, len(changed)); + i := 0; + for f := range changed { + list[i] = f; + i++; + } + sort.SortStrings(list); + for _, f := range list { + fmt.Printf("%s\n", f); + } +} + + +// make parent directory for name, if necessary +func makeParent(name string) { + parent, _ := path.Split(name); + chk(mkdirAll(parent, 0755)); +} + +// Copy of os.MkdirAll but adds to undo log after +// creating a directory. +func mkdirAll(path string, perm int) os.Error { + dir, err := os.Lstat(path); + if err == nil { + if dir.IsDirectory() { + return nil; + } + return &os.PathError{"mkdir", path, os.ENOTDIR}; + } + + i := len(path); + for i > 0 && path[i-1] == '/' { // Skip trailing slashes. + i--; + } + + j := i; + for j > 0 && path[j-1] != '/' { // Scan backward over element. + j--; + } + + if j > 0 { + err = mkdirAll(path[0 : j-1], perm); + if err != nil { + return err; + } + } + + err = os.Mkdir(path, perm); + if err != nil { + // Handle arguments like "foo/." by + // double-checking that directory doesn't exist. + dir, err1 := os.Lstat(path); + if err1 == nil && dir.IsDirectory() { + return nil; + } + return err; + } + undoRm(path); + return nil; +} + +// If err != nil, process the undo log and exit. +func chk(err os.Error) { + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err); + runUndo(); + os.Exit(2); + } +} + + +// Undo log +type undo func() os.Error + +var undoLog vector.Vector // vector of undo + +func undoRevert(name string) { + undoLog.Push(undo(func() os.Error { return hgRevert(name) })); +} + +func undoRm(name string) { + undoLog.Push(undo(func() os.Error { return os.Remove(name) })); +} + +func runUndo() { + for i := undoLog.Len() - 1; i >= 0; i-- { + if err := undoLog.At(i).(undo)(); err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err); + } + } +} + + +// hgRoot returns the root directory of the repository. +func hgRoot() (string, os.Error) { + out, err := run([]string{"hg", "root"}, nil); + if err != nil { + return "", err; + } + return strings.TrimSpace(out), nil; +} + +// hgIncoming returns true if hg sync will pull in changes. +func hgIncoming() bool { + // hg -q incoming exits 0 when there is nothing incoming, 1 otherwise. + _, err := run([]string{"hg", "-q", "incoming"}, nil); + return err == nil; +} + +// hgModified returns a list of the modified files in the +// repository. +func hgModified() ([]string, os.Error) { + out, err := run([]string{"hg", "status", "-n"}, nil); + if err != nil { + return nil, err; + } + return strings.Split(strings.TrimSpace(out), "\n", 0), nil; +} + +// hgAdd adds name to the repository. +func hgAdd(name string) os.Error { + _, err := run([]string{"hg", "add", name}, nil); + return err; +} + +// hgRemove removes name from the repository. +func hgRemove(name string) os.Error { + _, err := run([]string{"hg", "rm", name}, nil); + return err; +} + +// hgRevert reverts name. +func hgRevert(name string) os.Error { + _, err := run([]string{"hg", "revert", name}, nil); + return err; +} + +// hgCopy copies src to dst in the repository. +// Note that the argument order matches io.Copy, not "hg cp". +func hgCopy(dst, src string) os.Error { + _, err := run([]string{"hg", "cp", src, dst}, nil); + return err; +} + +// hgRename renames src to dst in the repository. +// Note that the argument order matches io.Copy, not "hg mv". +func hgRename(dst, src string) os.Error { + _, err := run([]string{"hg", "mv", src, dst}, nil); + return err; +} + +func copy(a []string) []string { + b := make([]string, len(a)); + for i, s := range a { + b[i] = s; + } + return b; +} + +var lookPathCache = make(map[string]string) + +// run runs the command argv, resolving argv[0] if necessary by searching $PATH. +// It provides input on standard input to the command. +func run(argv []string, input []byte) (out string, err os.Error) { + if len(argv) < 1 { + err = os.EINVAL; + goto Error; + } + prog, ok := lookPathCache[argv[0]]; + if !ok { + prog, err = exec.LookPath(argv[0]); + if err != nil { + goto Error; + } + lookPathCache[argv[0]] = prog; + } + fmt.Fprintf(os.Stderr, "%v\n", argv); + var cmd *exec.Cmd; + if len(input) == 0 { + cmd, err = exec.Run(prog, argv, os.Environ(), exec.DevNull, exec.Pipe, exec.MergeWithStdout); + if err != nil { + goto Error; + } + } else { + cmd, err = exec.Run(prog, argv, os.Environ(), exec.Pipe, exec.Pipe, exec.MergeWithStdout); + if err != nil { + goto Error; + } + go func() { + cmd.Stdin.Write(input); + cmd.Stdin.Close(); + }(); + } + defer cmd.Close(); + var buf bytes.Buffer; + _, err = io.Copy(&buf, cmd.Stdout); + out = buf.String(); + if err != nil { + cmd.Wait(0); + goto Error; + } + w, err := cmd.Wait(0); + if err != nil { + goto Error; + } + if !w.Exited() || w.ExitStatus() != 0 { + err = w; + goto Error; + } + return; + +Error: + err = &runError{copy(argv), err}; + return; +} + +// A runError represents an error that occurred while running a command. +type runError struct { + cmd []string; + err os.Error; +} + +func (e *runError) String() string { + return strings.Join(e.cmd, " ") + ": " + e.err.String(); +} diff --git a/src/make.bash b/src/make.bash index 97750acc5b..aaaf547efd 100755 --- a/src/make.bash +++ b/src/make.bash @@ -20,7 +20,7 @@ CC=${CC:-gcc} sed -e "s|@CC@|$CC|" < quietgcc.bash > $GOBIN/quietgcc chmod +x $GOBIN/quietgcc -for i in lib9 libbio libmach cmd pkg libcgo cmd/cgo cmd/ebnflint cmd/godoc cmd/gofmt cmd/goyacc +for i in lib9 libbio libmach cmd pkg libcgo cmd/cgo cmd/ebnflint cmd/godoc cmd/gofmt cmd/goyacc cmd/hgpatch do case "$i-$GOOS" in libcgo-nacl)