Mercurial > hg4j
comparison src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java @ 226:26ad7827a62d
Support status query for a single file or a subdirectory of a repository
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Wed, 25 May 2011 12:16:24 +0200 |
parents | d63583b47bfa |
children | 1ec6b327a6ac |
comparison
equal
deleted
inserted
replaced
225:fad70a9e6c7f | 226:26ad7827a62d |
---|---|
26 import java.io.FileInputStream; | 26 import java.io.FileInputStream; |
27 import java.io.IOException; | 27 import java.io.IOException; |
28 import java.nio.ByteBuffer; | 28 import java.nio.ByteBuffer; |
29 import java.nio.channels.FileChannel; | 29 import java.nio.channels.FileChannel; |
30 import java.util.Collections; | 30 import java.util.Collections; |
31 import java.util.NoSuchElementException; | |
31 import java.util.Set; | 32 import java.util.Set; |
32 import java.util.TreeSet; | 33 import java.util.TreeSet; |
33 | 34 |
34 import org.tmatesoft.hg.core.HgDataStreamException; | 35 import org.tmatesoft.hg.core.HgDataStreamException; |
35 import org.tmatesoft.hg.core.HgException; | 36 import org.tmatesoft.hg.core.HgException; |
36 import org.tmatesoft.hg.core.Nodeid; | 37 import org.tmatesoft.hg.core.Nodeid; |
37 import org.tmatesoft.hg.internal.ByteArrayChannel; | 38 import org.tmatesoft.hg.internal.ByteArrayChannel; |
39 import org.tmatesoft.hg.internal.Experimental; | |
38 import org.tmatesoft.hg.internal.FilterByteChannel; | 40 import org.tmatesoft.hg.internal.FilterByteChannel; |
41 import org.tmatesoft.hg.internal.RelativePathRewrite; | |
39 import org.tmatesoft.hg.repo.HgStatusCollector.ManifestRevisionInspector; | 42 import org.tmatesoft.hg.repo.HgStatusCollector.ManifestRevisionInspector; |
40 import org.tmatesoft.hg.util.ByteChannel; | 43 import org.tmatesoft.hg.util.ByteChannel; |
41 import org.tmatesoft.hg.util.CancelledException; | 44 import org.tmatesoft.hg.util.CancelledException; |
42 import org.tmatesoft.hg.util.FileIterator; | 45 import org.tmatesoft.hg.util.FileIterator; |
46 import org.tmatesoft.hg.util.FileWalker; | |
43 import org.tmatesoft.hg.util.Path; | 47 import org.tmatesoft.hg.util.Path; |
44 import org.tmatesoft.hg.util.PathPool; | 48 import org.tmatesoft.hg.util.PathPool; |
45 import org.tmatesoft.hg.util.PathRewrite; | 49 import org.tmatesoft.hg.util.PathRewrite; |
46 | 50 |
47 /** | 51 /** |
110 isTipBase = true; | 114 isTipBase = true; |
111 } else { | 115 } else { |
112 isTipBase = baseRevision == repo.getChangelog().getLastRevision(); | 116 isTipBase = baseRevision == repo.getChangelog().getLastRevision(); |
113 } | 117 } |
114 HgStatusCollector.ManifestRevisionInspector collect = null; | 118 HgStatusCollector.ManifestRevisionInspector collect = null; |
115 Set<String> baseRevFiles = Collections.emptySet(); | 119 Set<String> baseRevFiles = Collections.emptySet(); // files from base revision not affected by status calculation |
116 if (!isTipBase) { | 120 if (!isTipBase) { |
117 if (baseRevisionCollector != null) { | 121 if (baseRevisionCollector != null) { |
118 collect = baseRevisionCollector.raw(baseRevision); | 122 collect = baseRevisionCollector.raw(baseRevision); |
119 } else { | 123 } else { |
120 collect = new HgStatusCollector.ManifestRevisionInspector(null, null); | 124 collect = new HgStatusCollector.ManifestRevisionInspector(null, null); |
128 } | 132 } |
129 repoWalker.reset(); | 133 repoWalker.reset(); |
130 final PathPool pp = getPathPool(); | 134 final PathPool pp = getPathPool(); |
131 while (repoWalker.hasNext()) { | 135 while (repoWalker.hasNext()) { |
132 repoWalker.next(); | 136 repoWalker.next(); |
133 Path fname = repoWalker.name(); | 137 Path fname = pp.path(repoWalker.name()); |
134 File f = repoWalker.file(); | 138 File f = repoWalker.file(); |
135 if (hgIgnore.isIgnored(fname)) { | 139 assert f.isFile(); |
136 inspector.ignored(pp.path(fname)); | 140 if (!f.exists()) { |
137 } else if (knownEntries.remove(fname.toString())) { | 141 // file coming from iterator doesn't exist. |
142 if (knownEntries.remove(fname.toString())) { | |
143 if (getDirstate().checkRemoved(fname) == null) { | |
144 inspector.missing(fname); | |
145 } else { | |
146 inspector.removed(fname); | |
147 } | |
148 // do not report it as removed later | |
149 if (collect != null) { | |
150 baseRevFiles.remove(fname.toString()); | |
151 } | |
152 } else { | |
153 // chances are it was known in baseRevision. We may rely | |
154 // that later iteration over baseRevFiles leftovers would yield correct Removed, | |
155 // but it doesn't hurt to be explicit (provided we know fname *is* inScope of the FileIterator | |
156 if (collect != null && baseRevFiles.remove(fname.toString())) { | |
157 inspector.removed(fname); | |
158 } else { | |
159 // not sure I shall report such files (i.e. arbitrary name coming from FileIterator) | |
160 // as unknown. Command-line HG aborts "system can't find the file specified" | |
161 // in similar case (against wc), or just gives nothing if --change <rev> is specified. | |
162 // however, as it's unlikely to get unexisting files from FileIterator, and | |
163 // its better to see erroneous file status rather than not to see any (which is too easy | |
164 // to overlook), I think unknown() is reasonable approach here | |
165 inspector.unknown(fname); | |
166 } | |
167 } | |
168 continue; | |
169 } | |
170 if (knownEntries.remove(fname.toString())) { | |
171 // tracked file. | |
138 // modified, added, removed, clean | 172 // modified, added, removed, clean |
139 if (collect != null) { // need to check against base revision, not FS file | 173 if (collect != null) { // need to check against base revision, not FS file |
140 checkLocalStatusAgainstBaseRevision(baseRevFiles, collect, baseRevision, fname, f, inspector); | 174 checkLocalStatusAgainstBaseRevision(baseRevFiles, collect, baseRevision, fname, f, inspector); |
141 baseRevFiles.remove(fname.toString()); | |
142 } else { | 175 } else { |
143 checkLocalStatusAgainstFile(fname, f, inspector); | 176 checkLocalStatusAgainstFile(fname, f, inspector); |
144 } | 177 } |
145 } else { | 178 } else { |
146 inspector.unknown(pp.path(fname)); | 179 if (hgIgnore.isIgnored(fname)) { // hgignore shall be consulted only for non-tracked files |
180 inspector.ignored(fname); | |
181 } else { | |
182 inspector.unknown(fname); | |
183 } | |
184 // the file is not tracked. Even if it's known at baseRevision, we don't need to remove it | |
185 // from baseRevFiles, it might need to be reported as removed as well (cmdline client does | |
186 // yield two statuses for the same file) | |
147 } | 187 } |
148 } | 188 } |
149 if (collect != null) { | 189 if (collect != null) { |
150 for (String r : baseRevFiles) { | 190 for (String r : baseRevFiles) { |
151 inspector.removed(pp.path(r)); | 191 final Path fromBase = pp.path(r); |
192 if (repoWalker.inScope(fromBase)) { | |
193 inspector.removed(fromBase); | |
194 } | |
152 } | 195 } |
153 } | 196 } |
154 for (String m : knownEntries) { | 197 for (String m : knownEntries) { |
198 if (!repoWalker.inScope(pp.path(m))) { | |
199 // do not report as missing/removed those FileIterator doesn't care about. | |
200 continue; | |
201 } | |
155 // missing known file from a working dir | 202 // missing known file from a working dir |
156 if (getDirstate().checkRemoved(m) == null) { | 203 if (getDirstate().checkRemoved(m) == null) { |
157 // not removed from the repository = 'deleted' | 204 // not removed from the repository = 'deleted' |
158 inspector.missing(pp.path(m)); | 205 inspector.missing(pp.path(m)); |
159 } else { | 206 } else { |
215 // merged: was not known, report as added? | 262 // merged: was not known, report as added? |
216 if ((r = getDirstate().checkNormal(fname)) != null) { | 263 if ((r = getDirstate().checkNormal(fname)) != null) { |
217 try { | 264 try { |
218 Path origin = HgStatusCollector.getOriginIfCopy(repo, fname, baseRevNames, baseRevision); | 265 Path origin = HgStatusCollector.getOriginIfCopy(repo, fname, baseRevNames, baseRevision); |
219 if (origin != null) { | 266 if (origin != null) { |
220 inspector.copied(getPathPool().path(origin), getPathPool().path(fname)); | 267 inspector.copied(getPathPool().path(origin), fname); |
221 return; | 268 return; |
222 } | 269 } |
223 } catch (HgDataStreamException ex) { | 270 } catch (HgDataStreamException ex) { |
224 ex.printStackTrace(); | 271 ex.printStackTrace(); |
225 // FIXME report to a mediator, continue status collection | 272 // FIXME report to a mediator, continue status collection |
226 } | 273 } |
227 } else if ((r = getDirstate().checkAdded(fname)) != null) { | 274 } else if ((r = getDirstate().checkAdded(fname)) != null) { |
228 if (r.name2 != null && baseRevNames.contains(r.name2)) { | 275 if (r.name2 != null && baseRevNames.contains(r.name2)) { |
229 baseRevNames.remove(r.name2); // XXX surely I shall not report rename source as Removed? | 276 baseRevNames.remove(r.name2); // XXX surely I shall not report rename source as Removed? |
230 inspector.copied(getPathPool().path(r.name2), getPathPool().path(fname)); | 277 inspector.copied(getPathPool().path(r.name2), fname); |
231 return; | 278 return; |
232 } | 279 } |
233 // fall-through, report as added | 280 // fall-through, report as added |
234 } else if (getDirstate().checkRemoved(fname) != null) { | 281 } else if (getDirstate().checkRemoved(fname) != null) { |
235 // removed: removed file was not known at the time of baseRevision, and we should not report it as removed | 282 // removed: removed file was not known at the time of baseRevision, and we should not report it as removed |
236 return; | 283 return; |
237 } | 284 } |
238 inspector.added(getPathPool().path(fname)); | 285 inspector.added(fname); |
239 } else { | 286 } else { |
240 // was known; check whether clean or modified | 287 // was known; check whether clean or modified |
241 // when added - seems to be the case of a file added once again, hence need to check if content is different | 288 // when added - seems to be the case of a file added once again, hence need to check if content is different |
242 if ((r = getDirstate().checkNormal(fname)) != null || (r = getDirstate().checkMerged(fname)) != null || (r = getDirstate().checkAdded(fname)) != null) { | 289 if ((r = getDirstate().checkNormal(fname)) != null || (r = getDirstate().checkMerged(fname)) != null || (r = getDirstate().checkAdded(fname)) != null) { |
243 // either clean or modified | 290 // either clean or modified |
244 HgDataFile fileNode = repo.getFileNode(fname); | 291 HgDataFile fileNode = repo.getFileNode(fname); |
245 final int lengthAtRevision = fileNode.length(nid1); | 292 final int lengthAtRevision = fileNode.length(nid1); |
246 if (r.size /* XXX File.length() ?! */ != lengthAtRevision || flags != todoGenerateFlags(fname /*java.io.File*/)) { | 293 if (r.size /* XXX File.length() ?! */ != lengthAtRevision || flags != todoGenerateFlags(fname /*java.io.File*/)) { |
247 inspector.modified(getPathPool().path(fname)); | 294 inspector.modified(fname); |
248 } else { | 295 } else { |
249 // check actual content to see actual changes | 296 // check actual content to see actual changes |
250 if (areTheSame(f, fileNode, fileNode.getLocalRevision(nid1))) { | 297 if (areTheSame(f, fileNode, fileNode.getLocalRevision(nid1))) { |
251 inspector.clean(getPathPool().path(fname)); | 298 inspector.clean(fname); |
252 } else { | 299 } else { |
253 inspector.modified(getPathPool().path(fname)); | 300 inspector.modified(fname); |
254 } | 301 } |
255 } | 302 } |
256 } | 303 baseRevNames.remove(fname.toString()); // consumed, processed, handled. |
257 // only those left in idsMap after processing are reported as removed | 304 } else if (getDirstate().checkRemoved(fname) != null) { |
305 // was known, and now marked as removed, report it right away, do not rely on baseRevNames processing later | |
306 inspector.removed(fname); | |
307 baseRevNames.remove(fname.toString()); // consumed, processed, handled. | |
308 } | |
309 // only those left in baseRevNames after processing are reported as removed | |
258 } | 310 } |
259 | 311 |
260 // TODO think over if content comparison may be done more effectively by e.g. calculating nodeid for a local file and comparing it with nodeid from manifest | 312 // TODO think over if content comparison may be done more effectively by e.g. calculating nodeid for a local file and comparing it with nodeid from manifest |
261 // we don't need to tell exact difference, hash should be enough to detect difference, and it doesn't involve reading historical file content, and it's relatively | 313 // we don't need to tell exact difference, hash should be enough to detect difference, and it doesn't involve reading historical file content, and it's relatively |
262 // cheap to calc hash on a file (no need to keep it completely in memory). OTOH, if I'm right that the next approach is used for nodeids: | 314 // cheap to calc hash on a file (no need to keep it completely in memory). OTOH, if I'm right that the next approach is used for nodeids: |
287 FileInputStream fis = null; | 339 FileInputStream fis = null; |
288 try { | 340 try { |
289 try { | 341 try { |
290 fis = new FileInputStream(f); | 342 fis = new FileInputStream(f); |
291 FileChannel fc = fis.getChannel(); | 343 FileChannel fc = fis.getChannel(); |
292 ByteBuffer fb = ByteBuffer.allocate(min(data.length * 2 /*to fit couple of lines appended*/, 8192)); | 344 ByteBuffer fb = ByteBuffer.allocate(min(1 + data.length * 2 /*to fit couple of lines appended; never zero*/, 8192)); |
293 class Check implements ByteChannel { | 345 class Check implements ByteChannel { |
294 final boolean debug = false; // XXX may want to add global variable to allow clients to turn | 346 final boolean debug = false; // XXX may want to add global variable to allow clients to turn |
295 boolean sameSoFar = true; | 347 boolean sameSoFar = true; |
296 int x = 0; | 348 int x = 0; |
297 | 349 |
345 private static String todoGenerateFlags(Path fname) { | 397 private static String todoGenerateFlags(Path fname) { |
346 // FIXME implement | 398 // FIXME implement |
347 return null; | 399 return null; |
348 } | 400 } |
349 | 401 |
402 @Experimental(reason="There's intention to support status query with multiple files/dirs, API might get changed") | |
403 public static HgWorkingCopyStatusCollector create(HgRepository hgRepo, Path file) { | |
404 FileIterator fi = file.isDirectory() ? new DirFileIterator(hgRepo, file) : new FileListIterator(hgRepo.getRepositoryRoot().getParentFile(), file); | |
405 return new HgWorkingCopyStatusCollector(hgRepo, fi); | |
406 } | |
407 | |
408 private static class FileListIterator implements FileIterator { | |
409 private final File dir; | |
410 private final Path[] paths; | |
411 private int index; | |
412 private File nextFile; // cache file() in case it's called more than once | |
413 | |
414 public FileListIterator(File startDir, Path... files) { | |
415 dir = startDir; | |
416 paths = files; | |
417 reset(); | |
418 } | |
419 | |
420 public void reset() { | |
421 index = -1; | |
422 nextFile = null; | |
423 } | |
424 | |
425 public boolean hasNext() { | |
426 return paths.length > 0 && index < paths.length-1; | |
427 } | |
428 | |
429 public void next() { | |
430 index++; | |
431 if (index == paths.length) { | |
432 throw new NoSuchElementException(); | |
433 } | |
434 nextFile = new File(dir, paths[index].toString()); | |
435 } | |
436 | |
437 public Path name() { | |
438 return paths[index]; | |
439 } | |
440 | |
441 public File file() { | |
442 return nextFile; | |
443 } | |
444 | |
445 public boolean inScope(Path file) { | |
446 for (int i = 0; i < paths.length; i++) { | |
447 if (paths[i].equals(file)) { | |
448 return true; | |
449 } | |
450 } | |
451 return false; | |
452 } | |
453 } | |
454 | |
455 private static class DirFileIterator implements FileIterator { | |
456 private final Path dirOfInterest; | |
457 private final FileWalker walker; | |
458 | |
459 public DirFileIterator(HgRepository hgRepo, Path directory) { | |
460 dirOfInterest = directory; | |
461 File dir = hgRepo.getRepositoryRoot().getParentFile(); | |
462 Path.Source pathSrc = new Path.SimpleSource(new PathRewrite.Composite(new RelativePathRewrite(dir), hgRepo.getToRepoPathHelper())); | |
463 walker = new FileWalker(new File(dir, directory.toString()), pathSrc); | |
464 } | |
465 | |
466 public void reset() { | |
467 walker.reset(); | |
468 } | |
469 | |
470 public boolean hasNext() { | |
471 return walker.hasNext(); | |
472 } | |
473 | |
474 public void next() { | |
475 walker.next(); | |
476 } | |
477 | |
478 public Path name() { | |
479 return walker.name(); | |
480 } | |
481 | |
482 public File file() { | |
483 return walker.file(); | |
484 } | |
485 | |
486 public boolean inScope(Path file) { | |
487 return file.toString().startsWith(dirOfInterest.toString()); | |
488 } | |
489 } | |
350 } | 490 } |