github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/dashboard/builder/vcs.go (about) 1 // Copyright 2013 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 "encoding/xml" 10 "fmt" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "strings" 15 "sync" 16 17 "golang.org/x/tools/go/vcs" 18 ) 19 20 // Repo represents a mercurial repository. 21 type Repo struct { 22 Path string 23 Master *vcs.RepoRoot 24 sync.Mutex 25 } 26 27 // RemoteRepo constructs a *Repo representing a remote repository. 28 func RemoteRepo(url, path string) (*Repo, error) { 29 rr, err := vcs.RepoRootForImportPath(url, *verbose) 30 if err != nil { 31 return nil, err 32 } 33 return &Repo{ 34 Path: path, 35 Master: rr, 36 }, nil 37 } 38 39 // Clone clones the current Repo to a new destination 40 // returning a new *Repo if successful. 41 func (r *Repo) Clone(path, rev string) (*Repo, error) { 42 r.Lock() 43 defer r.Unlock() 44 45 err := timeout(*cmdTimeout, func() error { 46 downloadPath := r.Path 47 if !r.Exists() { 48 downloadPath = r.Master.Repo 49 } 50 if rev == "" { 51 return r.Master.VCS.Create(path, downloadPath) 52 } 53 return r.Master.VCS.CreateAtRev(path, downloadPath, rev) 54 }) 55 if err != nil { 56 return nil, err 57 } 58 return &Repo{ 59 Path: path, 60 Master: r.Master, 61 }, nil 62 } 63 64 // Export exports the current Repo at revision rev to a new destination. 65 func (r *Repo) Export(path, rev string) error { 66 r.Lock() 67 68 downloadPath := r.Path 69 if !r.Exists() { 70 r.Unlock() 71 _, err := r.Clone(path, rev) 72 return err 73 } 74 75 switch r.Master.VCS.Cmd { 76 default: 77 r.Unlock() 78 // TODO(adg,cmang): implement Export in go/vcs 79 _, err := r.Clone(path, rev) 80 return err 81 case "hg": 82 defer r.Unlock() 83 cmd := exec.Command(r.Master.VCS.Cmd, "archive", "-t", "files", "-r", rev, path) 84 cmd.Dir = downloadPath 85 if err := run(cmd); err != nil { 86 return fmt.Errorf("executing %v: %v", cmd.Args, err) 87 } 88 } 89 return nil 90 } 91 92 // UpdateTo updates the working copy of this Repo to the 93 // supplied revision. 94 func (r *Repo) UpdateTo(hash string) error { 95 r.Lock() 96 defer r.Unlock() 97 98 if r.Master.VCS.Cmd == "git" { 99 cmd := exec.Command("git", "reset", "--hard", hash) 100 var log bytes.Buffer 101 err := run(cmd, runTimeout(*cmdTimeout), runDir(r.Path), allOutput(&log)) 102 if err != nil { 103 return fmt.Errorf("Error running git update -C %v: %v ; output=%s", hash, err, log.Bytes()) 104 } 105 return nil 106 } 107 108 // Else go down three more levels of abstractions, at 109 // least two of which are broken for git. 110 return timeout(*cmdTimeout, func() error { 111 return r.Master.VCS.TagSync(r.Path, hash) 112 }) 113 } 114 115 // Exists reports whether this Repo represents a valid Mecurial repository. 116 func (r *Repo) Exists() bool { 117 fi, err := os.Stat(filepath.Join(r.Path, "."+r.Master.VCS.Cmd)) 118 if err != nil { 119 return false 120 } 121 return fi.IsDir() 122 } 123 124 // Pull pulls changes from the default path, that is, the path 125 // this Repo was cloned from. 126 func (r *Repo) Pull() error { 127 r.Lock() 128 defer r.Unlock() 129 130 return timeout(*cmdTimeout, func() error { 131 return r.Master.VCS.Download(r.Path) 132 }) 133 } 134 135 // Log returns the changelog for this repository. 136 func (r *Repo) Log() ([]HgLog, error) { 137 if err := r.Pull(); err != nil { 138 return nil, err 139 } 140 r.Lock() 141 defer r.Unlock() 142 143 var logStruct struct { 144 Log []HgLog 145 } 146 err := timeout(*cmdTimeout, func() error { 147 data, err := r.Master.VCS.Log(r.Path, xmlLogTemplate) 148 if err != nil { 149 return err 150 } 151 152 // We have a commit with description that contains 0x1b byte. 153 // Mercurial does not escape it, but xml.Unmarshal does not accept it. 154 data = bytes.Replace(data, []byte{0x1b}, []byte{'?'}, -1) 155 156 err = xml.Unmarshal([]byte("<Top>"+string(data)+"</Top>"), &logStruct) 157 if err != nil { 158 return fmt.Errorf("unmarshal %s log: %v", r.Master.VCS, err) 159 } 160 return nil 161 }) 162 if err != nil { 163 return nil, err 164 } 165 for i, log := range logStruct.Log { 166 // Let's pretend there can be only one parent. 167 if log.Parent != "" && strings.Contains(log.Parent, " ") { 168 logStruct.Log[i].Parent = strings.Split(log.Parent, " ")[0] 169 } 170 } 171 return logStruct.Log, nil 172 } 173 174 // FullHash returns the full hash for the given Git or Mercurial revision. 175 func (r *Repo) FullHash(rev string) (string, error) { 176 r.Lock() 177 defer r.Unlock() 178 179 var hash string 180 err := timeout(*cmdTimeout, func() error { 181 var data []byte 182 // Avoid the vcs package for git, since it's broken 183 // for git, and and we're trying to remove levels of 184 // abstraction which are increasingly getting 185 // difficult to navigate. 186 if r.Master.VCS.Cmd == "git" { 187 cmd := exec.Command("git", "rev-parse", rev) 188 var out bytes.Buffer 189 err := run(cmd, runTimeout(*cmdTimeout), runDir(r.Path), allOutput(&out)) 190 data = out.Bytes() 191 if err != nil { 192 return fmt.Errorf("Failed to find FullHash of %q; git rev-parse: %v, %s", rev, err, data) 193 } 194 } else { 195 var err error 196 data, err = r.Master.VCS.LogAtRev(r.Path, rev, "{node}") 197 if err != nil { 198 return err 199 } 200 } 201 s := strings.TrimSpace(string(data)) 202 if s == "" { 203 return fmt.Errorf("cannot find revision") 204 } 205 if len(s) != 40 { // correct for both hg and git 206 return fmt.Errorf("%s returned invalid hash: %s", r.Master.VCS, s) 207 } 208 hash = s 209 return nil 210 }) 211 if err != nil { 212 return "", err 213 } 214 return hash, nil 215 } 216 217 // HgLog represents a single Mercurial revision. 218 type HgLog struct { 219 Hash string 220 Author string 221 Date string 222 Desc string 223 Parent string 224 Branch string 225 Files string 226 227 // Internal metadata 228 added bool 229 bench bool // needs to be benchmarked? 230 } 231 232 // xmlLogTemplate is a template to pass to Mercurial to make 233 // hg log print the log in valid XML for parsing with xml.Unmarshal. 234 // Can not escape branches and files, because it crashes python with: 235 // AttributeError: 'NoneType' object has no attribute 'replace' 236 const xmlLogTemplate = ` 237 <Log> 238 <Hash>{node|escape}</Hash> 239 <Parent>{p1node}</Parent> 240 <Author>{author|escape}</Author> 241 <Date>{date|rfc3339date}</Date> 242 <Desc>{desc|escape}</Desc> 243 <Branch>{branches}</Branch> 244 <Files>{files}</Files> 245 </Log> 246 `