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 public class Changeset {
54
55
56
57
58
59
60
61 private static final byte[] CHANGESET_PATTERN = Utils.randomBytes();
62
63
64
65
66
67
68
69 public static final String CHANGESET_STYLE_PATH = Utils.resourceAsFile("/styles/changesets.style",
70 ImmutableMap.of("pattern", CHANGESET_PATTERN)).getPath();
71
72 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 public Changeset(Repository repository, String node) {
104 this.repository = repository;
105 this.node = node;
106 }
107
108 private static Changeset createFromInputStream(Repository repository, HgInputStream in, boolean eager) throws IOException {
109 byte[] node = in.next(40);
110 String nodeString = new String(node);
111 int revision = in.revisionUpTo('\n');
112 Changeset cset = repository.changeset(nodeString);
113 String user = in.textUpTo('\n');
114 DateTime timestamp = in.dateTimeUpTo('\n');
115 String branch = in.textUpTo('\n');
116 in.upTo(':');
117 String p1 = in.nextAsText(40);
118 in.upTo(':');
119 String p2 = in.nextAsText(40);
120 in.mustMatch(' ');
121 Changeset parent1 = repository.changeset(p1);
122 Changeset parent2 = repository.changeset(p2);
123
124
125 if ( eager ){
126 Builder<String> addedBuilder = ImmutableList.builder();
127 Builder<String> modifiedBuilder = ImmutableList.builder();
128 Builder<String> deletedBuilder = ImmutableList.builder();
129
130 String line = in.textUpTo('\n');
131 while ( line.length() > 0 ) {
132 if ( line.startsWith("a ") ){
133 addedBuilder.add( line.substring(2) );
134 } else if ( line.startsWith("m ") ){
135 modifiedBuilder.add( line.substring(2) );
136 } else if ( line.startsWith("d ")){
137 deletedBuilder.add( line.substring(2) );
138 }
139 line = in.textUpTo('\n');
140 }
141
142 ChangesetFileData fileData = cset.fileData;
143 if (fileData == null){
144 fileData = new ChangesetFileData(addedBuilder.build(), modifiedBuilder.build(), deletedBuilder.build());
145 cset.fileData = fileData;
146 }
147 }
148 String message = in.textUpTo('\0');
149
150 if (cset == null) {
151
152 return null;
153 }
154
155 ChangesetData data = cset.data;
156 if (data == null) {
157 data = new ChangesetData(revision, user, timestamp, branch, parent1, parent2,
158 message);
159 cset.data = data;
160 } else if (revision != data.revision) {
161
162 data.revision = revision;
163 }
164 return cset;
165 }
166
167
168
169
170
171
172
173
174
175 public static List<Changeset> readListFromStream(Repository repository, HgInputStream in) {
176 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 List<Changeset> changesets = Lists.newArrayList();
193 try {
194 boolean found = in.find(CHANGESET_PATTERN);
195 if (found) {
196 while (!in.match(CHANGESET_PATTERN)) {
197 Changeset cset = Changeset.createFromInputStream(repository, in, eager);
198
199 if (cset != null) {
200 changesets.add(cset);
201 }
202 }
203 }
204
205 } catch (IOException e) {
206 throw new RuntimeIOException(e);
207 } finally {
208 try {
209 Utils.consumeAll(in);
210 } catch (IOException e) {
211 throw new RuntimeIOException(e);
212 }
213 }
214 return changesets;
215 }
216
217 public String getNode() {
218 return this.node;
219 }
220
221 public int getRevision() {
222 ensureAllDataLoaded();
223 return this.data.revision;
224 }
225
226 public String getUser() {
227 ensureAllDataLoaded();
228 return this.data.user;
229 }
230
231 public DateTime getTimestamp() {
232 ensureAllDataLoaded();
233 return this.data.timestamp;
234 }
235
236 public String getBranch() {
237 ensureAllDataLoaded();
238 return this.data.branch;
239 }
240
241 public Changeset getParent1() {
242 ensureAllDataLoaded();
243 return this.data.parent1;
244 }
245
246 public Changeset getParent2() {
247 ensureAllDataLoaded();
248 return this.data.parent2;
249 }
250
251 public String getMessage() {
252 ensureAllDataLoaded();
253 return this.data.message;
254 }
255
256 public List<String> getAddedFiles(){
257 ensureFileDataLoaded();
258 return this.fileData.addedFiles;
259 }
260
261 public List<String> getModifiedFiles(){
262 ensureFileDataLoaded();
263 return this.fileData.modifiedFiles;
264 }
265
266 public List<String> getDeletedFiles(){
267 ensureFileDataLoaded();
268 return this.fileData.deletedFiles;
269 }
270
271 private void loadFileData(){
272 StatusResult result = new StatusCommand(repository).added().modified()
273 .removed().change(this.node).execute();
274 if ( result != null ){
275 fileData = new ChangesetFileData(result.getAdded(), result.getModified(),
276 result.getRemoved());
277 } else {
278 throw new IllegalStateException("could not load file data from status");
279 }
280 }
281
282 private void ensureFileDataLoaded(){
283 if (this.fileData != null) {
284 return;
285 }
286 loadFileData();
287 if (this.fileData == null) {
288 throw new IllegalStateException("could not load file data");
289 }
290 }
291
292 private void ensureAllDataLoaded() {
293 if (this.data != null) {
294 return;
295 }
296 LogCommand.on(this.repository).rev(getNode()).execute();
297 if (this.data == null) {
298 throw new IllegalStateException("data was not loaded");
299 }
300 }
301
302 @Deprecated
303 public Phase readPhase() {
304 return phase();
305 }
306
307
308
309
310
311 public Phase phase() {
312 Map<Changeset, Phase> phases = getRepository().phases(getNode());
313 return phases.get(this);
314 }
315
316
317
318
319 public List<String> tags() {
320 GenericLogCommand cmd = new GenericLogCommand(getRepository()).style("tags");
321 cmd.rev(getNode());
322 HgInputStream stream = cmd.stream();
323 List<String> result = Lists.newArrayList();
324 try {
325 while (!stream.isEof()) {
326 String tag = stream.textUpTo(0);
327 if (!"tip".equals(tag)) {
328 result.add(tag);
329 }
330 }
331 } catch (IOException e) {
332 throw new RuntimeIOException(e);
333 } finally {
334 try {
335 stream.consumeAll();
336 } catch (IOException e) {
337 throw new RuntimeIOException(e);
338 }
339 }
340 return result;
341 }
342
343
344
345
346
347 public synchronized Extra getExtra() {
348 if (this.extra == null) {
349 GenericLogCommand cmd = new GenericLogCommand(getRepository()).style("extras");
350 cmd.rev(getNode());
351 this.extra = new Extra(cmd.stream());
352 }
353 return this.extra;
354 }
355
356 @Override
357 public boolean equals(Object that) {
358 if (that instanceof Changeset) {
359 return equals((Changeset) that);
360 } else {
361 return false;
362 }
363 }
364
365 public boolean equals(Changeset that) {
366 if (this.repository != that.repository) {
367 return false;
368 }
369 return this.getNode().equals(that.getNode());
370 }
371
372 @Override
373 public int hashCode() {
374 return getNode().hashCode();
375 }
376
377 @Override
378 public String toString() {
379 StringBuilder builder = new StringBuilder("changeset[");
380 builder.append(this.data == null ? "?" : this.data.revision).append(':').append(this.node).append(']');
381 return builder.toString();
382 }
383
384 @VisibleForTesting
385 ChangesetData getData() {
386 return this.data;
387 }
388
389 @VisibleForTesting
390 ChangesetFileData getFileData(){
391 return this.fileData;
392 }
393
394 Repository getRepository() {
395 return repository;
396 }
397
398 private String decodeBytes(byte[] bytes) {
399 return Utils.decodeBytes(bytes, getRepository().newDecoder());
400 }
401
402
403
404
405
406
407
408
409
410 public class Extra {
411
412 private final Map<String, byte[]> map;
413
414 private Extra(HgInputStream stream) {
415 try {
416 this.map = Maps.newHashMap();
417
418 byte[] node = stream.upTo(0);
419 while (!stream.isEof()) {
420 String key = stream.textUpTo(0);
421 byte[] value = stream.upTo(node);
422 this.map.put(key, value);
423 }
424 } catch (IOException e) {
425 throw new RuntimeIOException(e);
426 } finally {
427 try {
428 stream.consumeAll();
429 } catch (IOException e) {
430 throw new RuntimeIOException(e);
431 }
432 }
433 }
434
435
436
437
438
439 public String getString(String key) {
440 byte[] bytes = getBytes(key);
441 if (bytes == null) {
442 return null;
443 } else {
444 return decodeBytes(bytes);
445 }
446 }
447
448
449
450
451
452 public byte[] getBytes(String key) {
453 return map.get(key);
454 }
455
456
457
458
459
460 public Map<String, String> stringValuedMap() {
461 Function<byte[], String> f = new Function<byte[], String>() {
462 public String apply(byte[] input) {
463 return decodeBytes(input);
464 }
465 };
466 return Maps.transformValues(this.map, f);
467 }
468
469
470
471
472
473 public Map<String, byte[]> byteArrayValuedMap() {
474 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 {
492 this.addedFiles = addedFiles;
493 this.modifiedFiles = modifiedFiles;
494 this.deletedFiles = deletedFiles;
495 }
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 Changeset parent2, String message) {
509 this.revision = revision;
510 this.user = user;
511 this.timestamp = timestamp;
512 this.branch = branch;
513 this.parent1 = parent1;
514 this.parent2 = parent2;
515 this.message = message;
516 }
517 }