kitaev@213: /* kitaev@213: * Copyright (c) 2011 TMate Software Ltd kitaev@213: * kitaev@213: * This program is free software; you can redistribute it and/or modify kitaev@213: * it under the terms of the GNU General Public License as published by kitaev@213: * the Free Software Foundation; version 2 of the License. kitaev@213: * kitaev@213: * This program is distributed in the hope that it will be useful, kitaev@213: * but WITHOUT ANY WARRANTY; without even the implied warranty of kitaev@213: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the kitaev@213: * GNU General Public License for more details. kitaev@213: * kitaev@213: * For information on how to redistribute this software under kitaev@213: * the terms of a license other than GNU General Public License kitaev@213: * contact TMate Software at support@hg4j.com kitaev@213: */ kitaev@213: package org.tmatesoft.hg.core; kitaev@213: kitaev@213: import java.io.IOException; kitaev@213: import java.util.ArrayList; kitaev@213: import java.util.HashSet; kitaev@213: import java.util.Iterator; kitaev@213: import java.util.LinkedHashSet; kitaev@213: import java.util.LinkedList; kitaev@213: import java.util.List; kitaev@213: import java.util.Set; kitaev@213: import java.util.TreeSet; kitaev@213: kitaev@213: import org.tmatesoft.hg.internal.RepositoryComparator; kitaev@213: import org.tmatesoft.hg.internal.RepositoryComparator.BranchChain; kitaev@213: import org.tmatesoft.hg.repo.HgBundle; kitaev@213: import org.tmatesoft.hg.repo.HgChangelog; kitaev@213: import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; kitaev@213: import org.tmatesoft.hg.repo.HgRemoteRepository; kitaev@213: import org.tmatesoft.hg.repo.HgRepository; kitaev@213: import org.tmatesoft.hg.util.CancelledException; kitaev@213: kitaev@213: /** kitaev@213: * Command to find out changes available in a remote repository, missing locally. kitaev@213: * kitaev@213: * @author Artem Tikhomirov kitaev@213: * @author TMate Software Ltd. kitaev@213: */ kitaev@213: public class HgIncomingCommand { kitaev@213: kitaev@213: private final HgRepository localRepo; kitaev@213: private HgRemoteRepository remoteRepo; kitaev@213: @SuppressWarnings("unused") kitaev@213: private boolean includeSubrepo; kitaev@213: private RepositoryComparator comparator; kitaev@213: private List missingBranches; kitaev@213: private HgChangelog.ParentWalker parentHelper; kitaev@213: private Set branches; kitaev@213: kitaev@213: public HgIncomingCommand(HgRepository hgRepo) { kitaev@213: localRepo = hgRepo; kitaev@213: } kitaev@213: kitaev@213: public HgIncomingCommand against(HgRemoteRepository hgRemote) { kitaev@213: remoteRepo = hgRemote; kitaev@213: comparator = null; kitaev@213: missingBranches = null; kitaev@213: return this; kitaev@213: } kitaev@213: kitaev@213: /** kitaev@213: * Select specific branch to push. kitaev@213: * Multiple branch specification possible (changeset from any of these would be included in result). kitaev@213: * Note, {@link #executeLite(Object)} does not respect this setting. kitaev@213: * kitaev@213: * @param branch - branch name, case-sensitive, non-null. kitaev@213: * @return this for convenience kitaev@213: * @throws IllegalArgumentException when branch argument is null kitaev@213: */ kitaev@213: public HgIncomingCommand branch(String branch) { kitaev@213: if (branch == null) { kitaev@213: throw new IllegalArgumentException(); kitaev@213: } kitaev@213: if (branches == null) { kitaev@213: branches = new TreeSet(); kitaev@213: } kitaev@213: branches.add(branch); kitaev@213: return this; kitaev@213: } kitaev@213: kitaev@213: /** kitaev@213: * PLACEHOLDER, NOT IMPLEMENTED YET. kitaev@213: * kitaev@213: * Whether to include sub-repositories when collecting changes, default is true XXX or false? kitaev@213: * @return this for convenience kitaev@213: */ kitaev@213: public HgIncomingCommand subrepo(boolean include) { kitaev@213: includeSubrepo = include; kitaev@213: throw HgRepository.notImplemented(); kitaev@213: } kitaev@213: kitaev@213: /** kitaev@213: * Lightweight check for incoming changes, gives only list of revisions to pull. kitaev@213: * Reported changes are from any branch (limits set by {@link #branch(String)} are not taken into account. kitaev@213: * kitaev@213: * @param context anything hg4j can use to get progress and/or cancel support kitaev@213: * @return list of nodes present at remote and missing locally kitaev@213: * @throws HgException kitaev@213: * @throws CancelledException kitaev@213: */ kitaev@213: public List executeLite(Object context) throws HgException, CancelledException { kitaev@213: LinkedHashSet result = new LinkedHashSet(); kitaev@213: RepositoryComparator repoCompare = getComparator(context); kitaev@213: for (BranchChain bc : getMissingBranches(context)) { kitaev@213: List missing = repoCompare.visitBranches(bc); kitaev@213: HashSet common = new HashSet(); // ordering is irrelevant kitaev@213: repoCompare.collectKnownRoots(bc, common); kitaev@213: // missing could only start with common elements. Once non-common, rest is just distinct branch revision trails. kitaev@213: for (Iterator it = missing.iterator(); it.hasNext() && common.contains(it.next()); it.remove()) ; kitaev@213: result.addAll(missing); kitaev@213: } kitaev@213: ArrayList rv = new ArrayList(result); kitaev@213: return rv; kitaev@213: } kitaev@213: kitaev@213: /** kitaev@213: * Full information about incoming changes kitaev@213: * kitaev@213: * @throws HgException kitaev@213: * @throws CancelledException kitaev@213: */ kitaev@213: public void executeFull(final HgChangesetHandler handler) throws HgException, CancelledException { kitaev@213: if (handler == null) { kitaev@213: throw new IllegalArgumentException("Delegate can't be null"); kitaev@213: } kitaev@213: final List common = getCommon(handler); kitaev@213: HgBundle changegroup = remoteRepo.getChanges(common); kitaev@213: try { kitaev@213: changegroup.changes(localRepo, new HgChangelog.Inspector() { kitaev@213: private int localIndex; kitaev@213: private final HgChangelog.ParentWalker parentHelper; kitaev@213: private final ChangesetTransformer transformer; kitaev@213: kitaev@213: { kitaev@213: transformer = new ChangesetTransformer(localRepo, handler, getParentHelper()); kitaev@213: transformer.limitBranches(branches); kitaev@213: parentHelper = getParentHelper(); kitaev@213: // new revisions, if any, would be added after all existing, and would get numbered started with last+1 kitaev@213: localIndex = localRepo.getChangelog().getRevisionCount(); kitaev@213: } kitaev@213: kitaev@213: public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { kitaev@213: if (parentHelper.knownNode(nodeid)) { kitaev@213: if (!common.contains(nodeid)) { kitaev@213: throw new HgBadStateException("Bundle shall not report known nodes other than roots we've supplied"); kitaev@213: } kitaev@213: return; kitaev@213: } kitaev@213: transformer.next(localIndex++, nodeid, cset); kitaev@213: } kitaev@213: }); kitaev@213: } catch (IOException ex) { kitaev@213: throw new HgException(ex); kitaev@213: } kitaev@213: } kitaev@213: kitaev@213: private RepositoryComparator getComparator(Object context) throws HgException, CancelledException { kitaev@213: if (remoteRepo == null) { kitaev@213: throw new HgBadArgumentException("Shall specify remote repository to compare against", null); kitaev@213: } kitaev@213: if (comparator == null) { kitaev@213: comparator = new RepositoryComparator(getParentHelper(), remoteRepo); kitaev@213: // comparator.compare(context); // XXX meanwhile we use distinct path to calculate common kitaev@213: } kitaev@213: return comparator; kitaev@213: } kitaev@213: kitaev@213: private HgChangelog.ParentWalker getParentHelper() { kitaev@213: if (parentHelper == null) { kitaev@213: parentHelper = localRepo.getChangelog().new ParentWalker(); kitaev@213: parentHelper.init(); kitaev@213: } kitaev@213: return parentHelper; kitaev@213: } kitaev@213: kitaev@213: private List getMissingBranches(Object context) throws HgException, CancelledException { kitaev@213: if (missingBranches == null) { kitaev@213: missingBranches = getComparator(context).calculateMissingBranches(); kitaev@213: } kitaev@213: return missingBranches; kitaev@213: } kitaev@213: kitaev@213: private List getCommon(Object context) throws HgException, CancelledException { kitaev@213: // return getComparator(context).getCommon(); kitaev@213: final LinkedHashSet common = new LinkedHashSet(); kitaev@213: // XXX common can be obtained from repoCompare, but at the moment it would almost duplicate work of calculateMissingBranches kitaev@213: // once I refactor latter, common shall be taken from repoCompare. kitaev@213: RepositoryComparator repoCompare = getComparator(context); kitaev@213: for (BranchChain bc : getMissingBranches(context)) { kitaev@213: repoCompare.collectKnownRoots(bc, common); kitaev@213: } kitaev@213: return new LinkedList(common); kitaev@213: } kitaev@213: }