github.com/hwaf/hwaf@v0.0.0-20140814122253-5465f73b20f1/vcs/vcs.go (about)

     1  // Package vcs eases interactions with various Versioned Control Systems.
     2  //
     3  // This is mainly reaped off golang.org/src/cmd/go/vcs.go
     4  package vcs
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"os"
    10  	"os/exec"
    11  	//"path/filepath"
    12  	"regexp"
    13  	"strings"
    14  )
    15  
    16  // A Cmd describes how to use a version control system
    17  // like Mercurial, Git, or Subversion.
    18  type Cmd struct {
    19  	name    string
    20  	cmd     string // name of binary to invoke command
    21  	verbose bool   // printout what we do
    22  
    23  	createCmd   string // command to download a fresh copy of a repository
    24  	downloadCmd string // command to download updates into an existing repository
    25  
    26  	tagCmd         []tagCmd // commands to list tags
    27  	tagLookupCmd   []tagCmd // commands to lookup tags before running tagSyncCmd
    28  	tagSyncCmd     string   // command to sync to specific tag
    29  	tagSyncDefault string   // command to sync to default tag
    30  
    31  	scheme  []string
    32  	pingCmd string
    33  }
    34  
    35  // A tagCmd describes a command to list available tags
    36  // that can be passed to tagSyncCmd.
    37  type tagCmd struct {
    38  	cmd     string // command to list tags
    39  	pattern string // regexp to extract tags from list
    40  }
    41  
    42  // List lists the known version control systems
    43  var List = []*Cmd{
    44  	Hg,
    45  	Git,
    46  	Svn,
    47  	Bzr,
    48  }
    49  
    50  // ByCmd returns the version control system for the given
    51  // command name (hg, git, svn, bzr).
    52  func ByCmd(cmd string) *Cmd {
    53  	for _, vcs := range List {
    54  		if vcs.cmd == cmd {
    55  			return vcs
    56  		}
    57  	}
    58  	return nil
    59  }
    60  
    61  // Hg describes how to use Mercurial.
    62  var Hg = &Cmd{
    63  	name: "Mercurial",
    64  	cmd:  "hg",
    65  
    66  	createCmd:   "clone -U {repo} {dir}",
    67  	downloadCmd: "pull",
    68  
    69  	// We allow both tag and branch names as 'tags'
    70  	// for selecting a version.  This lets people have
    71  	// a go.release.r60 branch and a go1 branch
    72  	// and make changes in both, without constantly
    73  	// editing .hgtags.
    74  	tagCmd: []tagCmd{
    75  		{"tags", `^(\S+)`},
    76  		{"branches", `^(\S+)`},
    77  	},
    78  	tagSyncCmd:     "update -r {tag}",
    79  	tagSyncDefault: "update default",
    80  
    81  	scheme:  []string{"https", "http", "ssh"},
    82  	pingCmd: "identify {scheme}://{repo}",
    83  }
    84  
    85  // Git describes how to use Git.
    86  var Git = &Cmd{
    87  	name: "Git",
    88  	cmd:  "git",
    89  
    90  	createCmd:   "clone {repo} {dir}",
    91  	downloadCmd: "fetch",
    92  
    93  	tagCmd: []tagCmd{
    94  		// tags/xxx matches a git tag named xxx
    95  		// origin/xxx matches a git branch named xxx on the default remote repository
    96  		{"show-ref", `(?:tags|origin)/(\S+)$`},
    97  	},
    98  	tagLookupCmd: []tagCmd{
    99  		{"show-ref tags/{tag} origin/{tag}", `((?:tags|origin)/\S+)$`},
   100  	},
   101  	tagSyncCmd:     "checkout {tag}",
   102  	tagSyncDefault: "checkout origin/master",
   103  
   104  	scheme:  []string{"git", "https", "http", "git+ssh"},
   105  	pingCmd: "ls-remote {scheme}://{repo}",
   106  }
   107  
   108  // Bzr describes how to use Bazaar.
   109  var Bzr = &Cmd{
   110  	name: "Bazaar",
   111  	cmd:  "bzr",
   112  
   113  	createCmd: "branch {repo} {dir}",
   114  
   115  	// Without --overwrite bzr will not pull tags that changed.
   116  	// Replace by --overwrite-tags after http://pad.lv/681792 goes in.
   117  	downloadCmd: "pull --overwrite",
   118  
   119  	tagCmd:         []tagCmd{{"tags", `^(\S+)`}},
   120  	tagSyncCmd:     "update -r {tag}",
   121  	tagSyncDefault: "update -r revno:-1",
   122  
   123  	scheme:  []string{"https", "http", "bzr", "bzr+ssh"},
   124  	pingCmd: "info {scheme}://{repo}",
   125  }
   126  
   127  // Svn describes how to use Subversion.
   128  var Svn = &Cmd{
   129  	name: "Subversion",
   130  	cmd:  "svn",
   131  	//verbose: true,
   132  
   133  	createCmd:   "checkout {repo} {dir}",
   134  	downloadCmd: "update",
   135  
   136  	// There is no tag command in subversion.
   137  	// The branch information is all in the path names.
   138  
   139  	scheme:  []string{"https", "http", "svn", "svn+ssh"},
   140  	pingCmd: "info {scheme}://{repo}",
   141  }
   142  
   143  func (v *Cmd) String() string {
   144  	return v.name
   145  }
   146  
   147  // run runs the command line cmd in the given directory.
   148  // keyval is a list of key, value pairs.  run expands
   149  // instances of {key} in cmd into value, but only after
   150  // splitting cmd into individual arguments.
   151  // If an error occurs, run prints the command line and the
   152  // command's combined stdout+stderr to standard error.
   153  // Otherwise run discards the command's output.
   154  func (v *Cmd) run(dir string, cmd string, keyval ...string) error {
   155  	_, err := v.run1(dir, cmd, keyval, true)
   156  	return err
   157  }
   158  
   159  // runVerboseOnly is like run but only generates error output to standard error in verbose mode.
   160  func (v *Cmd) runVerboseOnly(dir string, cmd string, keyval ...string) error {
   161  	_, err := v.run1(dir, cmd, keyval, false)
   162  	return err
   163  }
   164  
   165  // runOutput is like run but returns the output of the command.
   166  func (v *Cmd) runOutput(dir string, cmd string, keyval ...string) ([]byte, error) {
   167  	return v.run1(dir, cmd, keyval, true)
   168  }
   169  
   170  // run1 is the generalized implementation of run and runOutput.
   171  func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([]byte, error) {
   172  	m := make(map[string]string)
   173  	for i := 0; i < len(keyval); i += 2 {
   174  		m[keyval[i]] = keyval[i+1]
   175  	}
   176  	args := strings.Fields(cmdline)
   177  	for i, arg := range args {
   178  		args[i] = expand(m, arg)
   179  	}
   180  
   181  	cmd := exec.Command(v.cmd, args...)
   182  	cmd.Dir = dir
   183  	if v.verbose {
   184  		fmt.Printf("cd %s\n", dir)
   185  		fmt.Printf("%s %s\n", v.cmd, strings.Join(args, " "))
   186  	}
   187  	var buf bytes.Buffer
   188  	cmd.Stdout = &buf
   189  	cmd.Stderr = &buf
   190  	err := cmd.Run()
   191  	out := buf.Bytes()
   192  	if err != nil {
   193  		if verbose || v.verbose {
   194  			fmt.Fprintf(os.Stderr, "# cd %s; %s %s\n", dir, v.cmd, strings.Join(args, " "))
   195  			os.Stderr.Write(out)
   196  		}
   197  		return nil, err
   198  	}
   199  	return out, nil
   200  }
   201  
   202  // Ping pings to determine scheme to use.
   203  func (v *Cmd) Ping(scheme, repo string) error {
   204  	return v.runVerboseOnly(".", v.pingCmd, "scheme", scheme, "repo", repo)
   205  }
   206  
   207  // Create creates a new copy of repo in dir.
   208  // The parent of dir must exist; dir must not.
   209  func (v *Cmd) Create(dir, repo string) error {
   210  	return v.run(".", v.createCmd, "dir", dir, "repo", repo)
   211  }
   212  
   213  // Download downloads any new changes for the repo in dir.
   214  func (v *Cmd) Download(dir string) error {
   215  	return v.run(dir, v.downloadCmd)
   216  }
   217  
   218  // Tags returns the list of available tags for the repo in dir.
   219  func (v *Cmd) Tags(dir string) ([]string, error) {
   220  	var tags []string
   221  	for _, tc := range v.tagCmd {
   222  		out, err := v.runOutput(dir, tc.cmd)
   223  		if err != nil {
   224  			return nil, err
   225  		}
   226  		re := regexp.MustCompile(`(?m-s)` + tc.pattern)
   227  		for _, m := range re.FindAllStringSubmatch(string(out), -1) {
   228  			tags = append(tags, m[1])
   229  		}
   230  	}
   231  	return tags, nil
   232  }
   233  
   234  // tagSync syncs the repo in dir to the named tag,
   235  // which either is a tag returned by tags or is v.tagDefault.
   236  func (v *Cmd) tagSync(dir, tag string) error {
   237  	if v.tagSyncCmd == "" {
   238  		return nil
   239  	}
   240  	if tag != "" {
   241  		for _, tc := range v.tagLookupCmd {
   242  			out, err := v.runOutput(dir, tc.cmd, "tag", tag)
   243  			if err != nil {
   244  				return err
   245  			}
   246  			re := regexp.MustCompile(`(?m-s)` + tc.pattern)
   247  			m := re.FindStringSubmatch(string(out))
   248  			if len(m) > 1 {
   249  				tag = m[1]
   250  				break
   251  			}
   252  		}
   253  	}
   254  	if tag == "" && v.tagSyncDefault != "" {
   255  		return v.run(dir, v.tagSyncDefault)
   256  	}
   257  	return v.run(dir, v.tagSyncCmd, "tag", tag)
   258  }
   259  
   260  // expand rewrites s to replace {k} with match[k] for each key k in match.
   261  func expand(match map[string]string, s string) string {
   262  	for k, v := range match {
   263  		s = strings.Replace(s, "{"+k+"}", v, -1)
   264  	}
   265  	return s
   266  }
   267  
   268  // EOF