tikhomirov@64: /* tikhomirov@423: * Copyright (c) 2011-2012 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@143: import static org.tmatesoft.hg.repo.HgRepository.*; tikhomirov@143: import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; 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@431: import org.tmatesoft.hg.internal.PathPool; tikhomirov@457: import org.tmatesoft.hg.repo.HgInvalidRevisionException; tikhomirov@74: import org.tmatesoft.hg.repo.HgManifest; tikhomirov@74: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@285: import org.tmatesoft.hg.repo.HgManifest.Flags; tikhomirov@427: import org.tmatesoft.hg.repo.HgRuntimeException; tikhomirov@427: import org.tmatesoft.hg.util.CancelSupport; tikhomirov@427: import org.tmatesoft.hg.util.CancelledException; tikhomirov@133: import org.tmatesoft.hg.util.Path; 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@215: public class HgManifestCommand extends HgAbstractCommand { tikhomirov@64: tikhomirov@64: private final HgRepository repo; tikhomirov@64: private Path.Matcher matcher; tikhomirov@64: private int startRev = 0, endRev = TIP; tikhomirov@427: private HgManifestHandler 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@148: /** tikhomirov@148: * Parameterize command to visit revisions [rev1..rev2]. tikhomirov@368: * @param rev1 - revision local index to start from. Non-negative. May be {@link HgRepository#TIP} (rev2 argument shall be {@link HgRepository#TIP} as well, then) tikhomirov@368: * @param rev2 - revision local index to end with, inclusive. Non-negative, greater or equal to rev1. May be {@link HgRepository#TIP}. tikhomirov@148: * @return this for convenience. tikhomirov@148: * @throws IllegalArgumentException if revision arguments are incorrect (see above). tikhomirov@148: */ tikhomirov@131: public HgManifestCommand range(int rev1, int rev2) { tikhomirov@143: // XXX if manifest range is different from that of changelog, need conversion utils (external?) tikhomirov@143: boolean badArgs = rev1 == BAD_REVISION || rev2 == BAD_REVISION || rev1 == WORKING_COPY || rev2 == WORKING_COPY; tikhomirov@143: badArgs |= rev2 != TIP && rev2 < rev1; // range(3, 1); tikhomirov@143: badArgs |= rev1 == TIP && rev2 != TIP; // range(TIP, 2), although this may be legitimate when TIP points to 2 tikhomirov@143: if (badArgs) { tikhomirov@143: throw new IllegalArgumentException(String.format("Bad range: [%d, %d]", rev1, rev2)); tikhomirov@143: } tikhomirov@143: startRev = rev1; tikhomirov@143: endRev = rev2; tikhomirov@143: return this; tikhomirov@64: } tikhomirov@64: tikhomirov@457: /** tikhomirov@457: * Select changeset for the command using revision index tikhomirov@457: * @param csetRevisionIndex index of changeset revision tikhomirov@457: * @return this for convenience. tikhomirov@457: */ tikhomirov@457: public HgManifestCommand changeset(int csetRevisionIndex) { tikhomirov@457: return range(csetRevisionIndex, csetRevisionIndex); tikhomirov@67: } tikhomirov@67: tikhomirov@457: /** tikhomirov@457: * Select changeset for the command tikhomirov@457: * tikhomirov@457: * @param nid changeset revision tikhomirov@457: * @return this for convenience tikhomirov@457: * @throws HgBadArgumentException if failed to find supplied changeset revision tikhomirov@457: */ tikhomirov@457: public HgManifestCommand changeset(Nodeid nid) throws HgBadArgumentException { tikhomirov@457: // XXX also see HgLogCommand#changeset(Nodeid) tikhomirov@457: try { tikhomirov@457: final int csetRevIndex = repo.getChangelog().getRevisionIndex(nid); tikhomirov@457: return range(csetRevIndex, csetRevIndex); tikhomirov@457: } catch (HgInvalidRevisionException ex) { tikhomirov@457: throw new HgBadArgumentException("Can't find revision", ex).setRevision(nid); tikhomirov@457: } tikhomirov@457: } tikhomirov@457: tikhomirov@457: /** tikhomirov@457: * @deprecated confusing whether it's changeset or manifest own revision index in use, use {@link #changeset(int)} instead tikhomirov@457: */ tikhomirov@457: @Deprecated tikhomirov@457: public HgManifestCommand revision(int rev) { tikhomirov@457: return changeset(rev); tikhomirov@457: } tikhomirov@444: 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@148: /** tikhomirov@427: * With all parameters set, execute the command. tikhomirov@427: * tikhomirov@148: * @param handler - callback to get the outcome tikhomirov@427: * @throws HgCallbackTargetException propagated exception from the handler tikhomirov@427: * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state tikhomirov@427: * @throws CancelledException if execution of the command was cancelled tikhomirov@148: * @throws IllegalArgumentException if handler is null tikhomirov@148: * @throws ConcurrentModificationException if this command is already in use (running) tikhomirov@148: */ tikhomirov@427: public void execute(HgManifestHandler handler) throws HgCallbackTargetException, HgException, CancelledException { 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@427: mediator.start(getCancelSupport(handler, true)); tikhomirov@64: repo.getManifest().walk(startRev, endRev, mediator); tikhomirov@427: mediator.checkFailure(); tikhomirov@427: } catch (HgRuntimeException ex) { tikhomirov@427: throw new HgLibraryFailureException(ex); tikhomirov@64: } finally { tikhomirov@92: mediator.done(); tikhomirov@64: visitor = null; tikhomirov@64: } tikhomirov@64: } tikhomirov@64: tikhomirov@132: // I'd rather let HgManifestCommand implement HgManifest.Inspector directly, but this pollutes API alot tikhomirov@424: 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@231: private List manifestContent; tikhomirov@64: private Nodeid manifestNodeid; tikhomirov@427: private Exception failure; tikhomirov@427: private CancelSupport cancelHelper; tikhomirov@64: tikhomirov@427: public void start(CancelSupport cs) { tikhomirov@427: assert cs != null; tikhomirov@142: // Manifest keeps normalized paths tikhomirov@142: pathPool = new PathPool(new PathRewrite.Empty()); tikhomirov@427: cancelHelper = cs; tikhomirov@64: } tikhomirov@64: tikhomirov@64: public void done() { tikhomirov@64: manifestContent = null; tikhomirov@64: pathPool = null; tikhomirov@64: } tikhomirov@427: tikhomirov@427: private void recordFailure(HgCallbackTargetException ex) { tikhomirov@427: failure = ex; tikhomirov@427: } tikhomirov@427: private void recordCancel(CancelledException ex) { tikhomirov@427: failure = ex; tikhomirov@427: } tikhomirov@427: tikhomirov@427: public void checkFailure() throws HgCallbackTargetException, CancelledException { tikhomirov@427: // TODO post-1.0 perhaps, can combine this code (record/checkFailure) for reuse in more classes (e.g. in Revlog) tikhomirov@427: if (failure instanceof HgCallbackTargetException) { tikhomirov@427: HgCallbackTargetException ex = (HgCallbackTargetException) failure; tikhomirov@427: failure = null; tikhomirov@427: throw ex; tikhomirov@427: } tikhomirov@427: if (failure instanceof CancelledException) { tikhomirov@427: CancelledException ex = (CancelledException) failure; tikhomirov@427: failure = null; tikhomirov@427: throw ex; tikhomirov@427: } tikhomirov@427: } tikhomirov@64: tikhomirov@222: public boolean begin(int manifestRevision, Nodeid nid, int changelogRevision) { tikhomirov@64: if (needDirs && manifestContent == null) { tikhomirov@231: manifestContent = new LinkedList(); tikhomirov@64: } tikhomirov@427: try { tikhomirov@427: visitor.begin(manifestNodeid = nid); tikhomirov@427: cancelHelper.checkCancelled(); tikhomirov@427: return true; tikhomirov@427: } catch (HgCallbackTargetException ex) { tikhomirov@427: recordFailure(ex); tikhomirov@427: return false; tikhomirov@427: } catch (CancelledException ex) { tikhomirov@427: recordCancel(ex); tikhomirov@427: return false; tikhomirov@427: } tikhomirov@64: } tikhomirov@64: public boolean end(int revision) { tikhomirov@427: try { tikhomirov@427: if (needDirs) { tikhomirov@427: LinkedHashMap> breakDown = new LinkedHashMap>(); tikhomirov@427: for (HgFileRevision fr : manifestContent) { tikhomirov@427: Path filePath = fr.getPath(); tikhomirov@427: Path dirPath = pathPool.parent(filePath); tikhomirov@427: LinkedList revs = breakDown.get(dirPath); tikhomirov@427: if (revs == null) { tikhomirov@427: revs = new LinkedList(); tikhomirov@427: breakDown.put(dirPath, revs); tikhomirov@427: } tikhomirov@427: revs.addLast(fr); tikhomirov@64: } tikhomirov@427: for (Path dir : breakDown.keySet()) { tikhomirov@427: visitor.dir(dir); tikhomirov@427: cancelHelper.checkCancelled(); tikhomirov@427: for (HgFileRevision fr : breakDown.get(dir)) { tikhomirov@427: visitor.file(fr); tikhomirov@427: } tikhomirov@427: } tikhomirov@427: manifestContent.clear(); tikhomirov@64: } tikhomirov@427: visitor.end(manifestNodeid); tikhomirov@427: cancelHelper.checkCancelled(); tikhomirov@427: return true; tikhomirov@427: } catch (HgCallbackTargetException ex) { tikhomirov@427: recordFailure(ex); tikhomirov@427: return false; tikhomirov@427: } catch (CancelledException ex) { tikhomirov@427: recordCancel(ex); tikhomirov@427: return false; tikhomirov@427: } finally { tikhomirov@427: manifestNodeid = null; tikhomirov@64: } tikhomirov@64: } tikhomirov@285: tikhomirov@285: public boolean next(Nodeid nid, Path fname, Flags flags) { tikhomirov@285: if (matcher != null && !matcher.accept(fname)) { tikhomirov@64: return true; tikhomirov@64: } tikhomirov@427: try { tikhomirov@427: HgFileRevision fr = new HgFileRevision(repo, nid, flags, fname); tikhomirov@427: if (needDirs) { tikhomirov@427: manifestContent.add(fr); tikhomirov@427: } else { tikhomirov@427: visitor.file(fr); tikhomirov@427: } tikhomirov@427: return true; tikhomirov@427: } catch (HgCallbackTargetException ex) { tikhomirov@427: recordFailure(ex); tikhomirov@427: return false; tikhomirov@64: } tikhomirov@64: } tikhomirov@64: } tikhomirov@64: }