# HG changeset patch # User Artem Tikhomirov # Date 1295585803 -3600 # Node ID 19e9e220bf684f1fb6036fa20678f7046d72b14e # Parent a47530a2ea127a544535270192167e4257506643 Convenient commands constitute hi-level API. org.tmatesoft namespace, GPL2 statement diff -r a47530a2ea12 -r 19e9e220bf68 COPYING --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/COPYING Fri Jan 21 05:56:43 2011 +0100 @@ -0,0 +1,14 @@ +Copyright (C) 2010-2011 TMate Software Ltd + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +For information on how to redistribute this software under +the terms of a license other than GNU General Public License +contact TMate Software at support@svnkit.com \ No newline at end of file diff -r a47530a2ea12 -r 19e9e220bf68 TODO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TODO Fri Jan 21 05:56:43 2011 +0100 @@ -0,0 +1,21 @@ +Read-only support, version 1.0 +============================== +Committed: +* hg log + user, date, branch, limit + filename(multiple?) + +* hg manifest (aka ls) + +* hg status + +* hg cat + +Proposed: +- LogCommand.revision(int... rev)+ to walk selected revisions only (list->sort(array) on execute, binary search) +- LogCommand.before(Date date) and .after() +- LogCommand.match() to specify pattern, no selected file()s only? + +Read-only support, version 1.1 +============================== + diff -r a47530a2ea12 -r 19e9e220bf68 design.txt --- a/design.txt Tue Jan 18 18:42:50 2011 +0100 +++ b/design.txt Fri Jan 21 05:56:43 2011 +0100 @@ -49,6 +49,11 @@ ??? http://mercurial.selenic.com/wiki/Manifest says "Multiple changesets may refer to the same manifest revision". To me, each changeset changes repository, hence manifest should update nodeids of the files it lists, effectively creating new manifest revision. +? hg status, compare revision and local file with kw expansion and eol extension +? subrepos in log, status (-S) and manifest commands + +Commands to get CommandContext where they may share various caches (e.g. StatusCollector) + >>>> Effective file read/data access ReadOperation, Revlog does: repo.getFileSystem().run(this.file, new ReadOperation(), long start=0, long end = -1) ReadOperation gets buffer (of whatever size, as decided by FS impl), parses it and then reports if needs more data. diff -r a47530a2ea12 -r 19e9e220bf68 src/com/tmate/hgkit/console/Manifest.java --- a/src/com/tmate/hgkit/console/Manifest.java Tue Jan 18 18:42:50 2011 +0100 +++ b/src/com/tmate/hgkit/console/Manifest.java Fri Jan 21 05:56:43 2011 +0100 @@ -5,6 +5,10 @@ import static com.tmate.hgkit.ll.HgRepository.TIP; +import org.tmatesoft.hg.core.Path; +import org.tmatesoft.hg.core.RepositoryTreeWalker; +import org.tmatesoft.hg.core.LogCommand.FileRevision; + import com.tmate.hgkit.fs.RepositoryLookup; import com.tmate.hgkit.ll.HgManifest; import com.tmate.hgkit.ll.HgRepository; @@ -25,8 +29,26 @@ return; } System.out.println(hgRepo.getLocation()); - HgManifest.Inspector insp = new Dump(); - hgRepo.getManifest().walk(0, TIP, insp); + hgRepo.getManifest().walk(0, TIP, new Dump()); + // + new RepositoryTreeWalker(hgRepo).dirs(true).walk(new RepositoryTreeWalker.Handler() { + + public void begin(Nodeid manifestRevision) { + System.out.println(">> " + manifestRevision); + } + public void dir(Path p) { + System.out.println(p); + } + public void file(FileRevision fileRevision) { + System.out.print(fileRevision.getRevision());; + System.out.print(" "); + System.out.println(fileRevision.getPath()); + } + + public void end(Nodeid manifestRevision) { + System.out.println(); + } + }); } public static final class Dump implements HgManifest.Inspector { diff -r a47530a2ea12 -r 19e9e220bf68 src/com/tmate/hgkit/ll/Changeset.java --- a/src/com/tmate/hgkit/ll/Changeset.java Tue Jan 18 18:42:50 2011 +0100 +++ b/src/com/tmate/hgkit/ll/Changeset.java Fri Jan 21 05:56:43 2011 +0100 @@ -95,6 +95,15 @@ sb.append("}"); return sb.toString(); } + + @Override + public Changeset clone() { + try { + return (Changeset) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new InternalError(ex.toString()); + } + } public static Changeset parse(byte[] data, int offset, int length) { Changeset rv = new Changeset(); diff -r a47530a2ea12 -r 19e9e220bf68 src/com/tmate/hgkit/ll/HgRepository.java --- a/src/com/tmate/hgkit/ll/HgRepository.java Tue Jan 18 18:42:50 2011 +0100 +++ b/src/com/tmate/hgkit/ll/HgRepository.java Fri Jan 21 05:56:43 2011 +0100 @@ -3,8 +3,12 @@ */ package com.tmate.hgkit.ll; +import org.tmatesoft.hg.core.Path; +import org.tmatesoft.hg.util.PathRewrite; + /** + * Shall be as state-less as possible, all the caching happens outside the repo, in commands/walkers * @author artem */ public abstract class HgRepository { @@ -57,8 +61,11 @@ protected abstract HgTags createTags(); public abstract HgDataFile getFileNode(String path); + public abstract HgDataFile getFileNode(Path path); public abstract String getLocation(); + + public abstract PathRewrite getPathHelper(); protected abstract String toStoragePath(String path, boolean isData); diff -r a47530a2ea12 -r 19e9e220bf68 src/com/tmate/hgkit/ll/LocalHgRepo.java --- a/src/com/tmate/hgkit/ll/LocalHgRepo.java Tue Jan 18 18:42:50 2011 +0100 +++ b/src/com/tmate/hgkit/ll/LocalHgRepo.java Fri Jan 21 05:56:43 2011 +0100 @@ -13,6 +13,9 @@ import java.util.HashMap; import java.util.TreeSet; +import org.tmatesoft.hg.core.Path; +import org.tmatesoft.hg.util.PathRewrite; + import com.tmate.hgkit.fs.DataAccessProvider; import com.tmate.hgkit.fs.FileWalker; @@ -24,6 +27,12 @@ private File repoDir; // .hg folder private final String repoLocation; private final DataAccessProvider dataAccess; + private final PathRewrite normalizePath = new PathRewrite() { + + public String rewrite(String path) { + return normalize(path); + } + }; public LocalHgRepo(String repositoryPath) { setInvalid(true); @@ -102,6 +111,16 @@ return new HgDataFile(this, nPath, content); } + @Override + public HgDataFile getFileNode(Path path) { + return getFileNode(path.toString()); + } + + @Override + public PathRewrite getPathHelper() { + return normalizePath; + } + private boolean revlogv1; private boolean store; private boolean fncache; diff -r a47530a2ea12 -r 19e9e220bf68 src/com/tmate/hgkit/ll/StatusCollector.java --- a/src/com/tmate/hgkit/ll/StatusCollector.java Tue Jan 18 18:42:50 2011 +0100 +++ b/src/com/tmate/hgkit/ll/StatusCollector.java Fri Jan 21 05:56:43 2011 +0100 @@ -30,6 +30,10 @@ cache.put(-1, emptyFakeState); } + public HgRepository getRepo() { + return repo; + } + private ManifestRevisionInspector get(int rev) { ManifestRevisionInspector i = cache.get(rev); if (i == null) { @@ -58,9 +62,14 @@ if (rev1 == rev2) { throw new IllegalArgumentException(); } + if (inspector == null) { + throw new IllegalArgumentException(); + } + if (inspector instanceof Record) { + ((Record) inspector).init(rev1, rev2, this); + } // in fact, rev1 and rev2 are often next (or close) to each other, // thus, we can optimize Manifest reads here (manifest.walk(rev1, rev2)) - ManifestRevisionInspector r1, r2; if (!cache.containsKey(rev1) && !cache.containsKey(rev2) && Math.abs(rev1 - rev2) < 5 /*subjective equivalent of 'close enough'*/) { int minRev = rev1 < rev2 ? rev1 : rev2; @@ -119,6 +128,35 @@ private List modified, added, removed, clean, missing, unknown, ignored; private Map copied; + private int startRev, endRev; + private StatusCollector statusHelper; + + // XXX StatusCollector may additionally initialize Record instance to speed lookup of changed file revisions + // here I need access to ManifestRevisionInspector via #raw(). Perhaps, non-static class (to get + // implicit reference to StatusCollector) may be better? + // Since users may want to reuse Record instance we've once created (and initialized), we need to + // ensure functionality is correct for each/any call (#walk checks instanceof Record and fixes it up) + // Perhaps, distinct helper (sc.getRevisionHelper().nodeid(fname)) would be better, just not clear + // how to supply [start..end] values there easily + /*package-local*/void init(int startRevision, int endRevision, StatusCollector self) { + startRev = startRevision; + endRev = endRevision; + statusHelper = self; + } + + public Nodeid nodeidBeforeChange(String fname) { + if ((modified == null || !modified.contains(fname)) && (removed == null || !removed.contains(fname))) { + return null; + } + return statusHelper.raw(startRev).nodeid(startRev, fname); + } + public Nodeid nodeidAfterChange(String fname) { + if ((modified == null || !modified.contains(fname)) && (added == null || !added.contains(fname))) { + return null; + } + return statusHelper.raw(endRev).nodeid(endRev, fname); + } + public List getModified() { return proper(modified); } @@ -208,6 +246,7 @@ } } + // XXX in fact, indexed access brings more trouble than benefits, get rid of it? Distinct instance per revision is good enough public /*XXX private, actually. Made public unless repo.statusLocal finds better place*/ static final class ManifestRevisionInspector implements HgManifest.Inspector { private final HashMap[] idsMap; private final HashMap[] flagsMap; diff -r a47530a2ea12 -r 19e9e220bf68 src/org/tmatesoft/hg/core/Cset.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/Cset.java Fri Jan 21 05:56:43 2011 +0100 @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2011 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@svnkit.com + */ +package org.tmatesoft.hg.core; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.tmatesoft.hg.core.LogCommand.FileRevision; +import org.tmatesoft.hg.util.PathPool; + +import com.tmate.hgkit.ll.Changeset; +import com.tmate.hgkit.ll.HgRepository; +import com.tmate.hgkit.ll.Nodeid; +import com.tmate.hgkit.ll.StatusCollector; + +/** + * TODO rename to Changeset along with original Changeset moved to .repo and renamed to HgChangeset? + * Not thread-safe, don't try to read from different threads + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class Cset implements Cloneable { + private final StatusCollector statusHelper; + private final PathPool pathHelper; + + // + private Changeset changeset; + private Nodeid nodeid; + + // + private List modifiedFiles, addedFiles; + private List deletedFiles; + private int revNumber; + + // XXX consider CommandContext with StatusCollector, PathPool etc. Commands optionally get CC through a cons or create new + // and pass it around + /*package-local*/Cset(StatusCollector statusCollector, PathPool pathPool) { + statusHelper = statusCollector; + pathHelper = pathPool; + } + + /*package-local*/ + void init(int localRevNumber, Nodeid nid, Changeset rawChangeset) { + revNumber = localRevNumber; + nodeid = nid; + changeset = rawChangeset; + } + + public String getUser() { + return changeset.user(); + } + public String getComment() { + return changeset.comment(); + } + public String getBranch() { + return changeset.branch(); + } + public String getDate() { + return changeset.dateString(); + } + + public List getAffectedFiles() { + ArrayList rv = new ArrayList(changeset.files().size()); + for (String name : changeset.files()) { + rv.add(pathHelper.path(name)); + } + return rv; + } + + public List getModifiedFiles() { + if (modifiedFiles == null) { + initFileChanges(); + } + return modifiedFiles; + } + + public List getAddedFiles() { + if (addedFiles == null) { + initFileChanges(); + } + return addedFiles; + } + + public List getRemovedFiles() { + if (deletedFiles == null) { + initFileChanges(); + } + return deletedFiles; + } + + @Override + public Cset clone() { + try { + Cset copy = (Cset) super.clone(); + copy.changeset = changeset.clone(); + return copy; + } catch (CloneNotSupportedException ex) { + throw new InternalError(ex.toString()); + } + } + + private /*synchronized*/ void initFileChanges() { + ArrayList deleted = new ArrayList(); + ArrayList modified = new ArrayList(); + ArrayList added = new ArrayList(); + StatusCollector.Record r = new StatusCollector.Record(); + statusHelper.change(revNumber, r); + final HgRepository repo = statusHelper.getRepo(); + for (String s : r.getModified()) { + Path p = pathHelper.path(s); + Nodeid nid = r.nodeidAfterChange(s); + if (nid == null) { + throw new IllegalArgumentException(); + } + modified.add(new FileRevision(repo, nid, p)); + } + for (String s : r.getAdded()) { + Path p = pathHelper.path(s); + Nodeid nid = r.nodeidAfterChange(s); + if (nid == null) { + throw new IllegalArgumentException(); + } + added.add(new FileRevision(repo, nid, p)); + } + for (String s : r.getRemoved()) { + deleted.add(pathHelper.path(s)); + } + modified.trimToSize(); + added.trimToSize(); + deleted.trimToSize(); + modifiedFiles = Collections.unmodifiableList(modified); + addedFiles = Collections.unmodifiableList(added); + deletedFiles = Collections.unmodifiableList(deleted); + } +} \ No newline at end of file diff -r a47530a2ea12 -r 19e9e220bf68 src/org/tmatesoft/hg/core/LogCommand.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/LogCommand.java Fri Jan 21 05:56:43 2011 +0100 @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2011 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@svnkit.com + */ +package org.tmatesoft.hg.core; + +import static com.tmate.hgkit.ll.HgRepository.TIP; + +import java.util.Calendar; +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.tmatesoft.hg.util.PathPool; + +import com.tmate.hgkit.ll.Changeset; +import com.tmate.hgkit.ll.HgRepository; +import com.tmate.hgkit.ll.Nodeid; +import com.tmate.hgkit.ll.StatusCollector; + +/** + *
+ *   new LogCommand().limit(20).branch("maintenance-2.1").user("me").execute();
+ * 
+ * Not thread-safe (each thread has to use own {@link LogCommand} instance). + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class LogCommand implements Changeset.Inspector { + + private final HgRepository repo; + private Set users; + private Set branches; + private int limit = 0, count = 0; + private int startRev = 0, endRev = TIP; + private Handler delegate; + private Calendar date; + private Cset changeset; + + public LogCommand(HgRepository hgRepo) { + this.repo = hgRepo; + } + + /** + * Limit search to specified user. Multiple user names may be specified. + * @param user - full or partial name of the user, case-insensitive, non-null. + * @return this instance for convenience + */ + public LogCommand user(String user) { + if (user == null) { + throw new IllegalArgumentException(); + } + if (users == null) { + users = new TreeSet(); + } + users.add(user.toLowerCase()); + return this; + } + + /** + * Limit search to specified branch. Multiple branch specification possible (changeset from any of these + * would be included in result). If unspecified, all branches are considered. + * @param branch - branch name, case-sensitive, non-null. + * @return this instance for convenience + */ + public LogCommand branch(String branch) { + if (branch == null) { + throw new IllegalArgumentException(); + } + if (branches == null) { + branches = new TreeSet(); + } + branches.add(branch); + return this; + } + + // limit search to specific date + // multiple? + public LogCommand date(Calendar date) { + this.date = date; + // FIXME implement + // isSet(field) - false => don't use in detection of 'same date' + throw HgRepository.notImplemented(); + } + + /** + * + * @param num - number of changeset to produce. Pass 0 to clear the limit. + * @return this instance for convenience + */ + public LogCommand limit(int num) { + limit = num; + return this; + } + + /** + * Limit to specified subset of Changelog, [min(rev1,rev2), max(rev1,rev2)], inclusive. + * Revision may be specified with {@link HgRepository#TIP} + * @param rev1 + * @param rev2 + * @return this instance for convenience + */ + public LogCommand range(int rev1, int rev2) { + if (rev1 != TIP && rev2 != TIP) { + startRev = rev2 < rev1 ? rev2 : rev1; + endRev = startRev == rev2 ? rev1 : rev2; + } else if (rev1 == TIP && rev2 != TIP) { + startRev = rev2; + endRev = rev1; + } else { + startRev = rev1; + endRev = rev2; + } + return this; + } + + // multiple? Bad idea, would need to include extra method into Handler to tell start of next file + public LogCommand file(Path file) { + // implicit --follow in this case + throw HgRepository.notImplemented(); + } + + /** + * Similar to {@link #execute(com.tmate.hgkit.ll.Changeset.Inspector)}, collects and return result as a list. + */ + public List execute() { + CollectHandler collector = new CollectHandler(); + execute(collector); + return collector.getChanges(); + } + + /** + * + * @param inspector + * @throws IllegalArgumentException when inspector argument is null + * @throws ConcurrentModificationException if this log command instance is already running + */ + public void execute(Handler handler) { + if (handler == null) { + throw new IllegalArgumentException(); + } + if (delegate != null) { + throw new ConcurrentModificationException(); + } + try { + delegate = handler; + count = 0; + changeset = new Cset(new StatusCollector(repo), new PathPool(repo.getPathHelper())); + repo.getChangelog().range(startRev, endRev, this); + } finally { + delegate = null; + changeset = null; + } + } + + // + + public void next(int revisionNumber, Nodeid nodeid, Changeset cset) { + if (limit > 0 && count >= limit) { + return; + } + if (branches != null && !branches.contains(cset.branch())) { + return; + } + if (users != null) { + String csetUser = cset.user().toLowerCase(); + boolean found = false; + for (String u : users) { + if (csetUser.indexOf(u) != -1) { + found = true; + break; + } + } + if (!found) { + return; + } + } + if (date != null) { + // FIXME + } + count++; + changeset.init(revisionNumber, nodeid, cset); + delegate.next(changeset); + } + + public interface Handler { + /** + * @param changeset not necessarily a distinct instance each time, {@link Cset#clone() clone()} if need a copy. + */ + void next(Cset changeset); + } + + public static class CollectHandler implements Handler { + private final List result = new LinkedList(); + + public List getChanges() { + return Collections.unmodifiableList(result); + } + + public void next(Cset changeset) { + result.add(changeset.clone()); + } + } + + public static final class FileRevision { + private final HgRepository repo; + private final Nodeid revision; + private final Path path; + + public FileRevision(HgRepository hgRepo, Nodeid rev, Path p) { + if (hgRepo == null || rev == null || p == null) { + throw new IllegalArgumentException(); + } + repo = hgRepo; + revision = rev; + path = p; + } + + public Path getPath() { + return path; + } + public Nodeid getRevision() { + return revision; + } + public byte[] getContent() { + // XXX Content wrapper, to allow formats other than byte[], e.g. Stream, DataAccess, etc? + return repo.getFileNode(path).content(); + } + } +} diff -r a47530a2ea12 -r 19e9e220bf68 src/org/tmatesoft/hg/core/Path.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/Path.java Fri Jan 21 05:56:43 2011 +0100 @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2011 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@svnkit.com + */ +package org.tmatesoft.hg.core; + +/** + * Identify repository files (not String nor io.File). Convenient for pattern matching. Memory-friendly. + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public final class Path implements CharSequence, Comparable/*Cloneable? - although clone for paths make no sense*/{ +// private String[] segments; +// private int flags; // dir, unparsed + private String path; + + /*package-local*/Path(String p) { + path = p; + } + + public int length() { + return path.length(); + } + + public char charAt(int index) { + return path.charAt(index); + } + + public CharSequence subSequence(int start, int end) { + // new Path if start-end matches boundaries of any subpath + return path.substring(start, end); + } + + @Override + public String toString() { + return path; // CharSequence demands toString() impl + } + + public int compareTo(Path o) { + return path.compareTo(o.path); + } + + @Override + public boolean equals(Object obj) { + if (obj != null && getClass() == obj.getClass()) { + return this == obj || path.equals(((Path) obj).path); + } + return false; + } + @Override + public int hashCode() { + return path.hashCode(); + } + + public static Path create(String path) { + if (path == null) { + throw new IllegalArgumentException(); + } + Path rv = new Path(path); + return rv; + } + public interface Matcher { + public boolean accept(Path path); + } +} diff -r a47530a2ea12 -r 19e9e220bf68 src/org/tmatesoft/hg/core/RepositoryTreeWalker.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/RepositoryTreeWalker.java Fri Jan 21 05:56:43 2011 +0100 @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2011 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@svnkit.com + */ +package org.tmatesoft.hg.core; + +import static com.tmate.hgkit.ll.HgRepository.TIP; + +import java.util.ConcurrentModificationException; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; + +import org.tmatesoft.hg.core.LogCommand.FileRevision; +import org.tmatesoft.hg.util.PathPool; + +import com.tmate.hgkit.ll.HgManifest; +import com.tmate.hgkit.ll.HgRepository; +import com.tmate.hgkit.ll.Nodeid; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class RepositoryTreeWalker { + + private final HgRepository repo; + private Path.Matcher matcher; + private int startRev = 0, endRev = TIP; + private Handler visitor; + private boolean needDirs = false; + + private final Mediator mediator = new Mediator(); + + public RepositoryTreeWalker(HgRepository hgRepo) { + this.repo = hgRepo; + } + + public RepositoryTreeWalker range(int rev1, int rev2) { + // if manifest range is different from that of changelog, need conversion utils (external?) + throw HgRepository.notImplemented(); + } + + public RepositoryTreeWalker dirs(boolean include) { + // XXX whether directories with directories only are include or not + // now lists only directories with files + needDirs = include; + return this; + } + + /** + * Limit manifest walk to a subset of files. + * @param pathMatcher - filter, pass null to clear. + * @return this instance for convenience + */ + public RepositoryTreeWalker match(Path.Matcher pathMatcher) { + matcher = pathMatcher; + return this; + } + + public void walk(Handler handler) { + if (handler == null) { + throw new IllegalArgumentException(); + } + if (visitor != null) { + throw new ConcurrentModificationException(); + } + try { + visitor = handler; + mediator.start(); + repo.getManifest().walk(startRev, endRev, mediator); + } finally { + visitor = null; + mediator.done(); + } + } + + /** + * Callback to walk file/directory tree of a revision + */ + public interface Handler { + void begin(Nodeid manifestRevision); + void dir(Path p); // optionally invoked (if walker was configured to spit out directories) prior to any files from this dir and subdirs + void file(FileRevision fileRevision); // XXX allow to check p is invalid (df.exists()) + void end(Nodeid manifestRevision); + } + + // I'd rather let RepositoryTreeWalker implement HgManifest.Inspector directly, but this pollutes API alot + private class Mediator implements HgManifest.Inspector { + private PathPool pathPool; + private List manifestContent; + private Nodeid manifestNodeid; + + public void start() { + pathPool = new PathPool(repo.getPathHelper()); + } + + public void done() { + manifestContent = null; + pathPool = null; + } + + public boolean begin(int revision, Nodeid nid) { + if (needDirs && manifestContent == null) { + manifestContent = new LinkedList(); + } + visitor.begin(manifestNodeid = nid); + return true; + } + public boolean end(int revision) { + if (needDirs) { + LinkedHashMap> breakDown = new LinkedHashMap>(); + for (FileRevision fr : manifestContent) { + Path filePath = fr.getPath(); + Path dirPath = pathPool.parent(filePath); + LinkedList revs = breakDown.get(dirPath); + if (revs == null) { + revs = new LinkedList(); + breakDown.put(dirPath, revs); + } + revs.addLast(fr); + } + for (Path dir : breakDown.keySet()) { + visitor.dir(dir); + for (FileRevision fr : breakDown.get(dir)) { + visitor.file(fr); + } + } + manifestContent.clear(); + } + visitor.end(manifestNodeid); + manifestNodeid = null; + return true; + } + public boolean next(Nodeid nid, String fname, String flags) { + Path p = pathPool.path(fname); + if (matcher != null && !matcher.accept(p)) { + return true; + } + FileRevision fr = new FileRevision(repo, nid, p); + if (needDirs) { + manifestContent.add(fr); + } else { + visitor.file(fr); + } + return true; + } + } +} diff -r a47530a2ea12 -r 19e9e220bf68 src/org/tmatesoft/hg/core/StatusCommand.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/StatusCommand.java Fri Jan 21 05:56:43 2011 +0100 @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2011 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@svnkit.com + */ +package org.tmatesoft.hg.core; + +import org.tmatesoft.hg.core.Path.Matcher; + +import com.tmate.hgkit.ll.HgRepository; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class StatusCommand { + private final HgRepository repo; + + private boolean needClean = false; + private boolean needIgnored = false; + private Matcher matcher; + private int startRevision; + private Integer endRevision; // need three states, set, -1 or actual rev number + private boolean visitSubRepo = true; + + public StatusCommand(HgRepository hgRepo) { + this.repo = hgRepo; + } + + public StatusCommand all() { + needClean = true; + return this; + } + + public StatusCommand clean(boolean include) { + needClean = include; + return this; + } + public StatusCommand ignored(boolean include) { + needIgnored = include; + return this; + } + + // if set, either base:revision or base:workingdir + public StatusCommand base(int revision) { + startRevision = revision; + return this; + } + + // revision without base == --change + public StatusCommand revision(int revision) { + // XXX how to clear endRevision, if needed. + // Perhaps, use of WC_REVISION or BAD_REVISION == -2 or Int.MIN_VALUE? + endRevision = new Integer(revision); + return this; + } + + public StatusCommand match(Path.Matcher pathMatcher) { + matcher = pathMatcher; + return this; + } + + public StatusCommand subrepo(boolean visit) { + visitSubRepo = visit; + throw HgRepository.notImplemented(); + } + + public void execute() { + throw HgRepository.notImplemented(); + } +} diff -r a47530a2ea12 -r 19e9e220bf68 src/org/tmatesoft/hg/util/PathPool.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/util/PathPool.java Fri Jan 21 05:56:43 2011 +0100 @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2011 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@svnkit.com + */ +package org.tmatesoft.hg.util; + +import java.lang.ref.SoftReference; +import java.util.WeakHashMap; + +import org.tmatesoft.hg.core.Path; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class PathPool { + private final WeakHashMap> cache; + private final PathRewrite pathRewrite; + + public PathPool(PathRewrite rewrite) { + pathRewrite = rewrite; + cache = new WeakHashMap>(); + } + + public Path path(String p) { + p = pathRewrite.rewrite(p); + SoftReference sr = cache.get(p); + Path path = sr == null ? null : sr.get(); + if (path == null) { + path = Path.create(p); + cache.put(p, new SoftReference(path)); + } + return path; + } + + // XXX what would be parent of an empty path? + // Path shall have similar functionality + public Path parent(Path path) { + if (path.length() == 0) { + throw new IllegalArgumentException(); + } + for (int i = path.length() - 2 /*if path represents a dir, trailing char is slash, skip*/; i >= 0; i--) { + if (path.charAt(i) == '/') { + return get(path.subSequence(0, i+1).toString(), true); + } + } + return get("", true); + } + + private Path get(String p, boolean create) { + SoftReference sr = cache.get(p); + Path path = sr == null ? null : sr.get(); + if (path == null) { + if (create) { + path = Path.create(p); + cache.put(p, new SoftReference(path)); + } else if (sr != null) { + // cached path no longer used, clear cache entry - do not wait for RefQueue to step in + cache.remove(p); + } + } + return path; + } +} diff -r a47530a2ea12 -r 19e9e220bf68 src/org/tmatesoft/hg/util/PathRewrite.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/util/PathRewrite.java Fri Jan 21 05:56:43 2011 +0100 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@svnkit.com + */ +package org.tmatesoft.hg.util; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public interface PathRewrite { + + public String rewrite(String path); +}