Mercurial > hg > mercurial
view mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OperationContext.java @ 875:b06ea5c379cc
more progress for 'hg pull' commands
author | Dmitry Neverov <dmitry.neverov@jetbrains.com> |
---|---|
date | Wed, 01 Oct 2014 14:21:31 +0200 |
parents | 95e7d2ff8945 |
children | 45311425ee3c |
line wrap: on
line source
/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.buildServer.buildTriggers.vcs.mercurial; import com.intellij.openapi.util.Pair; import gnu.trove.TLongObjectHashMap; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ProgressParser; import jetbrains.buildServer.util.Hash; import jetbrains.buildServer.util.graph.BFSVisitorAdapter; import jetbrains.buildServer.util.graph.DAG; import jetbrains.buildServer.vcs.ModificationData; import jetbrains.buildServer.vcs.RepositoryStateData; import jetbrains.buildServer.vcs.VcsException; import jetbrains.buildServer.vcs.VcsRoot; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.*; import static java.util.Collections.singleton; import static jetbrains.buildServer.util.CollectionsUtil.setOf; public class OperationContext { private final MercurialVcsSupport myVcs; private final RepoFactory myRepoFactory; private final HgPathProvider myHgPathProvider; private final Set<String> myUninterestingRevisions; private final RepositoryStateData myToState; private final Map<VcsRootKey, DAG<String>> myDags = new HashMap<VcsRootKey, DAG<String>>(); private Set<File> mySyncedDirs = new HashSet<File>(); private Map<String, HgVersion> myHgVersions = new HashMap<String, HgVersion>(); private Map<String, Set<SubrepoChangesInterval>> myProcessedSubrepoChanges = new HashMap<String, Set<SubrepoChangesInterval>>();//subrepo url -> processed changes intervals private Map<VcsRootKey, Set<String>> myRevisionsPerRoot = new HashMap<VcsRootKey, Set<String>>(); private Map<File, ServerHgRepo> myRepos = new HashMap<File, ServerHgRepo>(); private boolean myIncludeFromRevisions = false;//by default don't include them, they should be included only for subrepos private TLongObjectHashMap<String> myStringPool = new TLongObjectHashMap<String>(); private ProgressParser.ProgressConsumer myProgressConsumer; public OperationContext(@NotNull MercurialVcsSupport vcs, @NotNull RepoFactory repoFactory, @NotNull HgPathProvider hgPathProvider, @NotNull RepositoryStateData fromState, @NotNull RepositoryStateData toState) { this(vcs, repoFactory, hgPathProvider, new HashSet<String>(fromState.getBranchRevisions().values()), toState); } public OperationContext(@NotNull MercurialVcsSupport vcs, @NotNull RepoFactory repoFactory, @NotNull HgPathProvider hgPathProvider, @NotNull String fromVersion, @NotNull String toVersion) { this(vcs, repoFactory, hgPathProvider, setOf(fromVersion), RepositoryStateData.createSingleVersionState(toVersion)); } public OperationContext(@NotNull MercurialVcsSupport vcs, @NotNull RepoFactory repoFactory, @NotNull HgPathProvider hgPathProvider, @NotNull Collection<String> fromVersions, @NotNull RepositoryStateData toState) { myVcs = vcs; myRepoFactory = repoFactory; myHgPathProvider = hgPathProvider; myToState = toState; myUninterestingRevisions = new HashSet<String>(fromVersions); } public void syncRepository(@NotNull HgVcsRoot root, @Nullable ProgressParser.ProgressConsumer progressConsumer) throws VcsException { File dir = myVcs.getWorkingDir(root); if (mySyncedDirs.contains(dir)) return; SyncSettings<Void> settings = new SyncSettings<Void>(VcsCallable.NO_OP); settings.setProgressConsumer(progressConsumer); myVcs.syncRepository(root, settings); mySyncedDirs.add(dir); } @NotNull public HgVersion getHgVersion(@NotNull ServerHgRepo repo) throws VcsException { String hgPath = repo.getHgPath(); HgVersion cachedVersion = myHgVersions.get(hgPath); if (cachedVersion != null) return cachedVersion; HgVersion version = repo.version().call(); myHgVersions.put(hgPath, version); return version; } public void markProcessedSubrepoChanges(@NotNull VcsRoot subrepo, @NotNull List<String> previousSubrepoRevisions, @NotNull String currentSubrepoRevision) { String subrepoUrl = subrepo.getProperty(Constants.REPOSITORY_PROP); Set<SubrepoChangesInterval> processedSubrepoChanges = myProcessedSubrepoChanges.get(subrepoUrl); if (processedSubrepoChanges == null) { processedSubrepoChanges = new HashSet<SubrepoChangesInterval>(); myProcessedSubrepoChanges.put(subrepoUrl, processedSubrepoChanges); } processedSubrepoChanges.add(new SubrepoChangesInterval(previousSubrepoRevisions, currentSubrepoRevision)); } public boolean isProcessedSubrepoChanges(@NotNull VcsRoot subrepo, @NotNull List<String> previousSubrepoRevisions, @NotNull String currentSubrepoRevision) { String subrepoUrl = subrepo.getProperty(Constants.REPOSITORY_PROP); Set<SubrepoChangesInterval> processedSubrepoChanges = myProcessedSubrepoChanges.get(subrepoUrl); if (processedSubrepoChanges == null) return false; return processedSubrepoChanges.contains(new SubrepoChangesInterval(previousSubrepoRevisions, currentSubrepoRevision)); } @NotNull public ServerHgRepo createRepo(@NotNull final HgVcsRoot root, @NotNull final File workingDir) throws VcsException { ServerHgRepo repo = myRepos.get(workingDir); if (repo != null) { return repo; } repo = myRepoFactory.createRepo(root, workingDir); repo.setOperationContext(this); myRepos.put(workingDir, repo); return repo; } public boolean isReportedModification(@NotNull ModificationData m) { Set<String> revisions = getReportedRootRevisions(m); return revisions.contains(m.getVersion()); } public void markAsReported(@NotNull ModificationData m) { Set<String> revisions = getReportedRootRevisions(m); revisions.add(m.getVersion()); } @NotNull private Set<String> getReportedRootRevisions(@NotNull ModificationData m) { VcsRootKey key = VcsRootKey.create(m.getVcsRoot()); Set<String> revisions = myRevisionsPerRoot.get(key); if (revisions == null) { revisions = new HashSet<String>(); myRevisionsPerRoot.put(key, revisions); } return revisions; } public boolean includeFromRevisions() { return myIncludeFromRevisions; } public void setIncludeFromRevisions(boolean doInclude) { myIncludeFromRevisions = doInclude; } public String getStringFromPool(@Nullable String s) { if (s == null) return null; final long hash = Hash.calc(s); String reused = myStringPool.get(hash); if (reused != null) { if (!reused.equals(s)) // hash collision return s; return reused; } //noinspection RedundantStringConstructorCall reused = new String(s); myStringPool.put(hash, reused); return reused; } /** * Collecting changes is per branch, but we should take revisions of other branches * into account otherwise plugin can report redundant changes. Consider the following graph: * * default * | topic * | | * V V *103 * | 102 * | | *101| * |\| * | 100 * | | *99 | *..... * | | * |/ * 1 * * Let's say plugin collects changes between states {default:99, topic:100} and {default: 103, topic:102}. * If we run hg log -r 'ancestors(103)-ancestors(99)' it will return changes [1..103]. But changes * [1..100] were probably already reported and TeamCity will not persist them. * * This method detects such a cases and returns (99, 100) for 103. */ @NotNull public Collection<String> getFromRevisionsForBranch(@NotNull HgVcsRoot root, @NotNull String fromRevision, @NotNull String toRevision, @Nullable ProgressParser.ProgressConsumer progressConsumer) throws VcsException { syncRepository(root, progressConsumer); ServerHgRepo repo = createRepo(root, myVcs.getWorkingDir(root)); if (!repo.supportRevsets()) return singleton(fromRevision); Set<String> fromRevisions = new HashSet<String>(); if (myToState.getBranchRevisions().size() > 1) { VcsRootKey rootKey = VcsRootKey.create(root); DAG<String> dag = myDags.get(rootKey); if (dag == null) { dag = repo.loadDag(); myDags.put(rootKey, dag); } if (dag.containsNode(toRevision)) { FindIntervalVisitor visitor = new FindIntervalVisitor(dag, myUninterestingRevisions); dag.breadthFirstSearch(toRevision, visitor); fromRevisions.addAll(visitor.getEndpoints()); } else { fromRevisions.add(fromRevision); } } else { fromRevisions.add(fromRevision); } if (fromRevisions.isEmpty()) fromRevisions.add(toRevision); return fromRevisions; } private final static class SubrepoChangesInterval extends Pair<List<String>, String> { private SubrepoChangesInterval(@NotNull List<String> prevRevisions, @NotNull String currentRevision) { super(prevRevisions, currentRevision); } } private final static class VcsRootKey extends Pair<String, String> { private VcsRootKey(@NotNull String repositoryUrl, @Nullable String subrepoPath) { super(repositoryUrl, subrepoPath); } private static VcsRootKey create(@NotNull HgVcsRoot root) { return new VcsRootKey(root.getProperty(Constants.REPOSITORY_PROP), root.getProperty("teamcity.internal.subrepo.path")); } private static VcsRootKey create(@NotNull VcsRoot root) { return new VcsRootKey(root.getProperty(Constants.REPOSITORY_PROP), root.getProperty("teamcity.internal.subrepo.path")); } } private final static class FindIntervalVisitor extends BFSVisitorAdapter<String> { private final DAG<String> myDag; private final Collection<String> myUninterestingRevisions; private final Set<String> myVisitedUninterestingRevisions = new HashSet<String>(); private final Set<String> myEndpoints = new HashSet<String>(); private FindIntervalVisitor(@NotNull DAG<String> dag, @NotNull Collection<String> uninteresting) { myDag = dag; myUninterestingRevisions = uninteresting; } @Override public boolean discover(@NotNull String revision) { markUninteresting(); if (!isInteresting(revision)) { addEndpoint(revision); return false; } return true; } private void markUninteresting() { if (!myVisitedUninterestingRevisions.isEmpty()) return; for (String rev : myUninterestingRevisions) { if (myDag.containsNode(rev)) myDag.breadthFirstSearch(rev, new MarkUninterestingRevisions()); } } private boolean isInteresting(@NotNull String revision) { return !myVisitedUninterestingRevisions.contains(revision); } private void addEndpoint(@NotNull String revision) { myEndpoints.add(revision); } @NotNull public Set<String> getEndpoints() { return myEndpoints; } private class MarkUninterestingRevisions extends BFSVisitorAdapter<String> { @Override public boolean discover(@NotNull String node) { if (myVisitedUninterestingRevisions.contains(node)) return false; myVisitedUninterestingRevisions.add(node); return true; } } } @Nullable public ProgressParser.ProgressConsumer getProgressConsumer() { return myProgressConsumer; } public void setProgressConsumer(ProgressParser.ProgressConsumer progressConsumer) { myProgressConsumer = progressConsumer; } }