tikhomirov@645: /* tikhomirov@645: * Copyright (c) 2013 TMate Software Ltd tikhomirov@645: * tikhomirov@645: * This program is free software; you can redistribute it and/or modify tikhomirov@645: * it under the terms of the GNU General Public License as published by tikhomirov@645: * the Free Software Foundation; version 2 of the License. tikhomirov@645: * tikhomirov@645: * This program is distributed in the hope that it will be useful, tikhomirov@645: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@645: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@645: * GNU General Public License for more details. tikhomirov@645: * tikhomirov@645: * For information on how to redistribute this software under tikhomirov@645: * the terms of a license other than GNU General Public License tikhomirov@645: * contact TMate Software at support@hg4j.com tikhomirov@645: */ tikhomirov@645: package org.tmatesoft.hg.core; tikhomirov@645: tikhomirov@645: import java.io.File; tikhomirov@645: import java.io.IOException; tikhomirov@645: import java.net.URL; tikhomirov@649: import java.util.ArrayList; tikhomirov@645: import java.util.List; tikhomirov@645: tikhomirov@645: import org.tmatesoft.hg.internal.BundleGenerator; tikhomirov@649: import org.tmatesoft.hg.internal.Internals; tikhomirov@649: import org.tmatesoft.hg.internal.PhasesHelper; tikhomirov@645: import org.tmatesoft.hg.internal.RepositoryComparator; tikhomirov@649: import org.tmatesoft.hg.internal.RevisionSet; tikhomirov@646: import org.tmatesoft.hg.repo.HgBookmarks; tikhomirov@645: import org.tmatesoft.hg.repo.HgBundle; tikhomirov@645: import org.tmatesoft.hg.repo.HgChangelog; tikhomirov@645: import org.tmatesoft.hg.repo.HgInternals; tikhomirov@645: import org.tmatesoft.hg.repo.HgInvalidStateException; tikhomirov@645: import org.tmatesoft.hg.repo.HgLookup; tikhomirov@645: import org.tmatesoft.hg.repo.HgParentChildMap; tikhomirov@649: import org.tmatesoft.hg.repo.HgPhase; tikhomirov@645: import org.tmatesoft.hg.repo.HgRemoteRepository; tikhomirov@645: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@645: import org.tmatesoft.hg.repo.HgRuntimeException; tikhomirov@645: import org.tmatesoft.hg.util.CancelledException; tikhomirov@646: import org.tmatesoft.hg.util.Pair; tikhomirov@645: import org.tmatesoft.hg.util.ProgressSupport; tikhomirov@649: import org.tmatesoft.hg.util.LogFacility.Severity; tikhomirov@645: tikhomirov@645: /** tikhomirov@645: * tikhomirov@645: * @author Artem Tikhomirov tikhomirov@645: * @author TMate Software Ltd. tikhomirov@645: */ tikhomirov@645: public class HgPushCommand extends HgAbstractCommand { tikhomirov@645: tikhomirov@645: private final HgRepository repo; tikhomirov@645: private HgRemoteRepository remoteRepo; tikhomirov@645: tikhomirov@645: public HgPushCommand(HgRepository hgRepo) { tikhomirov@645: repo = hgRepo; tikhomirov@645: } tikhomirov@645: tikhomirov@645: public HgPushCommand destination(HgRemoteRepository hgRemote) { tikhomirov@645: remoteRepo = hgRemote; tikhomirov@645: return this; tikhomirov@645: } tikhomirov@645: tikhomirov@645: public void execute() throws HgRemoteConnectionException, HgIOException, CancelledException, HgLibraryFailureException { tikhomirov@645: final ProgressSupport progress = getProgressSupport(null); tikhomirov@645: try { tikhomirov@645: progress.start(100); tikhomirov@645: // tikhomirov@645: // find out missing tikhomirov@645: // TODO refactor same code in HgOutgoingCommand #getComparator and #getParentHelper tikhomirov@649: final HgChangelog clog = repo.getChangelog(); tikhomirov@649: final HgParentChildMap parentHelper = new HgParentChildMap(clog); tikhomirov@645: parentHelper.init(); tikhomirov@645: final RepositoryComparator comparator = new RepositoryComparator(parentHelper, remoteRepo); tikhomirov@645: comparator.compare(new ProgressSupport.Sub(progress, 50), getCancelSupport(null, true)); tikhomirov@645: List l = comparator.getLocalOnlyRevisions(); tikhomirov@645: // tikhomirov@645: // prepare bundle tikhomirov@649: final Internals implRepo = HgInternals.getImplementationRepo(repo); tikhomirov@649: BundleGenerator bg = new BundleGenerator(implRepo); tikhomirov@645: File bundleFile = bg.create(l); tikhomirov@645: progress.worked(20); tikhomirov@645: HgBundle b = new HgLookup(repo.getSessionContext()).loadBundle(bundleFile); tikhomirov@645: // tikhomirov@645: // send changes tikhomirov@645: remoteRepo.unbundle(b, comparator.getRemoteHeads()); tikhomirov@645: progress.worked(20); tikhomirov@645: // tikhomirov@649: // update phase information tikhomirov@649: PhasesHelper phaseHelper = new PhasesHelper(implRepo, parentHelper); tikhomirov@649: if (phaseHelper.isCapableOfPhases()) { tikhomirov@649: RevisionSet outgoing = new RevisionSet(l); tikhomirov@649: RevisionSet presentSecret = phaseHelper.allSecret(); tikhomirov@649: RevisionSet presentDraft = phaseHelper.allDraft(); tikhomirov@649: RevisionSet secretLeft, draftLeft; tikhomirov@649: HgRemoteRepository.Phases remotePhases = remoteRepo.getPhases(); tikhomirov@649: if (remotePhases.isPublishingServer()) { tikhomirov@649: // although it's unlikely outgoing would affect secret changesets, tikhomirov@649: // it doesn't hurt to check secret roots along with draft ones tikhomirov@649: secretLeft = presentSecret.subtract(outgoing); tikhomirov@649: draftLeft = presentDraft.subtract(outgoing); tikhomirov@649: } else { tikhomirov@649: // shall merge local and remote phase states tikhomirov@649: ArrayList knownRemoteDraftRoots = new ArrayList(); tikhomirov@649: for (Nodeid rdr : remotePhases.draftRoots()) { tikhomirov@649: if (clog.isKnown(rdr)) { tikhomirov@649: knownRemoteDraftRoots.add(rdr); tikhomirov@649: } tikhomirov@649: } tikhomirov@649: // childrenOf(knownRemoteDraftRoots) is everything remote may treat as Draft tikhomirov@649: RevisionSet remoteDrafts = new RevisionSet(parentHelper.childrenOf(knownRemoteDraftRoots)); tikhomirov@649: List localChildrenNotSent = parentHelper.childrenOf(outgoing.heads(parentHelper).asList()); tikhomirov@649: // remote shall know only what we've sent, subtract revisions we didn't actually sent tikhomirov@649: remoteDrafts = remoteDrafts.subtract(new RevisionSet(localChildrenNotSent)); tikhomirov@649: // if there's a remote draft root that points to revision we know is public tikhomirov@649: RevisionSet remoteDraftsLocallyPublic = remoteDrafts.subtract(presentSecret).subtract(presentDraft); tikhomirov@649: if (!remoteDraftsLocallyPublic.isEmpty()) { tikhomirov@649: // foreach remoteDraftsLocallyPublic.heads() do push Draft->Public tikhomirov@649: for (Nodeid n : remoteDraftsLocallyPublic.heads(parentHelper)) { tikhomirov@649: try { tikhomirov@649: remoteRepo.updatePhase(HgPhase.Draft, HgPhase.Public, n); tikhomirov@649: } catch (HgRemoteConnectionException ex) { tikhomirov@649: implRepo.getLog().dump(getClass(), Severity.Error, ex, String.format("Failed to update phase of %s", n.shortNotation())); tikhomirov@649: } tikhomirov@649: } tikhomirov@649: remoteDrafts = remoteDrafts.subtract(remoteDraftsLocallyPublic); tikhomirov@649: } tikhomirov@649: // revisions that cease to be secret (gonna become Public), e.g. someone else pushed them tikhomirov@649: RevisionSet secretGone = presentSecret.intersect(remoteDrafts); tikhomirov@649: // trace parents of these published secret revisions tikhomirov@649: RevisionSet secretMadePublic = presentSecret.parentsOf(secretGone, parentHelper); tikhomirov@649: secretLeft = presentSecret.subtract(secretGone).subtract(secretMadePublic); tikhomirov@649: // same for drafts tikhomirov@649: RevisionSet draftGone = presentDraft.intersect(remoteDrafts); tikhomirov@649: RevisionSet draftMadePublic = presentDraft.parentsOf(draftGone, parentHelper); tikhomirov@649: draftLeft = presentDraft.subtract(draftGone).subtract(draftMadePublic); tikhomirov@649: } tikhomirov@649: final RevisionSet newDraftRoots = draftLeft.roots(parentHelper); tikhomirov@649: final RevisionSet newSecretRoots = secretLeft.roots(parentHelper); tikhomirov@649: phaseHelper.updateRoots(newDraftRoots.asList(), newSecretRoots.asList()); tikhomirov@649: } tikhomirov@645: progress.worked(5); tikhomirov@645: // tikhomirov@646: // update bookmark information tikhomirov@646: HgBookmarks localBookmarks = repo.getBookmarks(); tikhomirov@646: if (!localBookmarks.getAllBookmarks().isEmpty()) { tikhomirov@649: for (Pair bm : remoteRepo.getBookmarks()) { tikhomirov@646: Nodeid localRevision = localBookmarks.getRevision(bm.first()); tikhomirov@646: if (localRevision == null || !parentHelper.knownNode(bm.second())) { tikhomirov@646: continue; tikhomirov@646: } tikhomirov@646: // we know both localRevision and revision of remote bookmark, tikhomirov@646: // need to make sure we don't push older revision than it's at the server tikhomirov@646: if (parentHelper.isChild(bm.second(), localRevision)) { tikhomirov@646: remoteRepo.updateBookmark(bm.first(), bm.second(), localRevision); tikhomirov@646: } tikhomirov@646: } tikhomirov@646: } tikhomirov@646: // XXX WTF is obsolete in namespaces key?? tikhomirov@645: progress.worked(5); tikhomirov@645: } catch (IOException ex) { tikhomirov@645: throw new HgIOException(ex.getMessage(), null); // XXX not a nice idea to throw IOException from BundleGenerator#create tikhomirov@645: } catch (HgRepositoryNotFoundException ex) { tikhomirov@645: final HgInvalidStateException e = new HgInvalidStateException("Failed to load a just-created bundle"); tikhomirov@645: e.initCause(ex); tikhomirov@645: throw new HgLibraryFailureException(e); tikhomirov@645: } catch (HgRuntimeException ex) { tikhomirov@645: throw new HgLibraryFailureException(ex); tikhomirov@645: } finally { tikhomirov@645: progress.done(); tikhomirov@645: } tikhomirov@645: } tikhomirov@645: tikhomirov@645: /* tikhomirov@645: * To test, start a server: tikhomirov@645: * $ hg --config web.allow_push=* --config web.push_ssl=False --config server.validate=True --debug serve tikhomirov@645: */ tikhomirov@645: public static void main(String[] args) throws Exception { tikhomirov@645: final HgLookup hgLookup = new HgLookup(); tikhomirov@649: HgRepository r = hgLookup.detect("/home/artem/hg/test-phases/"); tikhomirov@645: HgRemoteRepository rr = hgLookup.detect(new URL("http://localhost:8000/")); tikhomirov@645: new HgPushCommand(r).destination(rr).execute(); tikhomirov@645: } tikhomirov@645: }