LEFT | RIGHT |
1 // Copyright 2013 The Go Authors. All rights reserved. | 1 // Copyright 2013 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 package main | 5 package main |
6 | 6 |
7 import ( | 7 import ( |
8 "encoding/xml" | 8 "encoding/xml" |
9 "fmt" | 9 "fmt" |
10 "log" | 10 "log" |
11 "os" | 11 "os" |
12 "path/filepath" | 12 "path/filepath" |
13 "strconv" | 13 "strconv" |
14 "strings" | 14 "strings" |
15 "sync" | 15 "sync" |
16 ) | 16 ) |
17 | 17 |
18 // Repo represents a mercurial repository | 18 // Repo represents a mercurial repository. |
19 type Repo struct { | 19 type Repo struct { |
20 Path string | 20 Path string |
21 sync.Mutex | 21 sync.Mutex |
22 } | 22 } |
23 | 23 |
24 // CloneTo clones the current Repo to a new destination | 24 // RemoteRepo constructs a *Repo representing a remote repository. |
25 // returning a new *Repo if successful | 25 func RemoteRepo(url string) *Repo { |
26 func (r *Repo) CloneTo(path string) (*Repo, error) { | 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) { |
27 r.Lock() | 34 r.Lock() |
28 defer r.Unlock() | 35 defer r.Unlock() |
29 » if err := run(*cmdTimeout, nil, *buildroot, hgCmd("clone", r.Path, path)
...); err != nil { | 36 » if err := run(*cmdTimeout, nil, *buildroot, r.hgCmd("clone", "-r", rev,
r.Path, path)...); err != nil { |
30 return nil, err | 37 return nil, err |
31 } | 38 } |
32 return &Repo{ | 39 return &Repo{ |
33 Path: path, | 40 Path: path, |
34 }, nil | 41 }, nil |
35 } | 42 } |
36 | 43 |
37 // UpdateTo updates the working copy of this Repo to the | 44 // UpdateTo updates the working copy of this Repo to the |
38 // supplied revision. | 45 // supplied revision. |
39 func (r *Repo) UpdateTo(hash string) error { | 46 func (r *Repo) UpdateTo(hash string) error { |
40 r.Lock() | 47 r.Lock() |
41 defer r.Unlock() | 48 defer r.Unlock() |
42 » return run(*cmdTimeout, nil, r.Path, hgCmd("update", hash)...) | 49 » return run(*cmdTimeout, nil, r.Path, r.hgCmd("update", hash)...) |
43 } | 50 } |
44 | 51 |
45 // Exists reports whether this Repo represents a valid Mecurial repository. | 52 // Exists reports whether this Repo represents a valid Mecurial repository. |
46 func (r *Repo) Exists() bool { | 53 func (r *Repo) Exists() bool { |
47 // TODO(dfc) probably don't need to lock here | |
48 fi, err := os.Stat(filepath.Join(r.Path, ".hg")) | 54 fi, err := os.Stat(filepath.Join(r.Path, ".hg")) |
49 if err != nil { | 55 if err != nil { |
50 return false | 56 return false |
51 } | 57 } |
52 return fi.IsDir() | 58 return fi.IsDir() |
53 } | 59 } |
54 | 60 |
55 // Pull pulls changes from the default path, that is, the path | 61 // Pull pulls changes from the default path, that is, the path |
56 // this Repo was cloned from. | 62 // this Repo was cloned from. |
57 func (r *Repo) Pull() error { | 63 func (r *Repo) Pull() error { |
58 r.Lock() | 64 r.Lock() |
59 defer r.Unlock() | 65 defer r.Unlock() |
60 » return run(*cmdTimeout, nil, r.Path, hgCmd("pull")...) | 66 » return run(*cmdTimeout, nil, r.Path, r.hgCmd("pull")...) |
61 } | 67 } |
62 | 68 |
63 // Log returns the changelog for this repository. | 69 // Log returns the changelog for this repository. |
64 func (r *Repo) Log() ([]HgLog, error) { | 70 func (r *Repo) Log() ([]HgLog, error) { |
| 71 if err := r.Pull(); err != nil { |
| 72 return nil, err |
| 73 } |
65 const N = 50 // how many revisions to grab | 74 const N = 50 // how many revisions to grab |
66 | 75 |
67 r.Lock() | 76 r.Lock() |
68 defer r.Unlock() | 77 defer r.Unlock() |
69 » data, _, err := runLog(*cmdTimeout, nil, r.Path, hgCmd("log", | 78 » data, _, err := runLog(*cmdTimeout, nil, r.Path, r.hgCmd("log", |
70 "--encoding=utf-8", | 79 "--encoding=utf-8", |
71 "--limit="+strconv.Itoa(N), | 80 "--limit="+strconv.Itoa(N), |
72 "--template="+xmlLogTemplate)..., | 81 "--template="+xmlLogTemplate)..., |
73 ) | 82 ) |
74 if err != nil { | 83 if err != nil { |
75 return nil, err | 84 return nil, err |
76 } | 85 } |
77 | 86 |
78 var logStruct struct { | 87 var logStruct struct { |
79 Log []HgLog | 88 Log []HgLog |
80 } | 89 } |
81 err = xml.Unmarshal([]byte("<Top>"+data+"</Top>"), &logStruct) | 90 err = xml.Unmarshal([]byte("<Top>"+data+"</Top>"), &logStruct) |
82 if err != nil { | 91 if err != nil { |
83 log.Printf("unmarshal hg log: %v", err) | 92 log.Printf("unmarshal hg log: %v", err) |
84 return nil, err | 93 return nil, err |
85 } | 94 } |
86 return logStruct.Log, nil | 95 return logStruct.Log, nil |
87 } | 96 } |
88 | 97 |
89 // FullHash returns the full hash for the given Mercurial revision. | 98 // FullHash returns the full hash for the given Mercurial revision. |
90 func (r *Repo) FullHash(rev string) (string, error) { | 99 func (r *Repo) FullHash(rev string) (string, error) { |
91 r.Lock() | 100 r.Lock() |
92 defer r.Unlock() | 101 defer r.Unlock() |
93 s, _, err := runLog(*cmdTimeout, nil, r.Path, | 102 s, _, err := runLog(*cmdTimeout, nil, r.Path, |
94 » » hgCmd("log", | 103 » » r.hgCmd("log", |
95 "--encoding=utf-8", | 104 "--encoding=utf-8", |
96 "--rev="+rev, | 105 "--rev="+rev, |
97 "--limit=1", | 106 "--limit=1", |
98 "--template={node}")..., | 107 "--template={node}")..., |
99 ) | 108 ) |
100 if err != nil { | 109 if err != nil { |
101 return "", nil | 110 return "", nil |
102 } | 111 } |
103 s = strings.TrimSpace(s) | 112 s = strings.TrimSpace(s) |
104 if s == "" { | 113 if s == "" { |
105 return "", fmt.Errorf("cannot find revision") | 114 return "", fmt.Errorf("cannot find revision") |
106 } | 115 } |
107 if len(s) != 40 { | 116 if len(s) != 40 { |
108 return "", fmt.Errorf("hg returned invalid hash " + s) | 117 return "", fmt.Errorf("hg returned invalid hash " + s) |
109 } | 118 } |
110 return s, nil | 119 return s, nil |
111 } | 120 } |
| 121 |
| 122 func (r *Repo) hgCmd(args ...string) []string { |
| 123 return append([]string{"hg", "--config", "extensions.codereview=!"}, arg
s...) |
| 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 ` |
LEFT | RIGHT |