tikhomirov@181: /* tikhomirov@628: * Copyright (c) 2011-2013 TMate Software Ltd tikhomirov@181: * tikhomirov@181: * This program is free software; you can redistribute it and/or modify tikhomirov@181: * it under the terms of the GNU General Public License as published by tikhomirov@181: * the Free Software Foundation; version 2 of the License. tikhomirov@181: * tikhomirov@181: * This program is distributed in the hope that it will be useful, tikhomirov@181: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@181: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@181: * GNU General Public License for more details. tikhomirov@181: * tikhomirov@181: * For information on how to redistribute this software under tikhomirov@181: * the terms of a license other than GNU General Public License tikhomirov@181: * contact TMate Software at support@hg4j.com tikhomirov@181: */ tikhomirov@181: package org.tmatesoft.hg.core; tikhomirov@181: tikhomirov@192: import java.util.ArrayList; tikhomirov@202: import java.util.HashSet; tikhomirov@202: import java.util.Iterator; tikhomirov@192: import java.util.LinkedHashSet; tikhomirov@192: import java.util.LinkedList; tikhomirov@181: import java.util.List; tikhomirov@194: import java.util.Set; tikhomirov@194: import java.util.TreeSet; tikhomirov@181: tikhomirov@526: import org.tmatesoft.hg.internal.Internals; tikhomirov@192: import org.tmatesoft.hg.internal.RepositoryComparator; tikhomirov@192: import org.tmatesoft.hg.internal.RepositoryComparator.BranchChain; tikhomirov@192: import org.tmatesoft.hg.repo.HgBundle; tikhomirov@192: import org.tmatesoft.hg.repo.HgChangelog; tikhomirov@427: import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; tikhomirov@423: import org.tmatesoft.hg.repo.HgInvalidStateException; tikhomirov@628: import org.tmatesoft.hg.repo.HgParentChildMap; tikhomirov@192: import org.tmatesoft.hg.repo.HgRemoteRepository; tikhomirov@181: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@427: import org.tmatesoft.hg.repo.HgRuntimeException; tikhomirov@181: import org.tmatesoft.hg.util.CancelledException; tikhomirov@215: import org.tmatesoft.hg.util.ProgressSupport; tikhomirov@181: tikhomirov@181: /** tikhomirov@192: * Command to find out changes available in a remote repository, missing locally. tikhomirov@192: * tikhomirov@181: * @author Artem Tikhomirov tikhomirov@181: * @author TMate Software Ltd. tikhomirov@181: */ tikhomirov@215: public class HgIncomingCommand extends HgAbstractCommand { tikhomirov@181: tikhomirov@192: private final HgRepository localRepo; tikhomirov@192: private HgRemoteRepository remoteRepo; tikhomirov@202: @SuppressWarnings("unused") tikhomirov@181: private boolean includeSubrepo; tikhomirov@192: private RepositoryComparator comparator; tikhomirov@192: private List missingBranches; tikhomirov@432: private HgParentChildMap parentHelper; tikhomirov@194: private Set branches; tikhomirov@181: tikhomirov@181: public HgIncomingCommand(HgRepository hgRepo) { tikhomirov@192: localRepo = hgRepo; tikhomirov@192: } tikhomirov@192: tikhomirov@192: public HgIncomingCommand against(HgRemoteRepository hgRemote) { tikhomirov@192: remoteRepo = hgRemote; tikhomirov@192: comparator = null; tikhomirov@192: missingBranches = null; tikhomirov@192: return this; tikhomirov@181: } tikhomirov@181: tikhomirov@181: /** tikhomirov@194: * Select specific branch to push. tikhomirov@194: * Multiple branch specification possible (changeset from any of these would be included in result). tikhomirov@419: * Note, {@link #executeLite()} does not respect this setting. tikhomirov@192: * tikhomirov@194: * @param branch - branch name, case-sensitive, non-null. tikhomirov@181: * @return this for convenience tikhomirov@194: * @throws IllegalArgumentException when branch argument is null tikhomirov@181: */ tikhomirov@181: public HgIncomingCommand branch(String branch) { tikhomirov@194: if (branch == null) { tikhomirov@194: throw new IllegalArgumentException(); tikhomirov@194: } tikhomirov@194: if (branches == null) { tikhomirov@194: branches = new TreeSet(); tikhomirov@194: } tikhomirov@194: branches.add(branch); tikhomirov@194: return this; tikhomirov@181: } tikhomirov@181: tikhomirov@181: /** tikhomirov@192: * PLACEHOLDER, NOT IMPLEMENTED YET. tikhomirov@192: * tikhomirov@181: * Whether to include sub-repositories when collecting changes, default is true XXX or false? tikhomirov@181: * @return this for convenience tikhomirov@181: */ tikhomirov@181: public HgIncomingCommand subrepo(boolean include) { tikhomirov@181: includeSubrepo = include; tikhomirov@526: throw Internals.notImplemented(); tikhomirov@181: } tikhomirov@181: tikhomirov@181: /** tikhomirov@194: * Lightweight check for incoming changes, gives only list of revisions to pull. tikhomirov@194: * Reported changes are from any branch (limits set by {@link #branch(String)} are not taken into account. tikhomirov@181: * tikhomirov@181: * @return list of nodes present at remote and missing locally tikhomirov@295: * @throws HgRemoteConnectionException when failed to communicate with remote repository tikhomirov@427: * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state tikhomirov@380: * @throws CancelledException if execution of the command was cancelled tikhomirov@181: */ tikhomirov@427: public List executeLite() throws HgException, CancelledException { tikhomirov@427: try { tikhomirov@427: LinkedHashSet result = new LinkedHashSet(); tikhomirov@427: RepositoryComparator repoCompare = getComparator(); tikhomirov@427: for (BranchChain bc : getMissingBranches()) { tikhomirov@427: List missing = repoCompare.visitBranches(bc); tikhomirov@427: HashSet common = new HashSet(); // ordering is irrelevant tikhomirov@427: repoCompare.collectKnownRoots(bc, common); tikhomirov@427: // missing could only start with common elements. Once non-common, rest is just distinct branch revision trails. tikhomirov@427: for (Iterator it = missing.iterator(); it.hasNext() && common.contains(it.next()); it.remove()) ; tikhomirov@427: result.addAll(missing); tikhomirov@427: } tikhomirov@427: ArrayList rv = new ArrayList(result); tikhomirov@427: return rv; tikhomirov@427: } catch (HgRuntimeException ex) { tikhomirov@427: throw new HgLibraryFailureException(ex); tikhomirov@192: } tikhomirov@181: } tikhomirov@181: tikhomirov@181: /** tikhomirov@181: * Full information about incoming changes tikhomirov@181: * tikhomirov@295: * @throws HgCallbackTargetException to re-throw exception from the handler tikhomirov@427: * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state tikhomirov@380: * @throws CancelledException if execution of the command was cancelled tikhomirov@181: */ tikhomirov@427: public void executeFull(final HgChangesetHandler handler) throws HgCallbackTargetException, HgException, CancelledException { tikhomirov@192: if (handler == null) { tikhomirov@192: throw new IllegalArgumentException("Delegate can't be null"); tikhomirov@192: } tikhomirov@215: final ProgressSupport ps = getProgressSupport(handler); tikhomirov@192: try { tikhomirov@628: final List common = getCommon(); tikhomirov@628: HgBundle changegroup = remoteRepo.getChanges(common); tikhomirov@322: final ChangesetTransformer transformer = new ChangesetTransformer(localRepo, handler, getParentHelper(), ps, getCancelSupport(handler, true)); tikhomirov@215: transformer.limitBranches(branches); tikhomirov@192: changegroup.changes(localRepo, new HgChangelog.Inspector() { tikhomirov@208: private int localIndex; tikhomirov@432: private final HgParentChildMap parentHelper; tikhomirov@208: tikhomirov@192: { tikhomirov@192: parentHelper = getParentHelper(); tikhomirov@208: // new revisions, if any, would be added after all existing, and would get numbered started with last+1 tikhomirov@208: localIndex = localRepo.getChangelog().getRevisionCount(); tikhomirov@192: } tikhomirov@192: tikhomirov@628: public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) throws HgRuntimeException { tikhomirov@192: if (parentHelper.knownNode(nodeid)) { tikhomirov@698: // FIXME getCommon() and remote.changegroup do not work together nicely. tikhomirov@698: // e.g. for hgtest-annotate-merge repository and TestIncoming, common reports r0 and r5 (ancestor of r5) tikhomirov@698: // because there's a distinct branch from r0 (in addition to those after r5). tikhomirov@698: // remote.changegroup however answers with revisions that are children of either, tikhomirov@698: /// so revisions 0..5 are reported as well and the next check fails. Instead, shall pass tikhomirov@698: // not common, but 'first to load' to remote.changegroup() or use another method (e.g. getbundle) tikhomirov@698: // Note, sending r5 only (i.e. checking for ancestors in common) won't help, changegroup sends children of tikhomirov@698: // requested roots only, and doesn't look for anything else tikhomirov@698: // if (!common.contains(nodeid)) { tikhomirov@698: // throw new HgInvalidStateException("Bundle shall not report known nodes other than roots we've supplied"); tikhomirov@698: // } tikhomirov@192: return; tikhomirov@192: } tikhomirov@208: transformer.next(localIndex++, nodeid, cset); tikhomirov@192: } tikhomirov@192: }); tikhomirov@215: transformer.checkFailure(); tikhomirov@427: } catch (HgRuntimeException ex) { tikhomirov@427: throw new HgLibraryFailureException(ex); tikhomirov@215: } finally { tikhomirov@215: ps.done(); tikhomirov@192: } tikhomirov@192: } tikhomirov@192: tikhomirov@628: private RepositoryComparator getComparator() throws CancelledException, HgRuntimeException { tikhomirov@192: if (remoteRepo == null) { tikhomirov@215: throw new IllegalArgumentException("Shall specify remote repository to compare against", null); tikhomirov@192: } tikhomirov@192: if (comparator == null) { tikhomirov@192: comparator = new RepositoryComparator(getParentHelper(), remoteRepo); tikhomirov@202: // comparator.compare(context); // XXX meanwhile we use distinct path to calculate common tikhomirov@192: } tikhomirov@192: return comparator; tikhomirov@192: } tikhomirov@192: tikhomirov@628: private HgParentChildMap getParentHelper() throws HgRuntimeException { tikhomirov@192: if (parentHelper == null) { tikhomirov@432: parentHelper = new HgParentChildMap(localRepo.getChangelog()); tikhomirov@192: parentHelper.init(); tikhomirov@192: } tikhomirov@192: return parentHelper; tikhomirov@192: } tikhomirov@192: tikhomirov@628: private List getMissingBranches() throws HgRemoteConnectionException, CancelledException, HgRuntimeException { tikhomirov@192: if (missingBranches == null) { tikhomirov@215: missingBranches = getComparator().calculateMissingBranches(); tikhomirov@192: } tikhomirov@192: return missingBranches; tikhomirov@192: } tikhomirov@192: tikhomirov@628: private List getCommon() throws HgRemoteConnectionException, CancelledException, HgRuntimeException { tikhomirov@202: // return getComparator(context).getCommon(); tikhomirov@192: final LinkedHashSet common = new LinkedHashSet(); tikhomirov@192: // XXX common can be obtained from repoCompare, but at the moment it would almost duplicate work of calculateMissingBranches tikhomirov@192: // once I refactor latter, common shall be taken from repoCompare. tikhomirov@215: RepositoryComparator repoCompare = getComparator(); tikhomirov@215: for (BranchChain bc : getMissingBranches()) { tikhomirov@202: repoCompare.collectKnownRoots(bc, common); tikhomirov@192: } tikhomirov@192: return new LinkedList(common); tikhomirov@181: } tikhomirov@181: }