gopkg.in/zumata/godep.v14@v14.0.0-20151008182512-99082d62f381/vcs.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "strings" 10 11 "github.com/tools/godep/Godeps/_workspace/src/golang.org/x/tools/go/vcs" 12 ) 13 14 // VCS represents a version control system. 15 type VCS struct { 16 vcs *vcs.Cmd 17 18 IdentifyCmd string 19 DescribeCmd string 20 DiffCmd string 21 ListCmd string 22 23 // run in sandbox repos 24 ExistsCmd string 25 } 26 27 var vcsBzr = &VCS{ 28 vcs: vcs.ByCmd("bzr"), 29 30 IdentifyCmd: "version-info --custom --template {revision_id}", 31 DescribeCmd: "revno", // TODO(kr): find tag names if possible 32 DiffCmd: "diff -r {rev}", 33 ListCmd: "ls -R", 34 } 35 36 var vcsGit = &VCS{ 37 vcs: vcs.ByCmd("git"), 38 39 IdentifyCmd: "rev-parse HEAD", 40 DescribeCmd: "describe --tags", 41 DiffCmd: "diff {rev}", 42 ListCmd: "ls-files", 43 44 ExistsCmd: "cat-file -e {rev}", 45 } 46 47 var vcsHg = &VCS{ 48 vcs: vcs.ByCmd("hg"), 49 50 IdentifyCmd: "identify --id --debug", 51 DescribeCmd: "log -r . --template {latesttag}-{latesttagdistance}", 52 DiffCmd: "diff -r {rev}", 53 ListCmd: "status --all --no-status", 54 55 ExistsCmd: "cat -r {rev} .", 56 } 57 58 var cmd = map[*vcs.Cmd]*VCS{ 59 vcsBzr.vcs: vcsBzr, 60 vcsGit.vcs: vcsGit, 61 vcsHg.vcs: vcsHg, 62 } 63 64 // VCSFromDir returns a VCS value from a directory. 65 func VCSFromDir(dir, srcRoot string) (*VCS, string, error) { 66 vcscmd, reporoot, err := vcs.FromDir(dir, srcRoot) 67 if err != nil { 68 return nil, "", fmt.Errorf("error while inspecting %q: %v", dir, err) 69 } 70 vcsext := cmd[vcscmd] 71 if vcsext == nil { 72 return nil, "", fmt.Errorf("%s is unsupported: %s", vcscmd.Name, dir) 73 } 74 return vcsext, reporoot, nil 75 } 76 77 // VCSForImportPath returns a VCS value for an import path. 78 func VCSForImportPath(importPath string) (*VCS, error) { 79 rr, err := vcs.RepoRootForImportPath(importPath, verbose) 80 if err != nil { 81 return nil, err 82 } 83 vcs := cmd[rr.VCS] 84 if vcs == nil { 85 return nil, fmt.Errorf("%s is unsupported: %s", rr.VCS.Name, importPath) 86 } 87 return vcs, nil 88 } 89 90 func (v *VCS) identify(dir string) (string, error) { 91 out, err := v.runOutput(dir, v.IdentifyCmd) 92 return string(bytes.TrimSpace(out)), err 93 } 94 95 func (v *VCS) describe(dir, rev string) string { 96 out, err := v.runOutputVerboseOnly(dir, v.DescribeCmd, "rev", rev) 97 if err != nil { 98 return "" 99 } 100 return string(bytes.TrimSpace(out)) 101 } 102 103 func (v *VCS) isDirty(dir, rev string) bool { 104 out, err := v.runOutput(dir, v.DiffCmd, "rev", rev) 105 return err != nil || len(out) != 0 106 } 107 108 type vcsFiles map[string]bool 109 110 func (vf vcsFiles) Contains(path string) bool { 111 return vf[path] 112 } 113 114 // listFiles tracked by the VCS in the directory dir, converted to absolute path 115 func (v *VCS) listFiles(dir string) vcsFiles { 116 out, err := v.runOutput(dir, v.ListCmd) 117 if err != nil { 118 return nil 119 } 120 files := make(vcsFiles) 121 for _, file := range bytes.Split(out, []byte{'\n'}) { 122 if len(file) > 0 { 123 path, err := filepath.Abs(filepath.Join(dir, string(file))) 124 if err != nil { 125 panic(err) // this should not happen 126 } 127 files[path] = true 128 } 129 } 130 return files 131 } 132 133 func (v *VCS) exists(dir, rev string) bool { 134 err := v.runVerboseOnly(dir, v.ExistsCmd, "rev", rev) 135 return err == nil 136 } 137 138 // RevSync checks out the revision given by rev in dir. 139 // The dir must exist and rev must be a valid revision. 140 func (v *VCS) RevSync(dir, rev string) error { 141 return v.run(dir, v.vcs.TagSyncCmd, "tag", rev) 142 } 143 144 // run runs the command line cmd in the given directory. 145 // keyval is a list of key, value pairs. run expands 146 // instances of {key} in cmd into value, but only after 147 // splitting cmd into individual arguments. 148 // If an error occurs, run prints the command line and the 149 // command's combined stdout+stderr to standard error. 150 // Otherwise run discards the command's output. 151 func (v *VCS) run(dir string, cmdline string, kv ...string) error { 152 _, err := v.run1(dir, cmdline, kv, true) 153 return err 154 } 155 156 // runVerboseOnly is like run but only generates error output to standard error in verbose mode. 157 func (v *VCS) runVerboseOnly(dir string, cmdline string, kv ...string) error { 158 _, err := v.run1(dir, cmdline, kv, false) 159 return err 160 } 161 162 // runOutput is like run but returns the output of the command. 163 func (v *VCS) runOutput(dir string, cmdline string, kv ...string) ([]byte, error) { 164 return v.run1(dir, cmdline, kv, true) 165 } 166 167 // runOutputVerboseOnly is like runOutput but only generates error output to standard error in verbose mode. 168 func (v *VCS) runOutputVerboseOnly(dir string, cmdline string, kv ...string) ([]byte, error) { 169 return v.run1(dir, cmdline, kv, false) 170 } 171 172 // run1 is the generalized implementation of run and runOutput. 173 func (v *VCS) run1(dir string, cmdline string, kv []string, verbose bool) ([]byte, error) { 174 m := make(map[string]string) 175 for i := 0; i < len(kv); i += 2 { 176 m[kv[i]] = kv[i+1] 177 } 178 args := strings.Fields(cmdline) 179 for i, arg := range args { 180 args[i] = expand(m, arg) 181 } 182 183 _, err := exec.LookPath(v.vcs.Cmd) 184 if err != nil { 185 fmt.Fprintf(os.Stderr, "godep: missing %s command.\n", v.vcs.Name) 186 return nil, err 187 } 188 189 cmd := exec.Command(v.vcs.Cmd, args...) 190 cmd.Dir = dir 191 var buf bytes.Buffer 192 cmd.Stdout = &buf 193 cmd.Stderr = &buf 194 err = cmd.Run() 195 out := buf.Bytes() 196 if err != nil { 197 if verbose { 198 fmt.Fprintf(os.Stderr, "# cd %s; %s %s\n", dir, v.vcs.Cmd, strings.Join(args, " ")) 199 os.Stderr.Write(out) 200 } 201 return nil, err 202 } 203 return out, nil 204 } 205 206 func expand(m map[string]string, s string) string { 207 for k, v := range m { 208 s = strings.Replace(s, "{"+k+"}", v, -1) 209 } 210 return s 211 } 212 213 // Mercurial has no command equivalent to git remote add. 214 // We handle it as a special case in process. 215 func hgLink(dir, remote, url string) error { 216 hgdir := filepath.Join(dir, ".hg") 217 if err := os.MkdirAll(hgdir, 0777); err != nil { 218 return err 219 } 220 path := filepath.Join(hgdir, "hgrc") 221 f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) 222 if err != nil { 223 return err 224 } 225 fmt.Fprintf(f, "[paths]\n%s = %s\n", remote, url) 226 return f.Close() 227 }