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