tikhomirov@64: /*
tikhomirov@64: * Copyright (c) 2011 TMate Software Ltd
tikhomirov@64: *
tikhomirov@64: * This program is free software; you can redistribute it and/or modify
tikhomirov@64: * it under the terms of the GNU General Public License as published by
tikhomirov@64: * the Free Software Foundation; version 2 of the License.
tikhomirov@64: *
tikhomirov@64: * This program is distributed in the hope that it will be useful,
tikhomirov@64: * but WITHOUT ANY WARRANTY; without even the implied warranty of
tikhomirov@64: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
tikhomirov@64: * GNU General Public License for more details.
tikhomirov@64: *
tikhomirov@64: * For information on how to redistribute this software under
tikhomirov@64: * the terms of a license other than GNU General Public License
tikhomirov@102: * contact TMate Software at support@hg4j.com
tikhomirov@64: */
tikhomirov@64: package org.tmatesoft.hg.core;
tikhomirov@64:
tikhomirov@74: import static org.tmatesoft.hg.repo.HgRepository.TIP;
tikhomirov@64:
tikhomirov@64: import java.util.ConcurrentModificationException;
tikhomirov@64: import java.util.LinkedHashMap;
tikhomirov@64: import java.util.LinkedList;
tikhomirov@64: import java.util.List;
tikhomirov@64:
tikhomirov@131: import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
tikhomirov@74: import org.tmatesoft.hg.repo.HgManifest;
tikhomirov@74: import org.tmatesoft.hg.repo.HgRepository;
tikhomirov@133: import org.tmatesoft.hg.util.Path;
tikhomirov@64: import org.tmatesoft.hg.util.PathPool;
tikhomirov@142: import org.tmatesoft.hg.util.PathRewrite;
tikhomirov@64:
tikhomirov@64:
tikhomirov@64: /**
tikhomirov@131: * Gives access to list of files in each revision (Mercurial manifest information), 'hg manifest' counterpart.
tikhomirov@131: *
tikhomirov@64: * @author Artem Tikhomirov
tikhomirov@64: * @author TMate Software Ltd.
tikhomirov@64: */
tikhomirov@131: public class HgManifestCommand {
tikhomirov@64:
tikhomirov@64: private final HgRepository repo;
tikhomirov@64: private Path.Matcher matcher;
tikhomirov@64: private int startRev = 0, endRev = TIP;
tikhomirov@64: private Handler visitor;
tikhomirov@64: private boolean needDirs = false;
tikhomirov@64:
tikhomirov@64: private final Mediator mediator = new Mediator();
tikhomirov@64:
tikhomirov@131: public HgManifestCommand(HgRepository hgRepo) {
tikhomirov@123: repo = hgRepo;
tikhomirov@64: }
tikhomirov@64:
tikhomirov@131: public HgManifestCommand range(int rev1, int rev2) {
tikhomirov@64: // if manifest range is different from that of changelog, need conversion utils (external?)
tikhomirov@64: throw HgRepository.notImplemented();
tikhomirov@64: }
tikhomirov@64:
tikhomirov@131: public HgManifestCommand revision(int rev) {
tikhomirov@67: startRev = endRev = rev;
tikhomirov@67: return this;
tikhomirov@67: }
tikhomirov@67:
tikhomirov@131: public HgManifestCommand dirs(boolean include) {
tikhomirov@64: // XXX whether directories with directories only are include or not
tikhomirov@64: // now lists only directories with files
tikhomirov@64: needDirs = include;
tikhomirov@64: return this;
tikhomirov@64: }
tikhomirov@64:
tikhomirov@64: /**
tikhomirov@64: * Limit manifest walk to a subset of files.
tikhomirov@64: * @param pathMatcher - filter, pass null
to clear.
tikhomirov@64: * @return this
instance for convenience
tikhomirov@64: */
tikhomirov@131: public HgManifestCommand match(Path.Matcher pathMatcher) {
tikhomirov@64: matcher = pathMatcher;
tikhomirov@64: return this;
tikhomirov@64: }
tikhomirov@64:
tikhomirov@64: public void walk(Handler handler) {
tikhomirov@64: if (handler == null) {
tikhomirov@64: throw new IllegalArgumentException();
tikhomirov@64: }
tikhomirov@64: if (visitor != null) {
tikhomirov@64: throw new ConcurrentModificationException();
tikhomirov@64: }
tikhomirov@64: try {
tikhomirov@64: visitor = handler;
tikhomirov@64: mediator.start();
tikhomirov@64: repo.getManifest().walk(startRev, endRev, mediator);
tikhomirov@64: } finally {
tikhomirov@92: mediator.done();
tikhomirov@64: visitor = null;
tikhomirov@64: }
tikhomirov@64: }
tikhomirov@64:
tikhomirov@64: /**
tikhomirov@64: * Callback to walk file/directory tree of a revision
tikhomirov@64: */
tikhomirov@64: public interface Handler {
tikhomirov@64: void begin(Nodeid manifestRevision);
tikhomirov@64: void dir(Path p); // optionally invoked (if walker was configured to spit out directories) prior to any files from this dir and subdirs
tikhomirov@64: void file(FileRevision fileRevision); // XXX allow to check p is invalid (df.exists())
tikhomirov@64: void end(Nodeid manifestRevision);
tikhomirov@64: }
tikhomirov@64:
tikhomirov@132: // I'd rather let HgManifestCommand implement HgManifest.Inspector directly, but this pollutes API alot
tikhomirov@64: private class Mediator implements HgManifest.Inspector {
tikhomirov@142: // file names are likely to repeat in each revision, hence caching of Paths.
tikhomirov@142: // However, once HgManifest.Inspector switches to Path objects, perhaps global Path pool
tikhomirov@142: // might be more effective?
tikhomirov@64: private PathPool pathPool;
tikhomirov@64: private List manifestContent;
tikhomirov@64: private Nodeid manifestNodeid;
tikhomirov@64:
tikhomirov@64: public void start() {
tikhomirov@142: // Manifest keeps normalized paths
tikhomirov@142: pathPool = new PathPool(new PathRewrite.Empty());
tikhomirov@64: }
tikhomirov@64:
tikhomirov@64: public void done() {
tikhomirov@64: manifestContent = null;
tikhomirov@64: pathPool = null;
tikhomirov@64: }
tikhomirov@64:
tikhomirov@64: public boolean begin(int revision, Nodeid nid) {
tikhomirov@64: if (needDirs && manifestContent == null) {
tikhomirov@64: manifestContent = new LinkedList();
tikhomirov@64: }
tikhomirov@64: visitor.begin(manifestNodeid = nid);
tikhomirov@64: return true;
tikhomirov@64: }
tikhomirov@64: public boolean end(int revision) {
tikhomirov@64: if (needDirs) {
tikhomirov@64: LinkedHashMap> breakDown = new LinkedHashMap>();
tikhomirov@64: for (FileRevision fr : manifestContent) {
tikhomirov@64: Path filePath = fr.getPath();
tikhomirov@64: Path dirPath = pathPool.parent(filePath);
tikhomirov@64: LinkedList revs = breakDown.get(dirPath);
tikhomirov@64: if (revs == null) {
tikhomirov@64: revs = new LinkedList();
tikhomirov@64: breakDown.put(dirPath, revs);
tikhomirov@64: }
tikhomirov@64: revs.addLast(fr);
tikhomirov@64: }
tikhomirov@64: for (Path dir : breakDown.keySet()) {
tikhomirov@64: visitor.dir(dir);
tikhomirov@64: for (FileRevision fr : breakDown.get(dir)) {
tikhomirov@64: visitor.file(fr);
tikhomirov@64: }
tikhomirov@64: }
tikhomirov@64: manifestContent.clear();
tikhomirov@64: }
tikhomirov@64: visitor.end(manifestNodeid);
tikhomirov@64: manifestNodeid = null;
tikhomirov@64: return true;
tikhomirov@64: }
tikhomirov@64: public boolean next(Nodeid nid, String fname, String flags) {
tikhomirov@64: Path p = pathPool.path(fname);
tikhomirov@64: if (matcher != null && !matcher.accept(p)) {
tikhomirov@64: return true;
tikhomirov@64: }
tikhomirov@64: FileRevision fr = new FileRevision(repo, nid, p);
tikhomirov@64: if (needDirs) {
tikhomirov@64: manifestContent.add(fr);
tikhomirov@64: } else {
tikhomirov@64: visitor.file(fr);
tikhomirov@64: }
tikhomirov@64: return true;
tikhomirov@64: }
tikhomirov@64: }
tikhomirov@64: }