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@127: import static org.tmatesoft.hg.core.HgStatus.Kind.*;
tikhomirov@148: import static org.tmatesoft.hg.repo.HgInternals.wrongLocalRevision;
tikhomirov@109: import static org.tmatesoft.hg.repo.HgRepository.*;
tikhomirov@68:
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@109: import org.tmatesoft.hg.repo.HgStatusCollector;
tikhomirov@93: import org.tmatesoft.hg.repo.HgStatusInspector;
tikhomirov@94: import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
tikhomirov@133: import org.tmatesoft.hg.util.Path;
tikhomirov@133: import org.tmatesoft.hg.util.Path.Matcher;
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@131: public class HgStatusCommand {
tikhomirov@64: private final HgRepository repo;
tikhomirov@64:
tikhomirov@68: private int startRevision = TIP;
tikhomirov@68: private int endRevision = WORKING_COPY;
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@148: * @param revision - local revision number 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@131: public HgStatusCommand base(int revision) {
tikhomirov@148: if (revision == WORKING_COPY || wrongLocalRevision(revision)) {
tikhomirov@148: throw new IllegalArgumentException(String.valueOf(revision));
tikhomirov@68: }
tikhomirov@68: if (revision == BAD_REVISION) {
tikhomirov@68: revision = TIP;
tikhomirov@68: }
tikhomirov@64: startRevision = revision;
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@148: * @param revision - non-negative local revision number, or any of {@link HgRepository#BAD_REVISION}, {@link HgRepository#WORKING_COPY} or {@link HgRepository#TIP}
tikhomirov@148: * @return this
for convenience
tikhomirov@148: * @throws IllegalArgumentException if local revision number doesn't specify legitimate revision.
tikhomirov@68: */
tikhomirov@131: public HgStatusCommand revision(int revision) {
tikhomirov@68: if (revision == BAD_REVISION) {
tikhomirov@68: revision = WORKING_COPY;
tikhomirov@68: }
tikhomirov@148: if (wrongLocalRevision(revision)) {
tikhomirov@143: throw new IllegalArgumentException(String.valueOf(revision));
tikhomirov@143: }
tikhomirov@68: endRevision = revision;
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@148: * @param pathMatcher - matcher to use, pass null/ to reset
tikhomirov@148: * @return this
for convenience
tikhomirov@148: */
tikhomirov@131: public HgStatusCommand match(Path.Matcher pathMatcher) {
tikhomirov@93: mediator.matcher = pathMatcher;
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@93: * @param handler callback to get status information
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@109: public void execute(Handler statusHandler) {
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@128: mediator.start(statusHandler, new ChangelogHelper(repo, startRevision));
tikhomirov@93: if (endRevision == WORKING_COPY) {
tikhomirov@94: HgWorkingCopyStatusCollector wcsc = new HgWorkingCopyStatusCollector(repo);
tikhomirov@93: wcsc.setBaseRevisionCollector(sc);
tikhomirov@93: wcsc.walk(startRevision, mediator);
tikhomirov@68: } else {
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@93: } finally {
tikhomirov@93: mediator.done();
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93:
tikhomirov@109: public interface Handler {
tikhomirov@109: void handleStatus(HgStatus s);
tikhomirov@109: }
tikhomirov@109:
tikhomirov@93: private class Mediator implements HgStatusInspector {
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@93: Matcher matcher;
tikhomirov@128: Handler handler;
tikhomirov@128: private ChangelogHelper logHelper;
tikhomirov@93:
tikhomirov@93: Mediator() {
tikhomirov@93: }
tikhomirov@93:
tikhomirov@128: public void start(Handler h, ChangelogHelper changelogHelper) {
tikhomirov@128: handler = h;
tikhomirov@128: logHelper = changelogHelper;
tikhomirov@93: }
tikhomirov@128:
tikhomirov@93: public void done() {
tikhomirov@128: handler = null;
tikhomirov@128: logHelper = null;
tikhomirov@128: }
tikhomirov@128:
tikhomirov@128: public boolean busy() {
tikhomirov@128: return handler != null;
tikhomirov@93: }
tikhomirov@93:
tikhomirov@93: public void modified(Path fname) {
tikhomirov@93: if (needModified) {
tikhomirov@93: if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: handler.handleStatus(new HgStatus(Modified, fname, logHelper));
tikhomirov@93: }
tikhomirov@68: }
tikhomirov@68: }
tikhomirov@93: public void added(Path fname) {
tikhomirov@93: if (needAdded) {
tikhomirov@93: if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: handler.handleStatus(new HgStatus(Added, fname, logHelper));
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93: public void removed(Path fname) {
tikhomirov@93: if (needRemoved) {
tikhomirov@93: if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: handler.handleStatus(new HgStatus(Removed, fname, logHelper));
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93: public void copied(Path fnameOrigin, Path fnameAdded) {
tikhomirov@93: if (needCopies) {
tikhomirov@93: if (matcher == null || matcher.accept(fnameAdded)) {
tikhomirov@164: // FIXME in fact, merged files may report 'copied from' as well, correct status kind thus may differ from Added
tikhomirov@128: handler.handleStatus(new HgStatus(Added, fnameAdded, fnameOrigin, logHelper));
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93: public void missing(Path fname) {
tikhomirov@93: if (needMissing) {
tikhomirov@93: if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: handler.handleStatus(new HgStatus(Missing, fname, logHelper));
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93: public void unknown(Path fname) {
tikhomirov@93: if (needUnknown) {
tikhomirov@93: if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: handler.handleStatus(new HgStatus(Unknown, fname, logHelper));
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93: public void clean(Path fname) {
tikhomirov@93: if (needClean) {
tikhomirov@93: if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: handler.handleStatus(new HgStatus(Clean, fname, logHelper));
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93: public void ignored(Path fname) {
tikhomirov@93: if (needIgnored) {
tikhomirov@93: if (matcher == null || matcher.accept(fname)) {
tikhomirov@128: handler.handleStatus(new HgStatus(Ignored, fname, logHelper));
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@93: }
tikhomirov@64: }
tikhomirov@64: }