1 | |
|
2 | |
|
3 | |
|
4 | |
|
5 | |
|
6 | |
|
7 | |
|
8 | |
|
9 | |
|
10 | |
|
11 | |
|
12 | |
|
13 | |
|
14 | |
|
15 | |
|
16 | |
|
17 | |
|
18 | |
|
19 | |
|
20 | |
|
21 | |
|
22 | |
|
23 | |
|
24 | |
|
25 | |
|
26 | |
package com.aragost.javahg; |
27 | |
|
28 | |
import java.io.IOException; |
29 | |
import java.util.List; |
30 | |
import java.util.Map; |
31 | |
|
32 | |
import com.aragost.javahg.commands.LogCommand; |
33 | |
import com.aragost.javahg.commands.StatusCommand; |
34 | |
import com.aragost.javahg.commands.StatusResult; |
35 | |
import com.aragost.javahg.internals.GenericLogCommand; |
36 | |
import com.aragost.javahg.internals.HgInputStream; |
37 | |
import com.aragost.javahg.internals.RuntimeIOException; |
38 | |
import com.aragost.javahg.internals.Utils; |
39 | |
import com.google.common.annotations.VisibleForTesting; |
40 | |
import com.google.common.base.Function; |
41 | |
import com.google.common.collect.ImmutableList; |
42 | |
import com.google.common.collect.ImmutableMap; |
43 | |
import com.google.common.collect.Lists; |
44 | |
import com.google.common.collect.Maps; |
45 | |
import com.google.common.collect.ImmutableList.Builder; |
46 | |
|
47 | |
|
48 | |
|
49 | |
|
50 | |
|
51 | |
|
52 | |
|
53 | 6 | public class Changeset { |
54 | |
|
55 | |
|
56 | |
|
57 | |
|
58 | |
|
59 | |
|
60 | |
|
61 | 1 | private static final byte[] CHANGESET_PATTERN = Utils.randomBytes(); |
62 | |
|
63 | |
|
64 | |
|
65 | |
|
66 | |
|
67 | |
|
68 | |
|
69 | 1 | public static final String CHANGESET_STYLE_PATH = Utils.resourceAsFile("/styles/changesets.style", |
70 | |
ImmutableMap.of("pattern", CHANGESET_PATTERN)).getPath(); |
71 | |
|
72 | 1 | public static final String CHANGESET_EAGER_STYLE_PATH = Utils.resourceAsFile("/styles/changesets-eager.style", |
73 | |
ImmutableMap.of("pattern", CHANGESET_PATTERN)).getPath(); |
74 | |
|
75 | |
|
76 | |
|
77 | |
|
78 | |
public static final String NULL_ID = "0000000000000000000000000000000000000000"; |
79 | |
|
80 | |
private final String node; |
81 | |
private final Repository repository; |
82 | |
|
83 | |
|
84 | |
|
85 | |
|
86 | |
protected ChangesetData data; |
87 | |
|
88 | |
|
89 | |
|
90 | |
|
91 | |
protected ChangesetFileData fileData; |
92 | |
|
93 | |
|
94 | |
|
95 | |
|
96 | |
private Extra extra; |
97 | |
|
98 | |
|
99 | |
|
100 | |
|
101 | |
|
102 | |
|
103 | 967 | public Changeset(Repository repository, String node) { |
104 | 967 | this.repository = repository; |
105 | 967 | this.node = node; |
106 | 967 | } |
107 | |
|
108 | |
private static Changeset createFromInputStream(Repository repository, HgInputStream in, boolean eager) throws IOException { |
109 | 110 | byte[] node = in.next(40); |
110 | 110 | String nodeString = new String(node); |
111 | 110 | int revision = in.revisionUpTo('\n'); |
112 | 110 | Changeset cset = repository.changeset(nodeString); |
113 | 110 | String user = in.textUpTo('\n'); |
114 | 110 | DateTime timestamp = in.dateTimeUpTo('\n'); |
115 | 110 | String branch = in.textUpTo('\n'); |
116 | 110 | in.upTo(':'); |
117 | 110 | String p1 = in.nextAsText(40); |
118 | 110 | in.upTo(':'); |
119 | 110 | String p2 = in.nextAsText(40); |
120 | 110 | in.mustMatch(' '); |
121 | 110 | Changeset parent1 = repository.changeset(p1); |
122 | 110 | Changeset parent2 = repository.changeset(p2); |
123 | |
|
124 | |
|
125 | 110 | if ( eager ){ |
126 | 1 | Builder<String> addedBuilder = ImmutableList.builder(); |
127 | 1 | Builder<String> modifiedBuilder = ImmutableList.builder(); |
128 | 1 | Builder<String> deletedBuilder = ImmutableList.builder(); |
129 | |
|
130 | 1 | String line = in.textUpTo('\n'); |
131 | 2 | while ( line.length() > 0 ) { |
132 | 1 | if ( line.startsWith("a ") ){ |
133 | 1 | addedBuilder.add( line.substring(2) ); |
134 | 0 | } else if ( line.startsWith("m ") ){ |
135 | 0 | modifiedBuilder.add( line.substring(2) ); |
136 | 0 | } else if ( line.startsWith("d ")){ |
137 | 0 | deletedBuilder.add( line.substring(2) ); |
138 | |
} |
139 | 1 | line = in.textUpTo('\n'); |
140 | |
} |
141 | |
|
142 | 1 | ChangesetFileData fileData = cset.fileData; |
143 | 1 | if (fileData == null){ |
144 | 1 | fileData = new ChangesetFileData(addedBuilder.build(), modifiedBuilder.build(), deletedBuilder.build()); |
145 | 1 | cset.fileData = fileData; |
146 | |
} |
147 | |
} |
148 | 110 | String message = in.textUpTo('\0'); |
149 | |
|
150 | 110 | if (cset == null) { |
151 | |
|
152 | 0 | return null; |
153 | |
} |
154 | |
|
155 | 110 | ChangesetData data = cset.data; |
156 | 110 | if (data == null) { |
157 | 79 | data = new ChangesetData(revision, user, timestamp, branch, parent1, parent2, |
158 | |
message); |
159 | 79 | cset.data = data; |
160 | 31 | } else if (revision != data.revision) { |
161 | |
|
162 | 0 | data.revision = revision; |
163 | |
} |
164 | 110 | return cset; |
165 | |
} |
166 | |
|
167 | |
|
168 | |
|
169 | |
|
170 | |
|
171 | |
|
172 | |
|
173 | |
|
174 | |
|
175 | |
public static List<Changeset> readListFromStream(Repository repository, HgInputStream in) { |
176 | 42 | return readListFromStream(repository, in, false); |
177 | |
} |
178 | |
|
179 | |
|
180 | |
|
181 | |
|
182 | |
|
183 | |
|
184 | |
|
185 | |
|
186 | |
|
187 | |
|
188 | |
|
189 | |
|
190 | |
|
191 | |
public static List<Changeset> readListFromStream(Repository repository, HgInputStream in, boolean eager) { |
192 | 79 | List<Changeset> changesets = Lists.newArrayList(); |
193 | |
try { |
194 | 79 | boolean found = in.find(CHANGESET_PATTERN); |
195 | 79 | if (found) { |
196 | 183 | while (!in.match(CHANGESET_PATTERN)) { |
197 | 110 | Changeset cset = Changeset.createFromInputStream(repository, in, eager); |
198 | |
|
199 | 110 | if (cset != null) { |
200 | 110 | changesets.add(cset); |
201 | |
} |
202 | 110 | } |
203 | |
} |
204 | |
|
205 | 0 | } catch (IOException e) { |
206 | 0 | throw new RuntimeIOException(e); |
207 | |
} finally { |
208 | 0 | try { |
209 | 79 | Utils.consumeAll(in); |
210 | 0 | } catch (IOException e) { |
211 | 0 | throw new RuntimeIOException(e); |
212 | 79 | } |
213 | |
} |
214 | 79 | return changesets; |
215 | |
} |
216 | |
|
217 | |
public String getNode() { |
218 | 631 | return this.node; |
219 | |
} |
220 | |
|
221 | |
public int getRevision() { |
222 | 10 | ensureAllDataLoaded(); |
223 | 10 | return this.data.revision; |
224 | |
} |
225 | |
|
226 | |
public String getUser() { |
227 | 7 | ensureAllDataLoaded(); |
228 | 7 | return this.data.user; |
229 | |
} |
230 | |
|
231 | |
public DateTime getTimestamp() { |
232 | 1 | ensureAllDataLoaded(); |
233 | 1 | return this.data.timestamp; |
234 | |
} |
235 | |
|
236 | |
public String getBranch() { |
237 | 2 | ensureAllDataLoaded(); |
238 | 2 | return this.data.branch; |
239 | |
} |
240 | |
|
241 | |
public Changeset getParent1() { |
242 | 4 | ensureAllDataLoaded(); |
243 | 4 | return this.data.parent1; |
244 | |
} |
245 | |
|
246 | |
public Changeset getParent2() { |
247 | 3 | ensureAllDataLoaded(); |
248 | 3 | return this.data.parent2; |
249 | |
} |
250 | |
|
251 | |
public String getMessage() { |
252 | 5 | ensureAllDataLoaded(); |
253 | 5 | return this.data.message; |
254 | |
} |
255 | |
|
256 | |
public List<String> getAddedFiles(){ |
257 | 4 | ensureFileDataLoaded(); |
258 | 4 | return this.fileData.addedFiles; |
259 | |
} |
260 | |
|
261 | |
public List<String> getModifiedFiles(){ |
262 | 2 | ensureFileDataLoaded(); |
263 | 2 | return this.fileData.modifiedFiles; |
264 | |
} |
265 | |
|
266 | |
public List<String> getDeletedFiles(){ |
267 | 1 | ensureFileDataLoaded(); |
268 | 1 | return this.fileData.deletedFiles; |
269 | |
} |
270 | |
|
271 | |
private void loadFileData(){ |
272 | 4 | StatusResult result = new StatusCommand(repository).added().modified() |
273 | |
.removed().change(this.node).execute(); |
274 | 4 | if ( result != null ){ |
275 | 4 | fileData = new ChangesetFileData(result.getAdded(), result.getModified(), |
276 | |
result.getRemoved()); |
277 | |
} else { |
278 | 0 | throw new IllegalStateException("could not load file data from status"); |
279 | |
} |
280 | 4 | } |
281 | |
|
282 | |
private void ensureFileDataLoaded(){ |
283 | 7 | if (this.fileData != null) { |
284 | 3 | return; |
285 | |
} |
286 | 4 | loadFileData(); |
287 | 4 | if (this.fileData == null) { |
288 | 0 | throw new IllegalStateException("could not load file data"); |
289 | |
} |
290 | 4 | } |
291 | |
|
292 | |
private void ensureAllDataLoaded() { |
293 | 32 | if (this.data != null) { |
294 | 24 | return; |
295 | |
} |
296 | 8 | LogCommand.on(this.repository).rev(getNode()).execute(); |
297 | 8 | if (this.data == null) { |
298 | 0 | throw new IllegalStateException("data was not loaded"); |
299 | |
} |
300 | 8 | } |
301 | |
|
302 | |
@Deprecated |
303 | |
public Phase readPhase() { |
304 | 0 | return phase(); |
305 | |
} |
306 | |
|
307 | |
|
308 | |
|
309 | |
|
310 | |
|
311 | |
public Phase phase() { |
312 | 10 | Map<Changeset, Phase> phases = getRepository().phases(getNode()); |
313 | 10 | return phases.get(this); |
314 | |
} |
315 | |
|
316 | |
|
317 | |
|
318 | |
|
319 | |
public List<String> tags() { |
320 | 3 | GenericLogCommand cmd = new GenericLogCommand(getRepository()).style("tags"); |
321 | 3 | cmd.rev(getNode()); |
322 | 3 | HgInputStream stream = cmd.stream(); |
323 | 3 | List<String> result = Lists.newArrayList(); |
324 | |
try { |
325 | 7 | while (!stream.isEof()) { |
326 | 4 | String tag = stream.textUpTo(0); |
327 | 4 | if (!"tip".equals(tag)) { |
328 | 3 | result.add(tag); |
329 | |
} |
330 | 4 | } |
331 | 0 | } catch (IOException e) { |
332 | 0 | throw new RuntimeIOException(e); |
333 | |
} finally { |
334 | 0 | try { |
335 | 3 | stream.consumeAll(); |
336 | 0 | } catch (IOException e) { |
337 | 0 | throw new RuntimeIOException(e); |
338 | 3 | } |
339 | |
} |
340 | 3 | return result; |
341 | |
} |
342 | |
|
343 | |
|
344 | |
|
345 | |
|
346 | |
|
347 | |
public synchronized Extra getExtra() { |
348 | 9 | if (this.extra == null) { |
349 | 9 | GenericLogCommand cmd = new GenericLogCommand(getRepository()).style("extras"); |
350 | 9 | cmd.rev(getNode()); |
351 | 9 | this.extra = new Extra(cmd.stream()); |
352 | |
} |
353 | 9 | return this.extra; |
354 | |
} |
355 | |
|
356 | |
@Override |
357 | |
public boolean equals(Object that) { |
358 | 50 | if (that instanceof Changeset) { |
359 | 49 | return equals((Changeset) that); |
360 | |
} else { |
361 | 1 | return false; |
362 | |
} |
363 | |
} |
364 | |
|
365 | |
public boolean equals(Changeset that) { |
366 | 53 | if (this.repository != that.repository) { |
367 | 1 | return false; |
368 | |
} |
369 | 52 | return this.getNode().equals(that.getNode()); |
370 | |
} |
371 | |
|
372 | |
@Override |
373 | |
public int hashCode() { |
374 | 34 | return getNode().hashCode(); |
375 | |
} |
376 | |
|
377 | |
@Override |
378 | |
public String toString() { |
379 | 0 | StringBuilder builder = new StringBuilder("changeset["); |
380 | 0 | builder.append(this.data == null ? "?" : this.data.revision).append(':').append(this.node).append(']'); |
381 | 0 | return builder.toString(); |
382 | |
} |
383 | |
|
384 | |
@VisibleForTesting |
385 | |
ChangesetData getData() { |
386 | 3 | return this.data; |
387 | |
} |
388 | |
|
389 | |
@VisibleForTesting |
390 | |
ChangesetFileData getFileData(){ |
391 | 2 | return this.fileData; |
392 | |
} |
393 | |
|
394 | |
Repository getRepository() { |
395 | 38 | return repository; |
396 | |
} |
397 | |
|
398 | |
private String decodeBytes(byte[] bytes) { |
399 | 6 | return Utils.decodeBytes(bytes, getRepository().newDecoder()); |
400 | |
} |
401 | |
|
402 | |
|
403 | |
|
404 | |
|
405 | |
|
406 | |
|
407 | |
|
408 | |
|
409 | |
|
410 | 9 | public class Extra { |
411 | |
|
412 | |
private final Map<String, byte[]> map; |
413 | |
|
414 | 9 | private Extra(HgInputStream stream) { |
415 | |
try { |
416 | 9 | this.map = Maps.newHashMap(); |
417 | |
|
418 | 9 | byte[] node = stream.upTo(0); |
419 | 22 | while (!stream.isEof()) { |
420 | 13 | String key = stream.textUpTo(0); |
421 | 13 | byte[] value = stream.upTo(node); |
422 | 13 | this.map.put(key, value); |
423 | 13 | } |
424 | 0 | } catch (IOException e) { |
425 | 0 | throw new RuntimeIOException(e); |
426 | |
} finally { |
427 | 0 | try { |
428 | 9 | stream.consumeAll(); |
429 | 0 | } catch (IOException e) { |
430 | 0 | throw new RuntimeIOException(e); |
431 | 9 | } |
432 | |
} |
433 | 9 | } |
434 | |
|
435 | |
|
436 | |
|
437 | |
|
438 | |
|
439 | |
public String getString(String key) { |
440 | 4 | byte[] bytes = getBytes(key); |
441 | 4 | if (bytes == null) { |
442 | 0 | return null; |
443 | |
} else { |
444 | 4 | return decodeBytes(bytes); |
445 | |
} |
446 | |
} |
447 | |
|
448 | |
|
449 | |
|
450 | |
|
451 | |
|
452 | |
public byte[] getBytes(String key) { |
453 | 4 | return map.get(key); |
454 | |
} |
455 | |
|
456 | |
|
457 | |
|
458 | |
|
459 | |
|
460 | |
public Map<String, String> stringValuedMap() { |
461 | 1 | Function<byte[], String> f = new Function<byte[], String>() { |
462 | |
public String apply(byte[] input) { |
463 | 2 | return decodeBytes(input); |
464 | |
} |
465 | |
}; |
466 | 1 | return Maps.transformValues(this.map, f); |
467 | |
} |
468 | |
|
469 | |
|
470 | |
|
471 | |
|
472 | |
|
473 | |
public Map<String, byte[]> byteArrayValuedMap() { |
474 | 0 | return this.map; |
475 | |
} |
476 | |
|
477 | |
} |
478 | |
|
479 | |
} |
480 | |
|
481 | |
|
482 | |
class ChangesetFileData { |
483 | |
|
484 | |
public List<String> addedFiles; |
485 | |
public List<String> modifiedFiles; |
486 | |
public List<String> deletedFiles; |
487 | |
|
488 | |
public ChangesetFileData(List<String> addedFiles, |
489 | |
List<String> modifiedFiles, |
490 | |
List<String> deletedFiles) |
491 | 5 | { |
492 | 5 | this.addedFiles = addedFiles; |
493 | 5 | this.modifiedFiles = modifiedFiles; |
494 | 5 | this.deletedFiles = deletedFiles; |
495 | 5 | } |
496 | |
|
497 | |
} |
498 | |
class ChangesetData { |
499 | |
public int revision; |
500 | |
public String user; |
501 | |
public DateTime timestamp; |
502 | |
public String branch; |
503 | |
public Changeset parent1; |
504 | |
public Changeset parent2; |
505 | |
public String message; |
506 | |
|
507 | |
public ChangesetData(int revision, String user, DateTime timestamp, String branch, Changeset parent1, |
508 | 79 | Changeset parent2, String message) { |
509 | 79 | this.revision = revision; |
510 | 79 | this.user = user; |
511 | 79 | this.timestamp = timestamp; |
512 | 79 | this.branch = branch; |
513 | 79 | this.parent1 = parent1; |
514 | 79 | this.parent2 = parent2; |
515 | 79 | this.message = message; |
516 | 79 | } |
517 | |
} |