LEFT | RIGHT |
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.Utils; | 25 using Google.Apis.Utils; |
| 26 using Google.Apis.Utils.Trace; |
26 | 27 |
27 namespace Google.Apis.Release.Repositories | 28 namespace Google.Apis.Release.Repositories |
28 { | 29 { |
29 /// <summary>Mercurial repository interface class.</summary> | 30 /// <summary>Mercurial repository interface class.</summary> |
30 public sealed class Hg : IDisposable | 31 public sealed class Hg : IDisposable |
31 { | 32 { |
32 static readonly TraceSource TraceSource = new TraceSource("Google.Apis")
; | 33 static readonly TraceSource TraceSource = new TraceSource("Google.Apis")
; |
33 | 34 |
34 /// <summary>Gets or sets the name of the repository.summary> | 35 /// <summary>Gets the name of the repository.summary> |
35 public string Name { get; private set; } | 36 public string Name { get; private set; } |
36 | 37 |
37 /// <summary>Gets or sets the URI of the repository</summary> | 38 /// <summary>Gets the URI of the repository</summary> |
38 public Uri RepositoryUri { get; private set; } | 39 public Uri RepositoryUri { get; private set; } |
39 | 40 |
40 /// <summary>Gets or sets the local working directory.</summary> | 41 /// <summary>Gets the local working directory.</summary> |
41 public string WorkingDirectory { get; private set; } | 42 public string WorkingDirectory { get; private set; } |
42 | 43 |
43 /// <summary>Gets indication if this working copy has pending changes.</
summary> | 44 /// <summary>Gets indication if repository has pending changes.</summary
> |
44 public bool HasUncommitedChanges | 45 public bool HasUncommitedChanges |
45 { | 46 { |
46 get | 47 get |
47 { | 48 { |
48 string[] changes = RunListeningHg("status"); | 49 string[] changes = RunListeningHg("status"); |
49 return changes.Length >= 1; | 50 return changes.Length >= 1; |
50 } | 51 } |
51 } | 52 } |
52 | 53 |
53 /// <summary>Gets indication if there are incoming changes.</summary> | 54 /// <summary>Gets indication if there are incoming changes.</summary> |
(...skipping 13 matching lines...) Expand all Loading... |
67 } | 68 } |
68 } | 69 } |
69 | 70 |
70 /// <summary>Gets indication if the working copy has un-pushed changes.<
/summary> | 71 /// <summary>Gets indication if the working copy has un-pushed changes.<
/summary> |
71 public bool HasUnpushedChanges | 72 public bool HasUnpushedChanges |
72 { | 73 { |
73 get | 74 get |
74 { | 75 { |
75 try | 76 try |
76 { | 77 { |
77 string[] changes = RunListeningHg("outgoing {0} {1}", "-l",
"1"); | 78 string[] changes = RunListeningHg(string.Format("outgoing {0
} {1}", "-l", "1")); |
78 return changes.Length >= 4; | 79 return changes.Length >= 4; |
79 } | 80 } |
80 catch (ExternalException) | 81 catch (ExternalException) |
81 { | 82 { |
82 // 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 |
83 // the executable "hg outgoing" returns 1 if there where no
changes. | 84 // the executable "hg outgoing" returns 1 if there where no
changes. |
84 // So treat ExternalException as no changes. | 85 // So treat ExternalException as no changes. |
85 return false; | 86 return false; |
86 } | 87 } |
87 } | 88 } |
88 } | 89 } |
89 | 90 |
90 private Hg(Uri repositoryUri) | 91 /// <summary> |
91 : 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 |
92 { | 93 /// the current status of the repository. |
93 } | 94 /// </summary> |
94 | 95 /// <param name="repositoryUri">The URI of this repository</param> |
95 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) |
96 { | 98 { |
97 RepositoryUri = repositoryUri; | 99 RepositoryUri = repositoryUri; |
98 WorkingDirectory = Path.GetFullPath(localDir); | 100 WorkingDirectory = Path.GetFullPath(localDir); |
99 Name = repositoryUri.Segments.Last(); | 101 Name = repositoryUri.Segments.Last(); |
100 | 102 |
101 if (!Directory.Exists(WorkingDirectory)) | 103 if (!Directory.Exists(WorkingDirectory)) |
102 { | 104 { |
| 105 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Fetchi
ng {1}", Name, repositoryUri); |
103 Directory.CreateDirectory(WorkingDirectory); | 106 Directory.CreateDirectory(WorkingDirectory); |
104 this.CloneFrom(repositoryUri); | 107 RunHg(string.Format("clone {0} {1}", repositoryUri.ToString(), W
orkingDirectory)); |
105 } | 108 } |
106 else | 109 else |
107 { | 110 { |
108 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Using
an existing repository", Name); | 111 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Using
an existing repository", Name); |
109 RunHg("status"); | 112 RunHg("status"); |
110 } | 113 } |
111 } | 114 } |
112 | 115 |
113 /// <summary>Commits the pending changes.</summary> | 116 /// <summary>Commits the pending changes.</summary> |
114 /// <param name="message">Description of the changes/the commit.</param> | 117 /// <param name="message">Description of the changes/the commit.</param> |
115 /// <returns>Whether a commit was made/possible.</returns> | 118 /// <returns>Whether a commit was made/possible.</returns> |
116 public bool Commit(string message) | 119 public bool Commit(string message) |
117 { | 120 { |
118 if (!HasUncommitedChanges) | 121 if (!HasUncommitedChanges) |
119 { | 122 { |
120 return false; | 123 return false; |
121 } | 124 } |
122 | 125 |
123 RunHg("status -m -a -r"); | 126 RunHg("status -m -a -r"); |
124 | 127 |
125 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Committing
changes", Name); | 128 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Committing
changes", Name); |
126 RunHg("commit -m \"{0}\"", message); | 129 RunHg(string.Format("commit -m \"{0}\"", message)); |
127 return true; | 130 return true; |
128 } | 131 } |
129 | 132 |
130 /// <summary>Adds a tag to the last revision.</summary> | 133 /// <summary>Adds a tag to the last revision.</summary> |
131 /// <param name="tagName">The tag to add.</param> | 134 /// <param name="tagName">The tag to add.</param> |
132 /// <param name="force"> | 135 /// <param name="force"> |
133 /// 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. |
134 /// --force parameter. | |
135 /// </param> | 137 /// </param> |
136 public void Tag(string tagName, bool force = false) | 138 public void Tag(string tagName, bool force = false) |
137 { | 139 { |
138 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Tagging wi
th {1}", Name, tagName); | 140 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Tagging wi
th {1}", Name, tagName); |
139 string options = (force ? "-f " : ""); | 141 string options = (force ? "-f " : ""); |
140 RunHg("tag {0}\"{1}\"", options, tagName); | 142 RunHg(string.Format("tag {0}\"{1}\"", options, tagName)); |
141 } | 143 } |
142 | 144 |
143 /// <summary>Adds all un-versioned files to the current commit.</summary
> | 145 /// <summary>Adds all unversioned files and remove all versioned files.<
/summary> |
144 public void AddUnversionedFiles() | 146 public void AddRemoveFiles() |
145 { | 147 { |
146 RunHg("add"); | 148 RunHg("addremove"); |
147 } | |
148 | |
149 /// <summary>Marks all versioned files which have been deleted as remove
d.</summary> | |
150 public void RemoveDeletedFiles() | |
151 { | |
152 var missingFiles = RunListeningHg("status -d") | |
153 .Where(l => l.StartsWith("! ")) | |
154 .Select(l => "\"" l.Substring("! ".Length) "
\""); | |
155 | |
156 string files = String.Join(" ", missingFiles); | |
157 if (!string.IsNullOrEmpty(files)) | |
158 { | |
159 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Removi
ng files from {1}", Name, files); | |
160 RunHg("remove {0}", files); | |
161 } | |
162 } | 149 } |
163 | 150 |
164 /// <summary>Pushes all committed changes to the server.</summary> | 151 /// <summary>Pushes all committed changes to the server.</summary> |
165 public void Push() | 152 public void Push() |
166 { | 153 { |
167 if (!HasUnpushedChanges) | 154 if (!HasUnpushedChanges) |
168 { | 155 { |
169 return; | 156 return; |
170 } | 157 } |
171 | 158 |
172 TraceSource.TraceEvent(TraceEventType.Information, "Pushing {0}", Na
me); | 159 TraceSource.TraceEvent(TraceEventType.Information, "Pushing {0}", Na
me); |
173 RunShellHg("push"); | 160 RunHg("push"); |
174 } | 161 } |
175 | 162 |
176 /// <summary>Updates the repository by the branch name.</summary> | 163 /// <summary>Updates the repository by the branch name.</summary> |
177 public void Update(string branchName) | 164 public void Update(string branchName) |
178 { | 165 { |
179 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Updating b
ranch to {1}", Name, branchName); | 166 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Updating b
ranch to {1}", Name, branchName); |
180 RunShellHg("update " branchName); | 167 RunHg("update " branchName); |
181 } | |
182 | |
183 /// <summary>Pulls and updates this clone.</summary> | |
184 public void PullUpdate() | |
185 { | |
186 RunShellHg("hg pull"); | |
187 RunShellHg("hg update"); | |
188 } | |
189 | |
190 /// <summary>Outputs the diff to the default output stream.</summary> | |
191 public void ShowDiff() | |
192 { | |
193 RunShellHg("diff"); | |
194 } | 168 } |
195 | 169 |
196 /// <summary>Creates the combined path of the specified directories.</su
mmary> | 170 /// <summary>Creates the combined path of the specified directories.</su
mmary> |
197 public string Combine(params string[] dirs) | 171 public string Combine(params string[] dirs) |
198 { | 172 { |
199 return dirs.Aggregate(WorkingDirectory, Path.Combine); | 173 return dirs.Aggregate(WorkingDirectory, Path.Combine); |
200 } | 174 } |
201 | 175 |
202 /// <summary>Creates a change list by listing all changes made since the
last release.</summary> | 176 /// <summary>Creates a change list by listing all changes made since the
last release.</summary> |
203 public IEnumerable<string> CreateChangelist() | 177 public IEnumerable<string> CreateChangelist() |
204 { | 178 { |
205 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); |
206 string branch = RunListeningHg("branch").Single(); | 180 string branch = RunListeningHg("branch").Single(); |
207 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)) |
208 .TakeWhile(line => !tagRegex.IsMatch(line)); | 182 .TakeWhile(line => !tagRegex.IsMatch(line)); |
209 } | 183 } |
210 | 184 |
211 private void CloneFrom(Uri uri) | 185 /// <summary>Runs a HG command. In addition it prints errors and message
s to trace.</summary> |
212 { | 186 private void RunHg(string command) |
213 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Fetching {
1}", Name, uri); | 187 { |
214 RunHg("clone {0} {1}", uri.ToString(), WorkingDirectory); | 188 RunHg(command, |
215 } | 189 error => |
216 | |
217 private void RunShellHg(string command, params string[] args) | |
218 { | |
219 RunHg(command, null, null, args); | |
220 } | |
221 | |
222 private void RunHg(string command, params string[] args) | |
223 { | |
224 RunHg(command, error => | |
225 { | 190 { |
226 if (!string.IsNullOrEmpty(error)) | 191 if (!string.IsNullOrEmpty(error)) |
227 { | 192 { |
228 TraceSource.TraceEvent(TraceEventType.Error, "[{0}] {1}"
, Name, error); | 193 TraceSource.TraceEvent(TraceEventType.Error, "[{0}] {1}"
, Name, error); |
229 } | 194 } |
230 }, msg => | 195 }, |
| 196 msg => |
231 { | 197 { |
232 if (!string.IsNullOrEmpty(msg)) | 198 if (!string.IsNullOrEmpty(msg)) |
233 { | 199 { |
234 TraceSource.TraceEvent(TraceEventType.Information, "[{0}
] hg {1}", Name, msg); | 200 TraceSource.TraceEvent(TraceEventType.Information, "[{0}
] hg {1}", Name, msg); |
235 } | 201 } |
236 }, args); | 202 }); |
237 } | 203 } |
238 | 204 |
239 private string[] RunListeningHg(string command, params string[] args) | 205 /// <summary> |
240 { | 206 /// Run a HG command which the specific callback for errors or messages
returned from the command. |
241 var msgs = new List<string>(); | 207 /// </summary> |
242 RunHg(command, msgs.Add, msgs.Add, args); | 208 private void RunHg(string command, Action<string> errorCallback = null,
Action<string> messageCallback = null) |
243 return msgs.ToArray(); | |
244 } | |
245 | |
246 private void RunHg(string command, Action<string> errors, Action<string>
msgs, params string[] args) | |
247 { | 209 { |
248 var process = new Process(); | 210 var process = new Process(); |
249 | 211 |
250 string commandLine = string.Format(command, args); | 212 process.StartInfo = new ProcessStartInfo("hg", command); |
251 process.StartInfo = new ProcessStartInfo("hg", commandLine); | |
252 process.StartInfo.WorkingDirectory = WorkingDirectory; | 213 process.StartInfo.WorkingDirectory = WorkingDirectory; |
253 | 214 |
254 if (errors != null || msgs != null) | 215 if (errorCallback != null || messageCallback != null) |
255 { | 216 { |
256 process.StartInfo.RedirectStandardError = true; | 217 process.StartInfo.RedirectStandardError = true; |
257 process.StartInfo.RedirectStandardOutput = true; | 218 process.StartInfo.RedirectStandardOutput = true; |
258 process.StartInfo.CreateNoWindow = true; | 219 process.StartInfo.CreateNoWindow = true; |
259 process.StartInfo.UseShellExecute = false; | 220 process.StartInfo.UseShellExecute = false; |
260 } | 221 } |
261 | 222 |
262 process.Start(); | 223 process.Start(); |
263 | 224 if (errorCallback != null) |
264 if (errors != null) | |
265 { | 225 { |
266 process.ErrorDataReceived = (sender, msg) => | 226 process.ErrorDataReceived = (sender, msg) => |
267 { | 227 { |
268 if (msg.Data != null) | 228 if (msg.Data != null) |
269 { | 229 { |
270 errors(msg.Data); | 230 errorCallback(msg.Data); |
271 } | 231 } |
272 }; | 232 }; |
273 process.BeginErrorReadLine(); | 233 process.BeginErrorReadLine(); |
274 } | 234 } |
275 if (msgs != null) | 235 if (messageCallback != null) |
276 { | 236 { |
277 process.OutputDataReceived = (sender, msg) => | 237 process.OutputDataReceived = (sender, msg) => |
278 { | 238 { |
279 if (msg.Data != null) | 239 if (msg.Data != null) |
280 { | 240 { |
281 msgs(msg.Data); | 241 messageCallback(msg.Data); |
282 } | 242 } |
283 }; | 243 }; |
284 process.BeginOutputReadLine(); | 244 process.BeginOutputReadLine(); |
285 } | 245 } |
286 | 246 |
287 process.WaitForExit(); | 247 process.WaitForExit(); |
| 248 |
288 if (process.ExitCode != 0) | 249 if (process.ExitCode != 0) |
289 { | 250 { |
290 string cmdLine = process.StartInfo.FileName " " process.Star
tInfo.Arguments; | 251 string cmdLine = process.StartInfo.FileName " " process.Star
tInfo.Arguments; |
291 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); |
292 } | 253 } |
293 } | 254 } |
294 | 255 |
295 /// <summary>Clones a new Mercurial repository.</summary> | 256 /// <summary>Runs a HG command and returns all its errors and messages o
utput.</summary> |
296 public static Hg Clone(string repository) | 257 private string[] RunListeningHg(string command) |
297 { | 258 { |
298 return new Hg(new Uri(repository)); | 259 var msgs = new List<string>(); |
299 } | 260 RunHg(command, msgs.Add, msgs.Add); |
300 | 261 return msgs.ToArray(); |
301 /// <summary>Reuses the existing local repository, or creates a new clon
e of it does not exist.</summary> | |
302 /// <param name="localDir">The local Mercurial repository.</param> | |
303 /// <param name="repository">The remote URL.</param> | |
304 public static Hg Get(string localDir, string repository) | |
305 { | |
306 return new Hg(new Uri(repository), localDir); | |
307 } | 262 } |
308 | 263 |
309 public void Dispose() | 264 public void Dispose() |
310 { | 265 { |
311 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Cleaning u
p", Name); | 266 TraceSource.TraceEvent(TraceEventType.Information, "[{0}] Cleaning u
p", Name); |
312 Directory.Delete(WorkingDirectory, true); | 267 Directory.Delete(WorkingDirectory, true); |
313 } | 268 } |
314 } | 269 } |
315 } | 270 } |
LEFT | RIGHT |