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