Mercurial > jhg
diff 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 |
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java Wed May 25 05:13:43 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java Wed May 25 12:16:24 2011 +0200 @@ -28,6 +28,7 @@ import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.Collections; +import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeSet; @@ -35,11 +36,14 @@ import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.ByteArrayChannel; +import org.tmatesoft.hg.internal.Experimental; import org.tmatesoft.hg.internal.FilterByteChannel; +import org.tmatesoft.hg.internal.RelativePathRewrite; import org.tmatesoft.hg.repo.HgStatusCollector.ManifestRevisionInspector; import org.tmatesoft.hg.util.ByteChannel; import org.tmatesoft.hg.util.CancelledException; import org.tmatesoft.hg.util.FileIterator; +import org.tmatesoft.hg.util.FileWalker; import org.tmatesoft.hg.util.Path; import org.tmatesoft.hg.util.PathPool; import org.tmatesoft.hg.util.PathRewrite; @@ -112,7 +116,7 @@ isTipBase = baseRevision == repo.getChangelog().getLastRevision(); } HgStatusCollector.ManifestRevisionInspector collect = null; - Set<String> baseRevFiles = Collections.emptySet(); + Set<String> baseRevFiles = Collections.emptySet(); // files from base revision not affected by status calculation if (!isTipBase) { if (baseRevisionCollector != null) { collect = baseRevisionCollector.raw(baseRevision); @@ -130,28 +134,71 @@ final PathPool pp = getPathPool(); while (repoWalker.hasNext()) { repoWalker.next(); - Path fname = repoWalker.name(); + Path fname = pp.path(repoWalker.name()); File f = repoWalker.file(); - if (hgIgnore.isIgnored(fname)) { - inspector.ignored(pp.path(fname)); - } else if (knownEntries.remove(fname.toString())) { + assert f.isFile(); + if (!f.exists()) { + // file coming from iterator doesn't exist. + if (knownEntries.remove(fname.toString())) { + if (getDirstate().checkRemoved(fname) == null) { + inspector.missing(fname); + } else { + inspector.removed(fname); + } + // do not report it as removed later + if (collect != null) { + baseRevFiles.remove(fname.toString()); + } + } else { + // chances are it was known in baseRevision. We may rely + // that later iteration over baseRevFiles leftovers would yield correct Removed, + // but it doesn't hurt to be explicit (provided we know fname *is* inScope of the FileIterator + if (collect != null && baseRevFiles.remove(fname.toString())) { + inspector.removed(fname); + } else { + // not sure I shall report such files (i.e. arbitrary name coming from FileIterator) + // as unknown. Command-line HG aborts "system can't find the file specified" + // in similar case (against wc), or just gives nothing if --change <rev> is specified. + // however, as it's unlikely to get unexisting files from FileIterator, and + // its better to see erroneous file status rather than not to see any (which is too easy + // to overlook), I think unknown() is reasonable approach here + inspector.unknown(fname); + } + } + continue; + } + if (knownEntries.remove(fname.toString())) { + // tracked file. // modified, added, removed, clean if (collect != null) { // need to check against base revision, not FS file checkLocalStatusAgainstBaseRevision(baseRevFiles, collect, baseRevision, fname, f, inspector); - baseRevFiles.remove(fname.toString()); } else { checkLocalStatusAgainstFile(fname, f, inspector); } } else { - inspector.unknown(pp.path(fname)); + if (hgIgnore.isIgnored(fname)) { // hgignore shall be consulted only for non-tracked files + inspector.ignored(fname); + } else { + inspector.unknown(fname); + } + // the file is not tracked. Even if it's known at baseRevision, we don't need to remove it + // from baseRevFiles, it might need to be reported as removed as well (cmdline client does + // yield two statuses for the same file) } } if (collect != null) { for (String r : baseRevFiles) { - inspector.removed(pp.path(r)); + final Path fromBase = pp.path(r); + if (repoWalker.inScope(fromBase)) { + inspector.removed(fromBase); + } } } for (String m : knownEntries) { + if (!repoWalker.inScope(pp.path(m))) { + // do not report as missing/removed those FileIterator doesn't care about. + continue; + } // missing known file from a working dir if (getDirstate().checkRemoved(m) == null) { // not removed from the repository = 'deleted' @@ -217,7 +264,7 @@ try { Path origin = HgStatusCollector.getOriginIfCopy(repo, fname, baseRevNames, baseRevision); if (origin != null) { - inspector.copied(getPathPool().path(origin), getPathPool().path(fname)); + inspector.copied(getPathPool().path(origin), fname); return; } } catch (HgDataStreamException ex) { @@ -227,7 +274,7 @@ } else if ((r = getDirstate().checkAdded(fname)) != null) { if (r.name2 != null && baseRevNames.contains(r.name2)) { baseRevNames.remove(r.name2); // XXX surely I shall not report rename source as Removed? - inspector.copied(getPathPool().path(r.name2), getPathPool().path(fname)); + inspector.copied(getPathPool().path(r.name2), fname); return; } // fall-through, report as added @@ -235,7 +282,7 @@ // removed: removed file was not known at the time of baseRevision, and we should not report it as removed return; } - inspector.added(getPathPool().path(fname)); + inspector.added(fname); } else { // was known; check whether clean or modified // when added - seems to be the case of a file added once again, hence need to check if content is different @@ -244,17 +291,22 @@ HgDataFile fileNode = repo.getFileNode(fname); final int lengthAtRevision = fileNode.length(nid1); if (r.size /* XXX File.length() ?! */ != lengthAtRevision || flags != todoGenerateFlags(fname /*java.io.File*/)) { - inspector.modified(getPathPool().path(fname)); + inspector.modified(fname); } else { // check actual content to see actual changes if (areTheSame(f, fileNode, fileNode.getLocalRevision(nid1))) { - inspector.clean(getPathPool().path(fname)); + inspector.clean(fname); } else { - inspector.modified(getPathPool().path(fname)); + inspector.modified(fname); } } + baseRevNames.remove(fname.toString()); // consumed, processed, handled. + } else if (getDirstate().checkRemoved(fname) != null) { + // was known, and now marked as removed, report it right away, do not rely on baseRevNames processing later + inspector.removed(fname); + baseRevNames.remove(fname.toString()); // consumed, processed, handled. } - // only those left in idsMap after processing are reported as removed + // only those left in baseRevNames after processing are reported as removed } // 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 @@ -289,7 +341,7 @@ try { fis = new FileInputStream(f); FileChannel fc = fis.getChannel(); - ByteBuffer fb = ByteBuffer.allocate(min(data.length * 2 /*to fit couple of lines appended*/, 8192)); + ByteBuffer fb = ByteBuffer.allocate(min(1 + data.length * 2 /*to fit couple of lines appended; never zero*/, 8192)); class Check implements ByteChannel { final boolean debug = false; // XXX may want to add global variable to allow clients to turn boolean sameSoFar = true; @@ -347,4 +399,92 @@ return null; } + @Experimental(reason="There's intention to support status query with multiple files/dirs, API might get changed") + public static HgWorkingCopyStatusCollector create(HgRepository hgRepo, Path file) { + FileIterator fi = file.isDirectory() ? new DirFileIterator(hgRepo, file) : new FileListIterator(hgRepo.getRepositoryRoot().getParentFile(), file); + return new HgWorkingCopyStatusCollector(hgRepo, fi); + } + + private static class FileListIterator implements FileIterator { + private final File dir; + private final Path[] paths; + private int index; + private File nextFile; // cache file() in case it's called more than once + + public FileListIterator(File startDir, Path... files) { + dir = startDir; + paths = files; + reset(); + } + + public void reset() { + index = -1; + nextFile = null; + } + + public boolean hasNext() { + return paths.length > 0 && index < paths.length-1; + } + + public void next() { + index++; + if (index == paths.length) { + throw new NoSuchElementException(); + } + nextFile = new File(dir, paths[index].toString()); + } + + public Path name() { + return paths[index]; + } + + public File file() { + return nextFile; + } + + public boolean inScope(Path file) { + for (int i = 0; i < paths.length; i++) { + if (paths[i].equals(file)) { + return true; + } + } + return false; + } + } + + private static class DirFileIterator implements FileIterator { + private final Path dirOfInterest; + private final FileWalker walker; + + public DirFileIterator(HgRepository hgRepo, Path directory) { + dirOfInterest = directory; + File dir = hgRepo.getRepositoryRoot().getParentFile(); + Path.Source pathSrc = new Path.SimpleSource(new PathRewrite.Composite(new RelativePathRewrite(dir), hgRepo.getToRepoPathHelper())); + walker = new FileWalker(new File(dir, directory.toString()), pathSrc); + } + + public void reset() { + walker.reset(); + } + + public boolean hasNext() { + return walker.hasNext(); + } + + public void next() { + walker.next(); + } + + public Path name() { + return walker.name(); + } + + public File file() { + return walker.file(); + } + + public boolean inScope(Path file) { + return file.toString().startsWith(dirOfInterest.toString()); + } + } }