Mercurial > jhg
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 |
