-
Notifications
You must be signed in to change notification settings - Fork 11
/
vcs.go
162 lines (139 loc) · 3.91 KB
/
vcs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package main
import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"
"github.com/jingweno/nut/vendor/_nuts/golang.org/x/tools/go/vcs"
)
var vcsGit = &VCS{
Cmd: vcs.ByCmd("git"),
IdentifyCmd: "rev-parse HEAD",
}
var vcsHg = &VCS{
Cmd: vcs.ByCmd("hg"),
IdentifyCmd: "identify --id --debug",
}
var vcsBzr = &VCS{
Cmd: vcs.ByCmd("bzr"),
IdentifyCmd: "version-info --custom --template {revision_id}",
}
var cmd = map[*vcs.Cmd]*VCS{
vcsBzr.Cmd: vcsBzr,
vcsGit.Cmd: vcsGit,
vcsHg.Cmd: vcsHg,
}
func VCSForImportPath(importPath string) (*vcs.RepoRoot, *VCS, error) {
rr, err := vcs.RepoRootForImportPath(importPath, false)
if err != nil {
return nil, nil, err
}
vcs := cmd[rr.VCS]
if vcs == nil {
return nil, nil, fmt.Errorf("%s is unsupported: %s", rr.VCS.Name, importPath)
}
return rr, vcs, nil
}
func VCSFromDir(dir, srcRoot string) (*VCS, string, error) {
vcscmd, reporoot, err := vcs.FromDir(dir, srcRoot)
if err != nil {
return nil, "", err
}
vcsext := cmd[vcscmd]
if vcsext == nil {
return nil, "", fmt.Errorf("%s is unsupported: %s", vcscmd.Name, dir)
}
return vcsext, reporoot, nil
}
type VCS struct {
*vcs.Cmd
IdentifyCmd string
}
func (v *VCS) Identify(dir string) (string, error) {
out, err := v.runOutput(dir, v.IdentifyCmd)
return string(bytes.TrimSpace(out)), err
}
// run runs the command line cmd in the given directory.
// keyval is a list of key, value pairs. run expands
// instances of {key} in cmd into value, but only after
// splitting cmd into individual arguments.
// If an error occurs, run prints the command line and the
// command's combined stdout+stderr to standard error.
// Otherwise run discards the command's output.
func (v *VCS) run(dir string, cmd string, keyval ...string) error {
_, err := v.run1(dir, cmd, keyval, true)
return err
}
// runOutput is like run but returns the output of the command.
func (v *VCS) runOutput(dir string, cmdline string, kv ...string) ([]byte, error) {
return v.run1(dir, cmdline, kv, true)
}
// run1 is the generalized implementation of run and runOutput.
func (v *VCS) run1(dir string, cmdline string, keyval []string, verbose bool) ([]byte, error) {
m := make(map[string]string)
for i := 0; i < len(keyval); i += 2 {
m[keyval[i]] = keyval[i+1]
}
args := strings.Fields(cmdline)
for i, arg := range args {
args[i] = expand(m, arg)
}
_, err := exec.LookPath(v.Cmd.Cmd)
if err != nil {
fmt.Fprintf(os.Stderr,
"go: missing %s command. See http://golang.org/s/gogetcmd\n",
v.Name)
return nil, err
}
cmd := exec.Command(v.Cmd.Cmd, args...)
cmd.Dir = dir
cmd.Env = envForDir(cmd.Dir)
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf
err = cmd.Run()
out := buf.Bytes()
if err != nil {
if verbose {
fmt.Fprintf(os.Stderr, "# cd %s; %s %s\n", dir, v.Cmd, strings.Join(args, " "))
os.Stderr.Write(out)
}
return nil, err
}
return out, nil
}
// expand rewrites s to replace {k} with match[k] for each key k in match.
func expand(match map[string]string, s string) string {
for k, v := range match {
s = strings.Replace(s, "{"+k+"}", v, -1)
}
return s
}
// envForDir returns a copy of the environment
// suitable for running in the given directory.
// The environment is the current process's environment
// but with an updated $PWD, so that an os.Getwd in the
// child will be faster.
func envForDir(dir string) []string {
env := os.Environ()
// Internally we only use rooted paths, so dir is rooted.
// Even if dir is not rooted, no harm done.
return mergeEnvLists([]string{"PWD=" + dir}, env)
}
// mergeEnvLists merges the two environment lists such that
// variables with the same name in "in" replace those in "out".
func mergeEnvLists(in, out []string) []string {
NextVar:
for _, inkv := range in {
k := strings.SplitAfterN(inkv, "=", 2)[0]
for i, outkv := range out {
if strings.HasPrefix(outkv, k) {
out[i] = inkv
continue NextVar
}
}
out = append(out, inkv)
}
return out
}