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@654: import java.util.Collection; tikhomirov@654: import java.util.Collections; 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@653: import org.tmatesoft.hg.util.LogFacility.Severity; tikhomirov@650: import org.tmatesoft.hg.util.Outcome; tikhomirov@646: import org.tmatesoft.hg.util.Pair; tikhomirov@645: import org.tmatesoft.hg.util.ProgressSupport; tikhomirov@645: tikhomirov@645: /** tikhomirov@664: * 'hg push ' counterpart, send local changes to a remote server tikhomirov@645: * tikhomirov@664: * @since 1.2 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@654: private RevisionSet outgoing; 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@650: final Internals implRepo = HgInternals.getImplementationRepo(repo); tikhomirov@650: final PhasesHelper phaseHelper = new PhasesHelper(implRepo, parentHelper); 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@650: if (phaseHelper.isCapableOfPhases() && phaseHelper.withSecretRoots()) { tikhomirov@650: RevisionSet secret = phaseHelper.allSecret(); tikhomirov@650: outgoing = new RevisionSet(l).subtract(secret); tikhomirov@650: } else { tikhomirov@650: outgoing = new RevisionSet(l); tikhomirov@650: } tikhomirov@645: // tikhomirov@645: // prepare bundle tikhomirov@649: BundleGenerator bg = new BundleGenerator(implRepo); tikhomirov@650: File bundleFile = bg.create(outgoing.asList()); 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: if (phaseHelper.isCapableOfPhases()) { tikhomirov@649: HgRemoteRepository.Phases remotePhases = remoteRepo.getPhases(); tikhomirov@663: RevisionSet remoteDraftsLocalPublic = phaseHelper.synchronizeWithRemote(remotePhases, outgoing); tikhomirov@650: if (!remoteDraftsLocalPublic.isEmpty()) { tikhomirov@650: // foreach remoteDraftsLocallyPublic.heads() do push Draft->Public tikhomirov@650: for (Nodeid n : remoteDraftsLocalPublic.heads(parentHelper)) { tikhomirov@650: try { tikhomirov@650: Outcome upo = remoteRepo.updatePhase(HgPhase.Draft, HgPhase.Public, n); tikhomirov@650: if (!upo.isOk()) { tikhomirov@650: implRepo.getLog().dump(getClass(), Severity.Info, "Failed to update remote phase, reason: %s", upo.getMessage()); tikhomirov@650: } tikhomirov@650: } catch (HgRemoteConnectionException ex) { tikhomirov@650: implRepo.getLog().dump(getClass(), Severity.Error, ex, String.format("Failed to update phase of %s", n.shortNotation())); tikhomirov@650: } tikhomirov@650: } tikhomirov@650: } 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@668: b.unlink(); // keep the file only in case of failure 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@654: public Collection getPushedRevisions() { tikhomirov@654: return outgoing == null ? Collections.emptyList() : outgoing.asList(); tikhomirov@654: } tikhomirov@645: }