github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/misc/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 "encoding/xml" 9 "fmt" 10 "log" 11 "os" 12 "path/filepath" 13 "strconv" 14 "strings" 15 "sync" 16 ) 17 18 // Repo represents a mercurial repository. 19 type Repo struct { 20 Path string 21 sync.Mutex 22 } 23 24 // RemoteRepo constructs a *Repo representing a remote repository. 25 func RemoteRepo(url string) *Repo { 26 return &Repo{ 27 Path: url, 28 } 29 } 30 31 // Clone clones the current Repo to a new destination 32 // returning a new *Repo if successful. 33 func (r *Repo) Clone(path, rev string) (*Repo, error) { 34 r.Lock() 35 defer r.Unlock() 36 if err := run(*cmdTimeout, nil, *buildroot, r.hgCmd("clone", "-r", rev, r.Path, path)...); err != nil { 37 return nil, err 38 } 39 return &Repo{ 40 Path: path, 41 }, nil 42 } 43 44 // UpdateTo updates the working copy of this Repo to the 45 // supplied revision. 46 func (r *Repo) UpdateTo(hash string) error { 47 r.Lock() 48 defer r.Unlock() 49 return run(*cmdTimeout, nil, r.Path, r.hgCmd("update", hash)...) 50 } 51 52 // Exists reports whether this Repo represents a valid Mecurial repository. 53 func (r *Repo) Exists() bool { 54 fi, err := os.Stat(filepath.Join(r.Path, ".hg")) 55 if err != nil { 56 return false 57 } 58 return fi.IsDir() 59 } 60 61 // Pull pulls changes from the default path, that is, the path 62 // this Repo was cloned from. 63 func (r *Repo) Pull() error { 64 r.Lock() 65 defer r.Unlock() 66 return run(*cmdTimeout, nil, r.Path, r.hgCmd("pull")...) 67 } 68 69 // Log returns the changelog for this repository. 70 func (r *Repo) Log() ([]HgLog, error) { 71 if err := r.Pull(); err != nil { 72 return nil, err 73 } 74 const N = 50 // how many revisions to grab 75 76 r.Lock() 77 defer r.Unlock() 78 data, _, err := runLog(*cmdTimeout, nil, r.Path, r.hgCmd("log", 79 "--encoding=utf-8", 80 "--limit="+strconv.Itoa(N), 81 "--template="+xmlLogTemplate)..., 82 ) 83 if err != nil { 84 return nil, err 85 } 86 87 var logStruct struct { 88 Log []HgLog 89 } 90 err = xml.Unmarshal([]byte("<Top>"+data+"</Top>"), &logStruct) 91 if err != nil { 92 log.Printf("unmarshal hg log: %v", err) 93 return nil, err 94 } 95 return logStruct.Log, nil 96 } 97 98 // FullHash returns the full hash for the given Mercurial revision. 99 func (r *Repo) FullHash(rev string) (string, error) { 100 r.Lock() 101 defer r.Unlock() 102 s, _, err := runLog(*cmdTimeout, nil, r.Path, 103 r.hgCmd("log", 104 "--encoding=utf-8", 105 "--rev="+rev, 106 "--limit=1", 107 "--template={node}")..., 108 ) 109 if err != nil { 110 return "", nil 111 } 112 s = strings.TrimSpace(s) 113 if s == "" { 114 return "", fmt.Errorf("cannot find revision") 115 } 116 if len(s) != 40 { 117 return "", fmt.Errorf("hg returned invalid hash " + s) 118 } 119 return s, nil 120 } 121 122 func (r *Repo) hgCmd(args ...string) []string { 123 return append([]string{"hg", "--config", "extensions.codereview=!"}, args...) 124 } 125 126 // HgLog represents a single Mercurial revision. 127 type HgLog struct { 128 Hash string 129 Author string 130 Date string 131 Desc string 132 Parent string 133 134 // Internal metadata 135 added bool 136 } 137 138 // xmlLogTemplate is a template to pass to Mercurial to make 139 // hg log print the log in valid XML for parsing with xml.Unmarshal. 140 const xmlLogTemplate = ` 141 <Log> 142 <Hash>{node|escape}</Hash> 143 <Parent>{parent|escape}</Parent> 144 <Author>{author|escape}</Author> 145 <Date>{date|rfc3339date}</Date> 146 <Desc>{desc|escape}</Desc> 147 </Log> 148 `