]> Cypherpunks repositories - gostls13.git/commitdiff
new command hgpatch, for use by codereview extension
authorRuss Cox <rsc@golang.org>
Thu, 5 Nov 2009 17:27:19 +0000 (09:27 -0800)
committerRuss Cox <rsc@golang.org>
Thu, 5 Nov 2009 17:27:19 +0000 (09:27 -0800)
R=r
http://go/go-review/1018059

src/cmd/clean.bash
src/cmd/hgpatch/Makefile [new file with mode: 0644]
src/cmd/hgpatch/doc.go [new file with mode: 0644]
src/cmd/hgpatch/main.go [new file with mode: 0644]
src/make.bash

index e18c600381c88e9fc8e5443a5ab31881b63f2f2b..3237d4c965d182e4f9ccebd2e2b9106b35855877 100644 (file)
@@ -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 (file)
index 0000000..85f0722
--- /dev/null
@@ -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 (file)
index 0000000..5d7674a
--- /dev/null
@@ -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 (file)
index 0000000..d4d0240
--- /dev/null
@@ -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();
+}
index 97750acc5b7dadbc3c1c8be6c07070adbebf5109..aaaf547efd2afd7c947bfd13c37de9da359b57e1 100755 (executable)
@@ -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)