The Go Programming Language

Source file src/cmd/hgpatch/main.go

     1	// Copyright 2009 The Go Authors.  All rights reserved.
     2	// Use of this source code is governed by a BSD-style
     3	// license that can be found in the LICENSE file.
     4	
     5	package main
     6	
     7	import (
     8		"bytes"
     9		"container/vector"
    10		"exec"
    11		"flag"
    12		"fmt"
    13		"io/ioutil"
    14		"os"
    15		"patch"
    16		"path/filepath"
    17		"sort"
    18		"strings"
    19	)
    20	
    21	var checkSync = flag.Bool("checksync", true, "check whether repository is out of sync")
    22	
    23	func usage() {
    24		fmt.Fprintf(os.Stderr, "usage: hgpatch [options] [patchfile]\n")
    25		flag.PrintDefaults()
    26		os.Exit(2)
    27	}
    28	
    29	func main() {
    30		flag.Usage = usage
    31		flag.Parse()
    32	
    33		args := flag.Args()
    34		var data []byte
    35		var err os.Error
    36		switch len(args) {
    37		case 0:
    38			data, err = ioutil.ReadAll(os.Stdin)
    39		case 1:
    40			data, err = ioutil.ReadFile(args[0])
    41		default:
    42			usage()
    43		}
    44		chk(err)
    45	
    46		pset, err := patch.Parse(data)
    47		chk(err)
    48	
    49		// Change to hg root directory, because
    50		// patch paths are relative to root.
    51		root, err := hgRoot()
    52		chk(err)
    53		chk(os.Chdir(root))
    54	
    55		// Make sure there are no pending changes on the server.
    56		if *checkSync && hgIncoming() {
    57			fmt.Fprintf(os.Stderr, "incoming changes waiting; run hg sync first\n")
    58			os.Exit(2)
    59		}
    60	
    61		// Make sure we won't be editing files with local pending changes.
    62		dirtylist, err := hgModified()
    63		chk(err)
    64		dirty := make(map[string]bool)
    65		for _, f := range dirtylist {
    66			dirty[f] = true
    67		}
    68		conflict := make(map[string]bool)
    69		for _, f := range pset.File {
    70			if f.Verb == patch.Delete || f.Verb == patch.Rename {
    71				if dirty[f.Src] {
    72					conflict[f.Src] = true
    73				}
    74			}
    75			if f.Verb != patch.Delete {
    76				if dirty[f.Dst] {
    77					conflict[f.Dst] = true
    78				}
    79			}
    80		}
    81		if len(conflict) > 0 {
    82			fmt.Fprintf(os.Stderr, "cannot apply patch to locally modified files:\n")
    83			for name := range conflict {
    84				fmt.Fprintf(os.Stderr, "\t%s\n", name)
    85			}
    86			os.Exit(2)
    87		}
    88	
    89		// Apply changes in memory.
    90		op, err := pset.Apply(ioutil.ReadFile)
    91		chk(err)
    92	
    93		// Write changes to disk copy: order of commands matters.
    94		// Accumulate undo log as we go, in case there is an error.
    95		// Also accumulate list of modified files to print at end.
    96		changed := make(map[string]int)
    97	
    98		// Copy, Rename create the destination file, so they
    99		// must happen before we write the data out.
   100		// A single patch may have a Copy and a Rename
   101		// with the same source, so we have to run all the
   102		// Copy in one pass, then all the Rename.
   103		for i := range op {
   104			o := &op[i]
   105			if o.Verb == patch.Copy {
   106				makeParent(o.Dst)
   107				chk(hgCopy(o.Dst, o.Src))
   108				undoRevert(o.Dst)
   109				changed[o.Dst] = 1
   110			}
   111		}
   112		for i := range op {
   113			o := &op[i]
   114			if o.Verb == patch.Rename {
   115				makeParent(o.Dst)
   116				chk(hgRename(o.Dst, o.Src))
   117				undoRevert(o.Dst)
   118				undoRevert(o.Src)
   119				changed[o.Src] = 1
   120				changed[o.Dst] = 1
   121			}
   122		}
   123	
   124		// Run Delete before writing to files in case one of the
   125		// deleted paths is becoming a directory.
   126		for i := range op {
   127			o := &op[i]
   128			if o.Verb == patch.Delete {
   129				chk(hgRemove(o.Src))
   130				undoRevert(o.Src)
   131				changed[o.Src] = 1
   132			}
   133		}
   134	
   135		// Write files.
   136		for i := range op {
   137			o := &op[i]
   138			if o.Verb == patch.Delete {
   139				continue
   140			}
   141			if o.Verb == patch.Add {
   142				makeParent(o.Dst)
   143				changed[o.Dst] = 1
   144			}
   145			if o.Data != nil {
   146				chk(ioutil.WriteFile(o.Dst, o.Data, 0644))
   147				if o.Verb == patch.Add {
   148					undoRm(o.Dst)
   149				} else {
   150					undoRevert(o.Dst)
   151				}
   152				changed[o.Dst] = 1
   153			}
   154			if o.Mode != 0 {
   155				chk(os.Chmod(o.Dst, uint32(o.Mode&0755)))
   156				undoRevert(o.Dst)
   157				changed[o.Dst] = 1
   158			}
   159		}
   160	
   161		// hg add looks at the destination file, so it must happen
   162		// after we write the data out.
   163		for i := range op {
   164			o := &op[i]
   165			if o.Verb == patch.Add {
   166				chk(hgAdd(o.Dst))
   167				undoRevert(o.Dst)
   168				changed[o.Dst] = 1
   169			}
   170		}
   171	
   172		// Finished editing files.  Write the list of changed files to stdout.
   173		list := make([]string, len(changed))
   174		i := 0
   175		for f := range changed {
   176			list[i] = f
   177			i++
   178		}
   179		sort.Strings(list)
   180		for _, f := range list {
   181			fmt.Printf("%s\n", f)
   182		}
   183	}
   184	
   185	// make parent directory for name, if necessary
   186	func makeParent(name string) {
   187		parent, _ := filepath.Split(name)
   188		chk(mkdirAll(parent, 0755))
   189	}
   190	
   191	// Copy of os.MkdirAll but adds to undo log after
   192	// creating a directory.
   193	func mkdirAll(path string, perm uint32) os.Error {
   194		dir, err := os.Lstat(path)
   195		if err == nil {
   196			if dir.IsDirectory() {
   197				return nil
   198			}
   199			return &os.PathError{"mkdir", path, os.ENOTDIR}
   200		}
   201	
   202		i := len(path)
   203		for i > 0 && path[i-1] == '/' { // Skip trailing slashes.
   204			i--
   205		}
   206	
   207		j := i
   208		for j > 0 && path[j-1] != '/' { // Scan backward over element.
   209			j--
   210		}
   211	
   212		if j > 0 {
   213			err = mkdirAll(path[0:j-1], perm)
   214			if err != nil {
   215				return err
   216			}
   217		}
   218	
   219		err = os.Mkdir(path, perm)
   220		if err != nil {
   221			// Handle arguments like "foo/." by
   222			// double-checking that directory doesn't exist.
   223			dir, err1 := os.Lstat(path)
   224			if err1 == nil && dir.IsDirectory() {
   225				return nil
   226			}
   227			return err
   228		}
   229		undoRm(path)
   230		return nil
   231	}
   232	
   233	// If err != nil, process the undo log and exit.
   234	func chk(err os.Error) {
   235		if err != nil {
   236			fmt.Fprintf(os.Stderr, "%s\n", err)
   237			runUndo()
   238			os.Exit(2)
   239		}
   240	}
   241	
   242	// Undo log
   243	type undo func() os.Error
   244	
   245	var undoLog vector.Vector // vector of undo
   246	
   247	func undoRevert(name string) { undoLog.Push(undo(func() os.Error { return hgRevert(name) })) }
   248	
   249	func undoRm(name string) { undoLog.Push(undo(func() os.Error { return os.Remove(name) })) }
   250	
   251	func runUndo() {
   252		for i := undoLog.Len() - 1; i >= 0; i-- {
   253			if err := undoLog.At(i).(undo)(); err != nil {
   254				fmt.Fprintf(os.Stderr, "%s\n", err)
   255			}
   256		}
   257	}
   258	
   259	// hgRoot returns the root directory of the repository.
   260	func hgRoot() (string, os.Error) {
   261		out, err := run([]string{"hg", "root"}, nil)
   262		if err != nil {
   263			return "", err
   264		}
   265		return strings.TrimSpace(out), nil
   266	}
   267	
   268	// hgIncoming returns true if hg sync will pull in changes.
   269	func hgIncoming() bool {
   270		// hg -q incoming exits 0 when there is nothing incoming, 1 otherwise.
   271		_, err := run([]string{"hg", "-q", "incoming"}, nil)
   272		return err == nil
   273	}
   274	
   275	// hgModified returns a list of the modified files in the
   276	// repository.
   277	func hgModified() ([]string, os.Error) {
   278		out, err := run([]string{"hg", "status", "-n"}, nil)
   279		if err != nil {
   280			return nil, err
   281		}
   282		return strings.Split(strings.TrimSpace(out), "\n"), nil
   283	}
   284	
   285	// hgAdd adds name to the repository.
   286	func hgAdd(name string) os.Error {
   287		_, err := run([]string{"hg", "add", name}, nil)
   288		return err
   289	}
   290	
   291	// hgRemove removes name from the repository.
   292	func hgRemove(name string) os.Error {
   293		_, err := run([]string{"hg", "rm", name}, nil)
   294		return err
   295	}
   296	
   297	// hgRevert reverts name.
   298	func hgRevert(name string) os.Error {
   299		_, err := run([]string{"hg", "revert", name}, nil)
   300		return err
   301	}
   302	
   303	// hgCopy copies src to dst in the repository.
   304	// Note that the argument order matches io.Copy, not "hg cp".
   305	func hgCopy(dst, src string) os.Error {
   306		_, err := run([]string{"hg", "cp", src, dst}, nil)
   307		return err
   308	}
   309	
   310	// hgRename renames src to dst in the repository.
   311	// Note that the argument order matches io.Copy, not "hg mv".
   312	func hgRename(dst, src string) os.Error {
   313		_, err := run([]string{"hg", "mv", src, dst}, nil)
   314		return err
   315	}
   316	
   317	func dup(a []string) []string {
   318		b := make([]string, len(a))
   319		copy(b, a)
   320		return b
   321	}
   322	
   323	var lookPathCache = make(map[string]string)
   324	
   325	// run runs the command argv, resolving argv[0] if necessary by searching $PATH.
   326	// It provides input on standard input to the command.
   327	func run(argv []string, input []byte) (out string, err os.Error) {
   328		if len(argv) < 1 {
   329			return "", &runError{dup(argv), os.EINVAL}
   330		}
   331	
   332		prog, ok := lookPathCache[argv[0]]
   333		if !ok {
   334			prog, err = exec.LookPath(argv[0])
   335			if err != nil {
   336				return "", &runError{dup(argv), err}
   337			}
   338			lookPathCache[argv[0]] = prog
   339		}
   340	
   341		cmd := exec.Command(prog, argv[1:]...)
   342		if len(input) > 0 {
   343			cmd.Stdin = bytes.NewBuffer(input)
   344		}
   345		bs, err := cmd.CombinedOutput()
   346		if err != nil {
   347			return "", &runError{dup(argv), err}
   348		}
   349		return string(bs), nil
   350	}
   351	
   352	// A runError represents an error that occurred while running a command.
   353	type runError struct {
   354		cmd []string
   355		err os.Error
   356	}
   357	
   358	func (e *runError) String() string { return strings.Join(e.cmd, " ") + ": " + e.err.String() }

release.r60.3. Except as noted, this content is licensed under a Creative Commons Attribution 3.0 License.