tikhomirov@64: /* tikhomirov@419: * 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@127: import static org.tmatesoft.hg.core.HgStatus.Kind.*; tikhomirov@367: import static org.tmatesoft.hg.repo.HgInternals.wrongRevisionIndex; tikhomirov@109: import static org.tmatesoft.hg.repo.HgRepository.*; tikhomirov@68: tikhomirov@350: import java.io.IOException; tikhomirov@93: import java.util.ConcurrentModificationException; tikhomirov@93: tikhomirov@128: import org.tmatesoft.hg.internal.ChangelogHelper; tikhomirov@74: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@427: import org.tmatesoft.hg.repo.HgRuntimeException; tikhomirov@109: import org.tmatesoft.hg.repo.HgStatusCollector; tikhomirov@93: import org.tmatesoft.hg.repo.HgStatusInspector; tikhomirov@94: import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector; tikhomirov@423: import org.tmatesoft.hg.util.CancelSupport; tikhomirov@423: import org.tmatesoft.hg.util.CancelledException; tikhomirov@133: import org.tmatesoft.hg.util.Path; tikhomirov@454: import org.tmatesoft.hg.util.Outcome; tikhomirov@64: tikhomirov@64: /** tikhomirov@131: * Command to obtain file status information, 'hg status' counterpart. tikhomirov@131: * tikhomirov@64: * @author Artem Tikhomirov tikhomirov@64: * @author TMate Software Ltd. tikhomirov@64: */ tikhomirov@215: public class HgStatusCommand extends HgAbstractCommand { tikhomirov@64: private final HgRepository repo; tikhomirov@64: tikhomirov@68: private int startRevision = TIP; tikhomirov@229: private int endRevision = WORKING_COPY; tikhomirov@229: private Path.Matcher scope; tikhomirov@93: tikhomirov@93: private final Mediator mediator = new Mediator(); tikhomirov@64: tikhomirov@131: public HgStatusCommand(HgRepository hgRepo) { tikhomirov@68: repo = hgRepo; tikhomirov@68: defaults(); tikhomirov@64: } tikhomirov@64: tikhomirov@131: public HgStatusCommand defaults() { tikhomirov@93: final Mediator m = mediator; tikhomirov@93: m.needModified = m.needAdded = m.needRemoved = m.needUnknown = m.needMissing = true; tikhomirov@99: m.needCopies = m.needClean = m.needIgnored = false; tikhomirov@64: return this; tikhomirov@64: } tikhomirov@131: public HgStatusCommand all() { tikhomirov@93: final Mediator m = mediator; tikhomirov@93: m.needModified = m.needAdded = m.needRemoved = m.needUnknown = m.needMissing = true; tikhomirov@99: m.needCopies = m.needClean = m.needIgnored = true; tikhomirov@68: return this; tikhomirov@68: } tikhomirov@68: tikhomirov@64: tikhomirov@131: public HgStatusCommand modified(boolean include) { tikhomirov@93: mediator.needModified = include; tikhomirov@68: return this; tikhomirov@68: } tikhomirov@131: public HgStatusCommand added(boolean include) { tikhomirov@93: mediator.needAdded = include; tikhomirov@68: return this; tikhomirov@68: } tikhomirov@131: public HgStatusCommand removed(boolean include) { tikhomirov@93: mediator.needRemoved = include; tikhomirov@68: return this; tikhomirov@68: } tikhomirov@131: public HgStatusCommand deleted(boolean include) { tikhomirov@93: mediator.needMissing = include; tikhomirov@68: return this; tikhomirov@68: } tikhomirov@131: public HgStatusCommand unknown(boolean include) { tikhomirov@93: mediator.needUnknown = include; tikhomirov@68: return this; tikhomirov@68: } tikhomirov@131: public HgStatusCommand clean(boolean include) { tikhomirov@93: mediator.needClean = include; tikhomirov@64: return this; tikhomirov@64: } tikhomirov@131: public HgStatusCommand ignored(boolean include) { tikhomirov@93: mediator.needIgnored = include; tikhomirov@64: return this; tikhomirov@64: } tikhomirov@64: tikhomirov@68: /** tikhomirov@148: * If set, either base:revision or base:workingdir tikhomirov@68: * to unset, pass {@link HgRepository#TIP} or {@link HgRepository#BAD_REVISION} tikhomirov@367: * @param changesetRevisionIndex - local index of a changeset to base status from tikhomirov@148: * @return this for convenience tikhomirov@148: * @throws IllegalArgumentException when revision is negative or {@link HgRepository#WORKING_COPY} tikhomirov@68: */ tikhomirov@367: public HgStatusCommand base(int changesetRevisionIndex) { tikhomirov@367: if (changesetRevisionIndex == WORKING_COPY || wrongRevisionIndex(changesetRevisionIndex)) { tikhomirov@367: throw new IllegalArgumentException(String.valueOf(changesetRevisionIndex)); tikhomirov@68: } tikhomirov@367: if (changesetRevisionIndex == BAD_REVISION) { tikhomirov@367: changesetRevisionIndex = TIP; tikhomirov@68: } tikhomirov@367: startRevision = changesetRevisionIndex; tikhomirov@64: return this; tikhomirov@64: } tikhomirov@64: tikhomirov@68: /** tikhomirov@68: * Revision without base == --change tikhomirov@68: * Pass {@link HgRepository#WORKING_COPY} or {@link HgRepository#BAD_REVISION} to reset tikhomirov@368: * @param changesetRevisionIndex - non-negative changeset revision local index, or any of {@link HgRepository#BAD_REVISION}, {@link HgRepository#WORKING_COPY} or {@link HgRepository#TIP} tikhomirov@148: * @return this for convenience tikhomirov@368: * @throws IllegalArgumentException if revision index doesn't specify legitimate revision. tikhomirov@68: */ tikhomirov@367: public HgStatusCommand revision(int changesetRevisionIndex) { tikhomirov@367: if (changesetRevisionIndex == BAD_REVISION) { tikhomirov@367: changesetRevisionIndex = WORKING_COPY; tikhomirov@68: } tikhomirov@367: if (wrongRevisionIndex(changesetRevisionIndex)) { tikhomirov@367: throw new IllegalArgumentException(String.valueOf(changesetRevisionIndex)); tikhomirov@143: } tikhomirov@367: endRevision = changesetRevisionIndex; tikhomirov@64: return this; tikhomirov@64: } tikhomirov@64: tikhomirov@143: /** tikhomirov@143: * Shorthand for {@link #base(int) cmd.base(BAD_REVISION)}{@link #change(int) .revision(revision)} tikhomirov@143: * tikhomirov@143: * @param revision compare given revision against its parent tikhomirov@148: * @return this for convenience tikhomirov@143: */ tikhomirov@143: public HgStatusCommand change(int revision) { tikhomirov@143: base(BAD_REVISION); tikhomirov@143: return revision(revision); tikhomirov@143: } tikhomirov@143: tikhomirov@148: /** tikhomirov@148: * Limit status operation to certain sub-tree. tikhomirov@148: * tikhomirov@419: * @param scopeMatcher - matcher to use, pass null/ to reset tikhomirov@148: * @return this for convenience tikhomirov@148: */ tikhomirov@229: public HgStatusCommand match(Path.Matcher scopeMatcher) { tikhomirov@229: scope = scopeMatcher; tikhomirov@123: return this; tikhomirov@64: } tikhomirov@64: tikhomirov@131: public HgStatusCommand subrepo(boolean visit) { tikhomirov@64: throw HgRepository.notImplemented(); tikhomirov@64: } tikhomirov@93: tikhomirov@93: /** tikhomirov@93: * Perform status operation according to parameters set. tikhomirov@93: * tikhomirov@419: * @param statusHandler callback to get status information 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@429: * @throws IOException if there are (further unspecified) errors while walking working copy tikhomirov@423: * @throws CancelledException if execution of the command was cancelled tikhomirov@93: * @throws IllegalArgumentException if handler is null tikhomirov@93: * @throws ConcurrentModificationException if this command already runs (i.e. being used from another thread) tikhomirov@93: */ tikhomirov@429: public void execute(HgStatusHandler statusHandler) throws HgCallbackTargetException, HgException, IOException, CancelledException { tikhomirov@109: if (statusHandler == null) { tikhomirov@93: throw new IllegalArgumentException(); tikhomirov@93: } tikhomirov@128: if (mediator.busy()) { tikhomirov@93: throw new ConcurrentModificationException(); tikhomirov@93: } tikhomirov@94: HgStatusCollector sc = new HgStatusCollector(repo); // TODO from CommandContext tikhomirov@93: // PathPool pathHelper = new PathPool(repo.getPathHelper()); // TODO from CommandContext tikhomirov@93: try { tikhomirov@93: // XXX if I need a rough estimation (for ProgressMonitor) of number of work units, tikhomirov@93: // I may use number of files in either rev1 or rev2 manifest edition tikhomirov@423: mediator.start(statusHandler, getCancelSupport(statusHandler, true), new ChangelogHelper(repo, startRevision)); tikhomirov@93: if (endRevision == WORKING_COPY) { tikhomirov@229: HgWorkingCopyStatusCollector wcsc = scope != null ? HgWorkingCopyStatusCollector.create(repo, scope) : new HgWorkingCopyStatusCollector(repo); tikhomirov@93: wcsc.setBaseRevisionCollector(sc); tikhomirov@93: wcsc.walk(startRevision, mediator); tikhomirov@68: } else { tikhomirov@229: sc.setScope(scope); // explicitly set, even if null - would be handy once we reuse StatusCollector tikhomirov@93: if (startRevision == TIP) { tikhomirov@93: sc.change(endRevision, mediator); tikhomirov@93: } else { tikhomirov@93: sc.walk(startRevision, endRevision, mediator); tikhomirov@93: } tikhomirov@93: } tikhomirov@423: } catch (CancelledException ex) { tikhomirov@423: // this is our exception, thrown from Mediator. tikhomirov@423: // next check shall throw original cause of the stop - either HgCallbackTargetException or original CancelledException tikhomirov@423: mediator.checkFailure(); tikhomirov@427: } catch (HgRuntimeException ex) { tikhomirov@427: throw new HgLibraryFailureException(ex); tikhomirov@93: } finally { tikhomirov@93: mediator.done(); tikhomirov@93: } tikhomirov@93: } tikhomirov@93: tikhomirov@423: private class Mediator implements HgStatusInspector, CancelSupport { tikhomirov@93: boolean needModified; tikhomirov@93: boolean needAdded; tikhomirov@93: boolean needRemoved; tikhomirov@93: boolean needUnknown; tikhomirov@93: boolean needMissing; tikhomirov@93: boolean needClean; tikhomirov@93: boolean needIgnored; tikhomirov@99: boolean needCopies; tikhomirov@360: HgStatusHandler handler; tikhomirov@128: private ChangelogHelper logHelper; tikhomirov@423: private CancelSupport handlerCancelSupport; tikhomirov@423: private HgCallbackTargetException failure; tikhomirov@423: private CancelledException cancellation; tikhomirov@93: tikhomirov@93: Mediator() { tikhomirov@93: } tikhomirov@93: tikhomirov@423: public void start(HgStatusHandler h, CancelSupport hcs, ChangelogHelper changelogHelper) { tikhomirov@128: handler = h; tikhomirov@423: handlerCancelSupport = hcs; tikhomirov@128: logHelper = changelogHelper; tikhomirov@93: } tikhomirov@128: tikhomirov@93: public void done() { tikhomirov@128: handler = null; tikhomirov@423: handlerCancelSupport = null; tikhomirov@128: logHelper = null; tikhomirov@423: failure = null; tikhomirov@423: cancellation = null; tikhomirov@128: } tikhomirov@128: tikhomirov@128: public boolean busy() { tikhomirov@128: return handler != null; tikhomirov@93: } tikhomirov@93: tikhomirov@423: // XXX copy from ChangesetTransformer. Perhaps, can share the code? tikhomirov@423: public void checkFailure() throws HgCallbackTargetException, CancelledException { tikhomirov@423: // do not forget to clear exceptions for reuse of this instance tikhomirov@423: if (failure != null) { tikhomirov@423: HgCallbackTargetException toThrow = failure; tikhomirov@423: failure = null; tikhomirov@423: throw toThrow; tikhomirov@423: } tikhomirov@423: if (cancellation != null) { tikhomirov@423: CancelledException toThrow = cancellation; tikhomirov@423: cancellation = null; tikhomirov@423: throw toThrow; tikhomirov@423: } tikhomirov@423: } tikhomirov@423: tikhomirov@423: // XXX copy from ChangesetTransformer. code sharing note above applies tikhomirov@423: public void checkCancelled() throws CancelledException { tikhomirov@423: if (failure != null || cancellation != null) { tikhomirov@423: // stop status iteration. Our exception is for the purposes of cancellation only, tikhomirov@423: // the one we have stored (this.cancellation) is for user tikhomirov@423: throw new CancelledException(); tikhomirov@423: } tikhomirov@423: } tikhomirov@423: tikhomirov@423: private void dispatch(HgStatus s) { tikhomirov@423: try { tikhomirov@427: handler.status(s); tikhomirov@423: handlerCancelSupport.checkCancelled(); tikhomirov@423: } catch (HgCallbackTargetException ex) { tikhomirov@423: failure = ex; tikhomirov@423: } catch (CancelledException ex) { tikhomirov@423: cancellation = ex; tikhomirov@423: } tikhomirov@423: } tikhomirov@423: tikhomirov@93: public void modified(Path fname) { tikhomirov@93: if (needModified) { tikhomirov@423: dispatch(new HgStatus(Modified, fname, logHelper)); tikhomirov@68: } tikhomirov@68: } tikhomirov@93: public void added(Path fname) { tikhomirov@93: if (needAdded) { tikhomirov@423: dispatch(new HgStatus(Added, fname, logHelper)); tikhomirov@93: } tikhomirov@93: } tikhomirov@93: public void removed(Path fname) { tikhomirov@93: if (needRemoved) { tikhomirov@423: dispatch(new HgStatus(Removed, fname, logHelper)); tikhomirov@93: } tikhomirov@93: } tikhomirov@93: public void copied(Path fnameOrigin, Path fnameAdded) { tikhomirov@93: if (needCopies) { tikhomirov@418: // TODO post-1.0 in fact, merged files may report 'copied from' as well, correct status kind thus may differ from Added tikhomirov@423: dispatch(new HgStatus(Added, fnameAdded, fnameOrigin, logHelper)); tikhomirov@93: } tikhomirov@93: } tikhomirov@93: public void missing(Path fname) { tikhomirov@93: if (needMissing) { tikhomirov@423: dispatch(new HgStatus(Missing, fname, logHelper)); tikhomirov@93: } tikhomirov@93: } tikhomirov@93: public void unknown(Path fname) { tikhomirov@93: if (needUnknown) { tikhomirov@423: dispatch(new HgStatus(Unknown, fname, logHelper)); tikhomirov@93: } tikhomirov@93: } tikhomirov@93: public void clean(Path fname) { tikhomirov@93: if (needClean) { tikhomirov@423: dispatch(new HgStatus(Clean, fname, logHelper)); tikhomirov@93: } tikhomirov@93: } tikhomirov@93: public void ignored(Path fname) { tikhomirov@93: if (needIgnored) { tikhomirov@423: dispatch(new HgStatus(Ignored, fname, logHelper)); tikhomirov@93: } tikhomirov@93: } tikhomirov@360: tikhomirov@423: public void invalid(Path fname, Exception err) { tikhomirov@423: try { tikhomirov@454: handler.error(fname, new Outcome(Outcome.Kind.Failure, "Failed to get file status", err)); tikhomirov@423: handlerCancelSupport.checkCancelled(); tikhomirov@423: } catch (HgCallbackTargetException ex) { tikhomirov@423: failure = ex; tikhomirov@423: } catch (CancelledException ex) { tikhomirov@423: cancellation = ex; tikhomirov@423: } tikhomirov@360: } tikhomirov@64: } tikhomirov@64: }