Mercurial > hg4j
comparison src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java @ 282:e51dd9a14b6f
Yet another WC status fix, where dirstate parent and base revision are treated right (dirstate parent other than tip and explicit baseRevision are not the same)
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Sat, 03 Sep 2011 01:21:03 +0200 |
parents | 35125450c804 |
children | 7232b94f2ae3 |
comparison
equal
deleted
inserted
replaced
281:81e9a3c9bafe | 282:e51dd9a14b6f |
---|---|
57 private final HgRepository repo; | 57 private final HgRepository repo; |
58 private final FileIterator repoWalker; | 58 private final FileIterator repoWalker; |
59 private HgDirstate dirstate; | 59 private HgDirstate dirstate; |
60 private HgStatusCollector baseRevisionCollector; | 60 private HgStatusCollector baseRevisionCollector; |
61 private PathPool pathPool; | 61 private PathPool pathPool; |
62 private ManifestRevision dirstateParentManifest; | |
62 | 63 |
63 public HgWorkingCopyStatusCollector(HgRepository hgRepo) { | 64 public HgWorkingCopyStatusCollector(HgRepository hgRepo) { |
64 this(hgRepo, new HgInternals(hgRepo).createWorkingDirWalker(null)); | 65 this(hgRepo, new HgInternals(hgRepo).createWorkingDirWalker(null)); |
65 } | 66 } |
66 | 67 |
99 dirstate = repo.loadDirstate(); | 100 dirstate = repo.loadDirstate(); |
100 } | 101 } |
101 return dirstate; | 102 return dirstate; |
102 } | 103 } |
103 | 104 |
104 // may be invoked few times | 105 private ManifestRevision getManifest(int changelogLocalRev) { |
106 ManifestRevision mr; | |
107 if (baseRevisionCollector != null) { | |
108 mr = baseRevisionCollector.raw(changelogLocalRev); | |
109 } else { | |
110 mr = new ManifestRevision(null, null); | |
111 repo.getManifest().walk(changelogLocalRev, changelogLocalRev, mr); | |
112 } | |
113 return mr; | |
114 } | |
115 | |
116 private ManifestRevision getDirstateParentManifest() { | |
117 // WC not necessarily points to TIP, but may be result of update to any previous revision. | |
118 // In such case, we need to compare local files not to their TIP content, but to specific version at the time of selected revision | |
119 if (dirstateParentManifest == null) { | |
120 Nodeid dirstateParent = getDirstate().parents()[0]; | |
121 int changelogLocalRev = repo.getChangelog().getLocalRevision(dirstateParent); | |
122 dirstateParentManifest = getManifest(changelogLocalRev); | |
123 } | |
124 return dirstateParentManifest; | |
125 } | |
126 | |
127 // may be invoked few times, TIP or WORKING_COPY indicate comparison shall be run against working copy parent | |
105 // NOTE, use of TIP constant requires certain care. TIP here doesn't mean latest cset, but actual working copy parent. | 128 // NOTE, use of TIP constant requires certain care. TIP here doesn't mean latest cset, but actual working copy parent. |
106 // XXX this shall be changed, though, and use of TIP throughout code shall be revised - | |
107 // consider case when repository is updated to one of its previous revisions. TIP points to last change, but a lot of | |
108 // commands need to work with revision that is in dirstate now. | |
109 public void walk(int baseRevision, HgStatusInspector inspector) { | 129 public void walk(int baseRevision, HgStatusInspector inspector) { |
110 if (HgInternals.wrongLocalRevision(baseRevision) || baseRevision == BAD_REVISION || baseRevision == WORKING_COPY) { | 130 if (HgInternals.wrongLocalRevision(baseRevision) || baseRevision == BAD_REVISION) { |
111 throw new IllegalArgumentException(String.valueOf(baseRevision)); | 131 throw new IllegalArgumentException(String.valueOf(baseRevision)); |
132 } | |
133 ManifestRevision collect = null; // non null indicates we compare against base revision | |
134 Set<String> baseRevFiles = Collections.emptySet(); // files from base revision not affected by status calculation | |
135 if (baseRevision != TIP && baseRevision != WORKING_COPY) { | |
136 collect = getManifest(baseRevision); | |
137 baseRevFiles = new TreeSet<String>(collect.files()); | |
138 } | |
139 if (inspector instanceof HgStatusCollector.Record) { | |
140 HgStatusCollector sc = baseRevisionCollector == null ? new HgStatusCollector(repo) : baseRevisionCollector; | |
141 // nodeidAfterChange(dirstate's parent) doesn't make too much sense, | |
142 // because the change might be actually in working copy. Nevertheless, | |
143 // as long as no nodeids can be provided for WC, seems reasonable to report | |
144 // latest known nodeid change (although at the moment this is not used and | |
145 // is done mostly not to leave stale initialization in the Record) | |
146 int rev1,rev2 = getDirstateParentManifest().changesetLocalRev(); | |
147 if (baseRevision == TIP || baseRevision == WORKING_COPY) { | |
148 rev1 = rev2 - 1; // just use revision prior to dirstate's parent | |
149 } else { | |
150 rev1 = baseRevision; | |
151 } | |
152 ((HgStatusCollector.Record) inspector).init(rev1, rev2, sc); | |
112 } | 153 } |
113 final HgIgnore hgIgnore = repo.getIgnore(); | 154 final HgIgnore hgIgnore = repo.getIgnore(); |
114 TreeSet<String> knownEntries = getDirstate().all(); | 155 TreeSet<String> knownEntries = getDirstate().all(); |
115 if (baseRevision == TIP) { | |
116 // WC not necessarily points to TIP, but may be result of update to any previous revision. | |
117 // In such case, we need to compare local files not to their TIP content, but to specific version at the time of selected revision | |
118 Nodeid dirstateParentRev = getDirstate().parents()[0]; | |
119 Nodeid lastCsetRev = repo.getChangelog().getRevision(HgRepository.TIP); | |
120 if (lastCsetRev.equals(dirstateParentRev)) { | |
121 baseRevision = repo.getChangelog().getLastRevision(); | |
122 } else { | |
123 // can do it right away, but explicit check above might save few cycles (unless getLocalRevision(Nodeid) is effective) | |
124 baseRevision = repo.getChangelog().getLocalRevision(dirstateParentRev); | |
125 } | |
126 } | |
127 final boolean isTipBase = baseRevision == repo.getChangelog().getLastRevision(); | |
128 ManifestRevision collect = null; | |
129 Set<String> baseRevFiles = Collections.emptySet(); // files from base revision not affected by status calculation | |
130 if (!isTipBase) { | |
131 if (baseRevisionCollector != null) { | |
132 collect = baseRevisionCollector.raw(baseRevision); | |
133 } else { | |
134 collect = new ManifestRevision(null, null); | |
135 repo.getManifest().walk(baseRevision, baseRevision, collect); | |
136 } | |
137 baseRevFiles = new TreeSet<String>(collect.files()); | |
138 } | |
139 if (inspector instanceof HgStatusCollector.Record) { | |
140 HgStatusCollector sc = baseRevisionCollector == null ? new HgStatusCollector(repo) : baseRevisionCollector; | |
141 ((HgStatusCollector.Record) inspector).init(baseRevision, BAD_REVISION, sc); | |
142 } | |
143 repoWalker.reset(); | 156 repoWalker.reset(); |
144 final PathPool pp = getPathPool(); | 157 final PathPool pp = getPathPool(); |
145 while (repoWalker.hasNext()) { | 158 while (repoWalker.hasNext()) { |
146 repoWalker.next(); | 159 repoWalker.next(); |
147 final Path fname = pp.path(repoWalker.name()); | 160 final Path fname = pp.path(repoWalker.name()); |
243 inspector.modified(fname); | 256 inspector.modified(fname); |
244 } else { | 257 } else { |
245 // size is the same or unknown, and, perhaps, different timestamp | 258 // size is the same or unknown, and, perhaps, different timestamp |
246 // check actual content to avoid false modified files | 259 // check actual content to avoid false modified files |
247 HgDataFile df = repo.getFileNode(fname); | 260 HgDataFile df = repo.getFileNode(fname); |
248 if (!areTheSame(f, df, HgRepository.TIP)) { | 261 Nodeid rev = getDirstateParentManifest().nodeid(fname.toString()); |
262 if (!areTheSame(f, df, rev)) { | |
249 inspector.modified(df.getPath()); | 263 inspector.modified(df.getPath()); |
250 } else { | 264 } else { |
251 inspector.clean(df.getPath()); | 265 inspector.clean(df.getPath()); |
252 } | 266 } |
253 } | 267 } |
302 return; | 316 return; |
303 } | 317 } |
304 inspector.added(fname); | 318 inspector.added(fname); |
305 } else { | 319 } else { |
306 // was known; check whether clean or modified | 320 // was known; check whether clean or modified |
307 if ((r = getDirstate().checkNormal(fname)) != null) { | 321 Nodeid nidFromDirstate = getDirstateParentManifest().nodeid(fname.toString()); |
322 if ((r = getDirstate().checkNormal(fname)) != null && nid1.equals(nidFromDirstate)) { | |
323 // regular file, was the same up to WC initialization. Check if was modified since, and, if not, report right away | |
324 // same code as in #checkLocalStatusAgainstFile | |
308 final boolean timestampEqual = getFileModificationTime(f) == r.time, sizeEqual = r.size == f.length(); | 325 final boolean timestampEqual = getFileModificationTime(f) == r.time, sizeEqual = r.size == f.length(); |
326 boolean handled = false; | |
309 if (timestampEqual && sizeEqual) { | 327 if (timestampEqual && sizeEqual) { |
310 inspector.clean(fname); | 328 inspector.clean(fname); |
329 handled = true; | |
330 } else if (!sizeEqual && r.size >= 0) { | |
331 inspector.modified(fname); | |
332 handled = true; | |
333 } else if (!todoCheckFlagsEqual(f, flags)) { | |
334 // seems like flags have changed, no reason to check content further | |
335 inspector.modified(fname); | |
336 handled = true; | |
337 } | |
338 if (handled) { | |
311 baseRevNames.remove(fname.toString()); // consumed, processed, handled. | 339 baseRevNames.remove(fname.toString()); // consumed, processed, handled. |
312 return; | 340 return; |
313 } else if (!sizeEqual && r.size >= 0) { | 341 } |
314 inspector.modified(fname); | 342 // otherwise, shall check actual content (size not the same, or unknown (-1 or -2), or timestamp is different, |
315 baseRevNames.remove(fname.toString()); // consumed, processed, handled. | 343 // or nodeid in dirstate is different, but local change might have brought it back to baseRevision state) |
316 return; | |
317 } | |
318 // otherwise, shall check actual content (size not the same, or unknown (-1 or -2), or timestamp is different) | |
319 // FALL THROUGH | 344 // FALL THROUGH |
320 } | 345 } |
321 if (r != null /*Normal dirstate, but needs extra check*/ || (r = getDirstate().checkMerged(fname)) != null || (r = getDirstate().checkAdded(fname)) != null) { | 346 if (r != null || (r = getDirstate().checkMerged(fname)) != null || (r = getDirstate().checkAdded(fname)) != null) { |
347 // check actual content to see actual changes | |
322 // when added - seems to be the case of a file added once again, hence need to check if content is different | 348 // when added - seems to be the case of a file added once again, hence need to check if content is different |
323 // either clean or modified | 349 // either clean or modified |
324 if (r.size != -1 /*XXX what about ==-2?*/&& r.size != f.length() || !todoCheckFlagsEqual(f, flags)) { | 350 HgDataFile fileNode = repo.getFileNode(fname); |
351 if (areTheSame(f, fileNode, nid1)) { | |
352 inspector.clean(fname); | |
353 } else { | |
325 inspector.modified(fname); | 354 inspector.modified(fname); |
326 } else { | |
327 // check actual content to see actual changes | |
328 HgDataFile fileNode = repo.getFileNode(fname); | |
329 if (areTheSame(f, fileNode, fileNode.getLocalRevision(nid1))) { | |
330 inspector.clean(fname); | |
331 } else { | |
332 inspector.modified(fname); | |
333 } | |
334 } | 355 } |
335 baseRevNames.remove(fname.toString()); // consumed, processed, handled. | 356 baseRevNames.remove(fname.toString()); // consumed, processed, handled. |
336 } else if (getDirstate().checkRemoved(fname) != null) { | 357 } else if (getDirstate().checkRemoved(fname) != null) { |
337 // was known, and now marked as removed, report it right away, do not rely on baseRevNames processing later | 358 // was known, and now marked as removed, report it right away, do not rely on baseRevNames processing later |
338 inspector.removed(fname); | 359 inspector.removed(fname); |
347 // changeset nodeid + hash(actual content) => entry (Nodeid) in the next Manifest | 368 // changeset nodeid + hash(actual content) => entry (Nodeid) in the next Manifest |
348 // then it's sufficient to check parents from dirstate, and if they do not match parents from file's baseRevision (non matching parents means different nodeids). | 369 // then it's sufficient to check parents from dirstate, and if they do not match parents from file's baseRevision (non matching parents means different nodeids). |
349 // The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean' | 370 // The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean' |
350 } | 371 } |
351 | 372 |
352 private boolean areTheSame(File f, HgDataFile dataFile, int localRevision) { | 373 private boolean areTheSame(File f, HgDataFile dataFile, Nodeid revision) { |
353 // XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison | 374 // XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison |
354 ByteArrayChannel bac = new ByteArrayChannel(); | 375 ByteArrayChannel bac = new ByteArrayChannel(); |
355 boolean ioFailed = false; | 376 boolean ioFailed = false; |
356 try { | 377 try { |
378 int localRevision = dataFile.getLocalRevision(revision); | |
357 // need content with metadata striped off - although theoretically chances are metadata may be different, | 379 // need content with metadata striped off - although theoretically chances are metadata may be different, |
358 // WC doesn't have it anyway | 380 // WC doesn't have it anyway |
359 dataFile.content(localRevision, bac); | 381 dataFile.content(localRevision, bac); |
360 } catch (CancelledException ex) { | 382 } catch (CancelledException ex) { |
361 // silently ignore - can't happen, ByteArrayChannel is not cancellable | 383 // silently ignore - can't happen, ByteArrayChannel is not cancellable |