1
0
mirror of https://github.com/golang/go synced 2024-11-12 10:30:23 -07:00

new command hgpatch, for use by codereview extension

R=r
http://go/go-review/1018059
This commit is contained in:
Russ Cox 2009-11-05 09:27:19 -08:00
parent 3630bfbe9f
commit 1329012a23
5 changed files with 426 additions and 2 deletions

View 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

11
src/cmd/hgpatch/Makefile Normal file
View File

@ -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

19
src/cmd/hgpatch/doc.go Normal file
View File

@ -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

394
src/cmd/hgpatch/main.go Normal file
View File

@ -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();
}

View File

@ -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)