OLD | NEW |
1 /* | 1 /* |
2 Copyright 2011 Google Inc | 2 Copyright 2011 Google Inc |
3 | 3 |
4 Licensed under the Apache License, Version 2.0 (the "License"); | 4 Licensed under the Apache License, Version 2.0 (the "License"); |
5 you may not use this file except in compliance with the License. | 5 you may not use this file except in compliance with the License. |
6 You may obtain a copy of the License at | 6 You may obtain a copy of the License at |
7 | 7 |
8 http://www.apache.org/licenses/LICENSE-2.0 | 8 http://www.apache.org/licenses/LICENSE-2.0 |
9 | 9 |
10 Unless required by applicable law or agreed to in writing, software | 10 Unless required by applicable law or agreed to in writing, software |
11 distributed under the License is distributed on an "AS IS" BASIS, | 11 distributed under the License is distributed on an "AS IS" BASIS, |
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 See the License for the specific language governing permissions and | 13 See the License for the specific language governing permissions and |
14 limitations under the License. | 14 limitations under the License. |
15 */ | 15 */ |
16 | 16 |
17 using System; | 17 using System; |
18 using System.Collections.Generic; | 18 using System.Collections.Generic; |
19 using System.Diagnostics; | 19 using System.Diagnostics; |
20 using System.IO; | 20 using System.IO; |
21 using System.Linq; | 21 using System.Linq; |
22 using System.Runtime.InteropServices; | 22 using System.Runtime.InteropServices; |
23 using System.Text.RegularExpressions; | 23 using System.Text.RegularExpressions; |
24 | 24 |
25 using Google.Apis.Samples.Helper; | 25 using Google.Apis.Utils; |
| 26 using Google.Apis.Utils.Trace; |
26 | 27 |
27 namespace Google.Build.Utils.Repositories | 28 namespace Google.Apis.Release.Repositories |
28 { | 29 { |
29 /// <summary> | 30 /// <summary>Mercurial repository interface class.</summary> |
30 /// Mercurial interfacing class | |
31 /// </summary> | |
32 public sealed class Hg : IDisposable | 31 public sealed class Hg : IDisposable |
33 { | 32 { |
34 /// <summary> | 33 static readonly TraceSource TraceSource = new TraceSource("Google.Apis")
; |
35 /// Name of the repository. | 34 |
36 /// </summary> | 35 /// <summary>Gets the name of the repository.summary> |
37 public string Name { get; private set; } | 36 public string Name { get; private set; } |
38 | 37 |
39 /// <summary> | 38 /// <summary>Gets the URI of the repository</summary> |
40 /// The URI of the repositoty | |
41 /// </summary> | |
42 public Uri RepositoryUri { get; private set; } | 39 public Uri RepositoryUri { get; private set; } |
43 | 40 |
44 /// <summary> | 41 /// <summary>Gets the local working directory.</summary> |
45 /// The local working directory. | |
46 /// </summary> | |
47 public string WorkingDirectory { get; private set; } | 42 public string WorkingDirectory { get; private set; } |
48 | 43 |
49 /// <summary> | 44 /// <summary>Gets indication if repository has pending changes.</summary
> |
50 /// True if this working copy has pending changes. | |
51 /// </summary> | |
52 public bool HasUncommitedChanges | 45 public bool HasUncommitedChanges |
53 { | 46 { |
54 get | 47 get |
55 { | 48 { |
56 string[] changes = RunListeningHg("status"); | 49 string[] changes = RunListeningHg("status"); |
57 return changes.Length >= 1; | 50 return changes.Length >= 1; |
58 } | 51 } |
59 } | 52 } |
60 | 53 |
61 /// <summary> | 54 /// <summary>Gets indication if there are incoming changes.</summary> |
62 /// True if there are unpulled, incoming changes. | |
63 /// </summary> | |
64 public bool HasIncomingChanges | 55 public bool HasIncomingChanges |
65 { | 56 { |
66 get | 57 get |
67 { | 58 { |
68 try | 59 try |
69 { | 60 { |
70 RunListeningHg("incoming"); | 61 RunListeningHg("incoming"); |
71 return true; // Exit code was 0 | 62 return true; // Exit code was 0 |
72 } | 63 } |
73 catch (ExternalException) | 64 catch (ExternalException) |
74 { | 65 { |
75 return false; | 66 return false; |
76 } | 67 } |
77 } | 68 } |
78 } | 69 } |
79 | 70 |
80 /// <summary> | 71 /// <summary>Gets indication if the working copy has un-pushed changes.<
/summary> |
81 /// True if this working copy has unpushed changes. | |
82 /// </summary> | |
83 public bool HasUnpushedChanges | 72 public bool HasUnpushedChanges |
84 { | 73 { |
85 get | 74 get |
86 { | 75 { |
87 try | 76 try |
88 { | 77 { |
89 string[] changes = RunListeningHg("outgoing {0} {1}", "-l",
"1"); | 78 string[] changes = RunListeningHg(string.Format("outgoing {0
} {1}", "-l", "1")); |
90 return changes.Length >= 4; | 79 return changes.Length >= 4; |
91 } | 80 } |
92 catch (ExternalException) | 81 catch (ExternalException) |
93 { | 82 { |
94 // RunListeningHg throws an ExternalException when the launc
hed process returns non-zero on exiting | 83 // RunListeningHg throws an ExternalException when the launc
hed process returns non-zero on exiting |
95 // the executable "hg outgoing" returns 1 if there where no
changes. | 84 // the executable "hg outgoing" returns 1 if there where no
changes. |
96 // So treat ExternalException as no changes. | 85 // So treat ExternalException as no changes. |
97 return false; | 86 return false; |
98 } | 87 } |
99 } | 88 } |
100 } | 89 } |
101 | 90 |
102 private Hg(Uri repositoryUri) | 91 /// <summary> |
103 : this(repositoryUri, Path.Combine(Path.GetTempPath(), Path.GetRando
mFileName())) | 92 /// Constructs a new repository. It creates a new repository if the fold
er doesn't exists, otherwise it gets |
104 { | 93 /// the current status of the repository. |
105 | 94 /// </summary> |
106 } | 95 /// <param name="repositoryUri">The URI of this repository</param> |
107 private Hg(Uri repositoryUri, string localDir) | 96 /// <param name="localDir">The local directory which contains the reposi
tory files</param> |
| 97 public Hg(Uri repositoryUri, string localDir) |
108 { | 98 { |
109 RepositoryUri = repositoryUri; | 99 RepositoryUri = repositoryUri; |
110 WorkingDirectory = Path.GetFullPath(localDir); | 100 WorkingDirectory = Path.GetFullPath(localDir); |
111 Name = repositoryUri.Segments.Last(); | 101 Name = repositoryUri.Segments.Last(); |
112 | 102 |
113 if (!Directory.Exists(WorkingDirectory)) | 103 if (!Directory.Exists(WorkingDirectory)) |
114 { | 104 { |
| 105 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Fetchi
ng {1}", Name, repositoryUri); |
115 Directory.CreateDirectory(WorkingDirectory); | 106 Directory.CreateDirectory(WorkingDirectory); |
116 this.CloneFrom(repositoryUri); | 107 RunHg(string.Format("clone {0} {1}", repositoryUri.ToString(), W
orkingDirectory)); |
117 } | 108 } |
118 else | 109 else |
119 { | 110 { |
120 CommandLine.WriteAction("Using existing repository for " + Name
+ " at " + WorkingDirectory + " .."); | 111 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Using
an existing repository", Name); |
121 RunHg("status"); | 112 RunHg("status"); |
122 } | 113 } |
123 } | 114 } |
124 | 115 |
125 /// <summary> | 116 /// <summary>Commits the pending changes.</summary> |
126 /// Commits the pending changes. | |
127 /// </summary> | |
128 /// <param name="message">Description of the changes/the commit.</param> | 117 /// <param name="message">Description of the changes/the commit.</param> |
129 /// <returns>Whether a commit was made/possible.</returns> | 118 /// <returns>Whether a commit was made/possible.</returns> |
130 public bool Commit(string message) | 119 public bool Commit(string message) |
131 { | 120 { |
132 if (!HasUncommitedChanges) | 121 if (!HasUncommitedChanges) |
133 { | 122 { |
134 return false; | 123 return false; |
135 } | 124 } |
136 | 125 |
137 RunHg("status -m -a -r"); | 126 RunHg("status -m -a -r"); |
138 | 127 |
139 CommandLine.WriteAction("Commiting changes of " + Name + " .."); | 128 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Committing
changes", Name); |
140 RunHg("commit -m \"{0}\"", message); | 129 RunHg(string.Format("commit -m \"{0}\"", message)); |
141 return true; | 130 return true; |
142 } | 131 } |
143 | 132 |
144 /// <summary> | 133 /// <summary>Adds a tag to the last revision.</summary> |
145 /// Adds a tag to the last revision. | |
146 /// </summary> | |
147 /// <param name="tagName">The tag to add.</param> | 134 /// <param name="tagName">The tag to add.</param> |
148 /// <param name="force"> | 135 /// <param name="force"> |
149 /// If set to true will overwrite existing labels of this name see "hg h
elp tag" and its | 136 /// If set to true will overwrite existing labels of this name see "hg h
elp tag" and its --force parameter. |
150 /// --force parameter. | |
151 /// </param> | 137 /// </param> |
152 public void Tag(string tagName, bool force = false) | 138 public void Tag(string tagName, bool force = false) |
153 { | 139 { |
154 CommandLine.WriteAction("Tagging " + Name + " with " + tagName + "..
"); | 140 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Tagging wi
th {1}", Name, tagName); |
155 string options = (force ? "-f " : ""); | 141 string options = (force ? "-f " : ""); |
156 RunHg("tag {0}\"{1}\"", options, tagName); | 142 RunHg(string.Format("tag {0}\"{1}\"", options, tagName)); |
157 } | 143 } |
158 | 144 |
159 /// <summary> | 145 /// <summary>Adds all unversioned files and remove all versioned files.<
/summary> |
160 /// Adds all unversioned files to the current commit. | 146 public void AddRemoveFiles() |
161 /// </summary> | |
162 public void AddUnversionedFiles() | |
163 { | 147 { |
164 RunHg("add"); | 148 RunHg("addremove"); |
165 } | 149 } |
166 | 150 |
167 /// <summary> | 151 /// <summary>Pushes all committed changes to the server.</summary> |
168 /// Marks all versioned files which have been deleted as removed. | |
169 /// </summary> | |
170 public void RemoveDeletedFiles() | |
171 { | |
172 var missingFiles = RunListeningHg("status -d") | |
173 .Where(l => l.StartsWith("! ")) | |
174 .Select(l => "\"" + l.Substring("! ".Length) + "
\""); | |
175 | |
176 string files = String.Join(" ", missingFiles); | |
177 if (!string.IsNullOrEmpty(files)) | |
178 { | |
179 CommandLine.WriteAction("Removing files: " + files); | |
180 RunHg("remove {0}", files); | |
181 } | |
182 } | |
183 | |
184 /// <summary> | |
185 /// Pushes all commited changes to the server. | |
186 /// </summary> | |
187 public void Push() | 152 public void Push() |
188 { | 153 { |
189 if (!HasUnpushedChanges) | 154 if (!HasUnpushedChanges) |
190 { | 155 { |
191 return; | 156 return; |
192 } | 157 } |
193 | 158 |
194 CommandLine.WriteAction("Pushing " + Name + " .."); | 159 TraceSource.TraceEvent(TraceEventType.Information, "Pushing {0}", Na
me); |
195 RunShellHg("push"); | 160 RunHg("push"); |
196 } | 161 } |
197 | 162 |
198 /// <summary> Updates the repository by the branch name. </summary> | 163 /// <summary>Updates the repository by the branch name.</summary> |
199 /// <param name="branchName"></param> | |
200 public void Update(string branchName) | 164 public void Update(string branchName) |
201 { | 165 { |
202 CommandLine.WriteAction("Updating branch to " + branchName); | 166 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Updating b
ranch to {1}", Name, branchName); |
203 RunShellHg("update " + branchName); | 167 RunHg("update " + branchName); |
204 } | 168 } |
205 | 169 |
206 /// <summary> | 170 /// <summary>Creates the combined path of the specified directories.</su
mmary> |
207 /// Pulls and updates this clone. | |
208 /// </summary> | |
209 public void PullUpdate() | |
210 { | |
211 RunShellHg("hg pull"); | |
212 RunShellHg("hg update"); | |
213 } | |
214 | |
215 /// <summary> | |
216 /// Outputs the diff to the default output stream. | |
217 /// </summary> | |
218 public void ShowDiff() | |
219 { | |
220 RunShellHg("diff"); | |
221 } | |
222 | |
223 /// <summary> | |
224 /// Creates the combined path of the specified directories. | |
225 /// </summary> | |
226 public string Combine(params string[] dirs) | 171 public string Combine(params string[] dirs) |
227 { | 172 { |
228 return dirs.Aggregate(WorkingDirectory, Path.Combine); | 173 return dirs.Aggregate(WorkingDirectory, Path.Combine); |
229 } | 174 } |
230 | 175 |
231 /// <summary> | 176 /// <summary>Creates a change list by listing all changes made since the
last release.</summary> |
232 /// Creates a change list by listing all changes made since the last rel
ease. | |
233 /// </summary> | |
234 public IEnumerable<string> CreateChangelist() | 177 public IEnumerable<string> CreateChangelist() |
235 { | 178 { |
236 Regex tagRegex = new Regex("Added tag [0-9A-Za-z.-]+ for changeset [
^b]+", RegexOptions.Compiled); | 179 Regex tagRegex = new Regex("Added tag [0-9A-Za-z.-]+ for changeset [
^b]+", RegexOptions.Compiled); |
237 string branch = RunListeningHg("branch").Single(); | 180 string branch = RunListeningHg("branch").Single(); |
238 return RunListeningHg("log --template \"{{rev}}: {{desc}}\\r\\n\" -b
{0}", branch) | 181 return RunListeningHg(string.Format("log --template \"{{rev}}: {{des
c}}\\r\\n\" -b {0}", branch)) |
239 .TakeWhile(line => !tagRegex.IsMatch(line)); | 182 .TakeWhile(line => !tagRegex.IsMatch(line)); |
240 } | 183 } |
241 | 184 |
242 private void CloneFrom(Uri uri) | 185 /// <summary>Runs a HG command. In addition it prints errors and message
s to trace.</summary> |
| 186 private void RunHg(string command) |
243 { | 187 { |
244 CommandLine.WriteAction("Fetching " + uri + " .."); | 188 RunHg(command, |
245 RunHg("clone {0} {1}", uri.ToString(), WorkingDirectory); | 189 error => |
| 190 { |
| 191 if (!string.IsNullOrEmpty(error)) |
| 192 { |
| 193 TraceSource.TraceEvent(TraceEventType.Error, "[{0}] {1}"
, Name, error); |
| 194 } |
| 195 }, |
| 196 msg => |
| 197 { |
| 198 if (!string.IsNullOrEmpty(msg)) |
| 199 { |
| 200 TraceSource.TraceEvent(TraceEventType.Information, "[{0}
] hg {1}", Name, msg); |
| 201 } |
| 202 }); |
246 } | 203 } |
247 | 204 |
248 private void RunShellHg(string command, params string[] args) | 205 /// <summary> |
249 { | 206 /// Run a HG command which the specific callback for errors or messages
returned from the command. |
250 RunHg(command, null, null, args); | 207 /// </summary> |
251 } | 208 private void RunHg(string command, Action<string> errorCallback = null,
Action<string> messageCallback = null) |
252 | |
253 private void RunHg(string command, params string[] args) | |
254 { | |
255 RunHg( | |
256 command, error => | |
257 { | |
258 if (!string.IsNullOrEmpty(error)) | |
259 { | |
260 CommandLine.WriteError(error); | |
261 } | |
262 }, msg => | |
263 { | |
264 if (!string.IsNullOrEmpty(msg)) | |
265 { | |
266 CommandLine.WriteResult("hg", msg); | |
267 } | |
268 }, args); | |
269 } | |
270 | |
271 private string[] RunListeningHg(string command, params string[] args) | |
272 { | |
273 var msgs = new List<string>(); | |
274 RunHg(command, msgs.Add, msgs.Add, args); | |
275 return msgs.ToArray(); | |
276 } | |
277 | |
278 private void RunHg(string command, Action<string> errors, Action<string>
msgs, params string[] args) | |
279 { | 209 { |
280 var process = new Process(); | 210 var process = new Process(); |
281 | 211 |
282 string commandLine = string.Format(command, args); | 212 process.StartInfo = new ProcessStartInfo("hg", command); |
283 process.StartInfo = new ProcessStartInfo("hg", commandLine); | |
284 process.StartInfo.WorkingDirectory = WorkingDirectory; | 213 process.StartInfo.WorkingDirectory = WorkingDirectory; |
285 | 214 |
286 if (errors != null || msgs != null) | 215 if (errorCallback != null || messageCallback != null) |
287 { | 216 { |
288 process.StartInfo.RedirectStandardError = true; | 217 process.StartInfo.RedirectStandardError = true; |
289 process.StartInfo.RedirectStandardOutput = true; | 218 process.StartInfo.RedirectStandardOutput = true; |
290 process.StartInfo.CreateNoWindow = true; | 219 process.StartInfo.CreateNoWindow = true; |
291 process.StartInfo.UseShellExecute = false; | 220 process.StartInfo.UseShellExecute = false; |
292 } | 221 } |
293 | 222 |
294 process.Start(); | 223 process.Start(); |
295 | 224 if (errorCallback != null) |
296 if (errors != null) | |
297 { | 225 { |
298 process.ErrorDataReceived += (sender, msg) => | 226 process.ErrorDataReceived += (sender, msg) => |
299 { | 227 { |
300 if (msg.Data != null) | 228 if (msg.Data != null) |
301 { | 229 { |
302 errors(msg.Data); | 230 errorCallback(msg.Data); |
303 } | 231 } |
304 }; | 232 }; |
305 process.BeginErrorReadLine(); | 233 process.BeginErrorReadLine(); |
306 } | 234 } |
307 if (msgs != null) | 235 if (messageCallback != null) |
308 { | 236 { |
309 process.OutputDataReceived += (sender, msg) => | 237 process.OutputDataReceived += (sender, msg) => |
310 { | 238 { |
311 if (msg.Data != null) | 239 if (msg.Data != null) |
312 { | 240 { |
313 msgs(msg.Data); | 241 messageCallback(msg.Data); |
314 } | 242 } |
315 }; | 243 }; |
316 process.BeginOutputReadLine(); | 244 process.BeginOutputReadLine(); |
317 } | 245 } |
318 | 246 |
319 process.WaitForExit(); | 247 process.WaitForExit(); |
| 248 |
320 if (process.ExitCode != 0) | 249 if (process.ExitCode != 0) |
321 { | 250 { |
322 string cmdLine = process.StartInfo.FileName + " " + process.Star
tInfo.Arguments; | 251 string cmdLine = process.StartInfo.FileName + " " + process.Star
tInfo.Arguments; |
323 throw new ExternalException("The process '" + cmdLine + "' exite
d with errors.", process.ExitCode); | 252 throw new ExternalException("The process '" + cmdLine + "' exite
d with errors.", process.ExitCode); |
324 } | 253 } |
325 } | 254 } |
326 | 255 |
327 /// <summary> | 256 /// <summary>Runs a HG command and returns all its errors and messages o
utput.</summary> |
328 /// Clones a new Mercurial repository. | 257 private string[] RunListeningHg(string command) |
329 /// </summary> | |
330 public static Hg Clone(string repository) | |
331 { | 258 { |
332 return new Hg(new Uri(repository)); | 259 var msgs = new List<string>(); |
333 } | 260 RunHg(command, msgs.Add, msgs.Add); |
334 | 261 return msgs.ToArray(); |
335 /// <summary> | |
336 /// Reuses the existing local repository, or creates a new clone of it d
oes not exist. | |
337 /// </summary> | |
338 /// <param name="localDir">The local Mercurial repository.</param> | |
339 /// <param name="repository">The remote URL.</param> | |
340 public static Hg Get(string localDir, string repository) | |
341 { | |
342 return new Hg(new Uri(repository), localDir); | |
343 } | 262 } |
344 | 263 |
345 public void Dispose() | 264 public void Dispose() |
346 { | 265 { |
347 CommandLine.WriteAction("Cleaning up " + Name + " .."); | 266 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Cleaning u
p", Name); |
348 Directory.Delete(WorkingDirectory, true); | 267 Directory.Delete(WorkingDirectory, true); |
349 } | 268 } |
350 } | 269 } |
351 } | 270 } |
OLD | NEW |