Mercurial > hg > mercurial
view mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CollectChangesContext.java @ 915:6b5e83970a26 Hajipur-9.0.x
Fix test: test repository requires a more recent mercurial
author | Dmitry Neverov <dmitry.neverov@jetbrains.com> |
---|---|
date | Fri, 02 Jan 2015 12:36:52 +0100 |
parents | 39ff04730ccc |
children | 31ac1d822fd7 |
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.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 CollectChangesContext extends OperationContext { private final Set<String> myUninterestingRevisions; private final Map<VcsRootKey, DAG<String>> myDags = new HashMap<VcsRootKey, DAG<String>>(); private Set<File> mySyncedDirs = new HashSet<File>(); 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>(); public CollectChangesContext(@NotNull MercurialVcsSupport vcs, @NotNull RepoFactory repoFactory, @NotNull MercurialProgress progress, @NotNull RepositoryStateData fromState) { this(vcs, repoFactory, progress, new HashSet<String>(fromState.getBranchRevisions().values())); } public CollectChangesContext(@NotNull MercurialVcsSupport vcs, @NotNull RepoFactory repoFactory, @NotNull MercurialProgress progress, @NotNull String fromVersion) { this(vcs, repoFactory, progress, setOf(fromVersion)); } public CollectChangesContext(@NotNull MercurialVcsSupport vcs, @NotNull RepoFactory repoFactory, @NotNull MercurialProgress progress, @NotNull Collection<String> fromVersions) { super(vcs, repoFactory, progress); myUninterestingRevisions = new HashSet<String>(fromVersions); } public void syncRepository(@NotNull HgVcsRoot root) throws VcsException { File dir = myVcs.getWorkingDir(root); if (mySyncedDirs.contains(dir)) return; super.syncRepository(root); mySyncedDirs.add(dir); } 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 = super.createRepo(root, workingDir); repo.setOperationContext(this); myRepos.put(workingDir, repo); return repo; } @NotNull @Override public ServerHgRepo createRepo(@NotNull HgVcsRoot root) throws VcsException { File workingDir = myVcs.getWorkingDir(root); return createRepo(root, workingDir); } 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, @NotNull RepositoryStateData toState) throws VcsException { syncRepository(root); ServerHgRepo repo = createRepo(root, myVcs.getWorkingDir(root)); if (!repo.supportRevsets()) return singleton(fromRevision); Set<String> fromRevisions = new HashSet<String>(); if (toState.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; } } } }