Mercurial > hg > mercurial
changeset 879:c0c1a3f4b6f7
Extract OperationContext without details needed only for changes collecting
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialProgress.java Sat Oct 04 23:16:32 2014 +0200 @@ -0,0 +1,33 @@ +/* + * 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 org.jetbrains.annotations.NotNull; + +public interface MercurialProgress { + + void reportProgress(@NotNull String progressMessage); + + void reportProgress(float percentage, @NotNull String stage); + + static MercurialProgress NO_OP = new MercurialProgress() { + public void reportProgress(@NotNull String progressMessage) { + } + public void reportProgress(float percentage, @NotNull String stage) { + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CollectChangesContext.java Sat Oct 04 23:16:32 2014 +0200 @@ -0,0 +1,313 @@ +/* + * 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 CollectChangesContext extends OperationContext { + + 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, 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 CollectChangesContext(@NotNull MercurialVcsSupport vcs, + @NotNull RepoFactory repoFactory, + @NotNull RepositoryStateData fromState, + @NotNull RepositoryStateData toState) { + this(vcs, repoFactory, new HashSet<String>(fromState.getBranchRevisions().values()), toState); + } + + public CollectChangesContext(@NotNull MercurialVcsSupport vcs, + @NotNull RepoFactory repoFactory, + @NotNull String fromVersion, + @NotNull String toVersion) { + this(vcs, repoFactory, setOf(fromVersion), RepositoryStateData.createSingleVersionState(toVersion)); + } + + public CollectChangesContext(@NotNull MercurialVcsSupport vcs, + @NotNull RepoFactory repoFactory, + @NotNull Collection<String> fromVersions, + @NotNull RepositoryStateData toState) { + super(vcs, repoFactory, MercurialProgress.NO_OP); + 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); + } + + 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; + } + + 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; + } +}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCollectChangesPolicy.java Fri Oct 03 22:48:45 2014 +0200 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCollectChangesPolicy.java Sat Oct 04 23:16:32 2014 +0200 @@ -96,7 +96,7 @@ @NotNull CheckoutRules rules) throws VcsException { List<ModificationData> changes = new ArrayList<ModificationData>(); HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root); - OperationContext ctx = new OperationContext(myVcs, myRepoFactory, fromState, toState); + CollectChangesContext ctx = new CollectChangesContext(myVcs, myRepoFactory, fromState, toState); ctx.setProgressConsumer(createProgressConsumer()); for (Map.Entry<String, String> entry : toState.getBranchRevisions().entrySet()) { String branch = entry.getKey(); @@ -149,7 +149,7 @@ @NotNull CheckoutRules checkoutRules) throws VcsException { if (currentVersion == null) return emptyList(); - OperationContext ctx = new OperationContext(myVcs, myRepoFactory, fromVersion, currentVersion); + CollectChangesContext ctx = new CollectChangesContext(myVcs, myRepoFactory, fromVersion, currentVersion); ctx.setProgressConsumer(createProgressConsumer()); List<ModificationData> changes = collectChanges(ctx, root, asList(fromVersion), currentVersion, checkoutRules); changes.addAll(getSubrepoChanges(ctx, root, changes)); @@ -184,7 +184,7 @@ @NotNull - private List<ModificationData> collectChanges(@NotNull OperationContext ctx, + private List<ModificationData> collectChanges(@NotNull CollectChangesContext ctx, @NotNull VcsRoot root, @NotNull Collection<String> fromVersion, @Nullable String currentVersion, @@ -205,7 +205,7 @@ @NotNull - private List<ChangeSet> getChangesets(@NotNull OperationContext ctx, + private List<ChangeSet> getChangesets(@NotNull CollectChangesContext ctx, @NotNull final HgVcsRoot root, @NotNull final Collection<String> fromVersions, @Nullable final String toVersion) throws VcsException { @@ -239,7 +239,7 @@ } } - private ModificationData createModificationData(@NotNull final OperationContext ctx, + private ModificationData createModificationData(@NotNull final CollectChangesContext ctx, @NotNull final ChangeSet cset, @NotNull final VcsRoot root, @NotNull final HgVcsRoot hgRoot, @@ -250,7 +250,7 @@ } @NotNull - private List<ModificationData> getSubrepoChanges(@NotNull OperationContext ctx, @NotNull VcsRoot root, @NotNull List<ModificationData> changes) throws VcsException { + private List<ModificationData> getSubrepoChanges(@NotNull CollectChangesContext ctx, @NotNull VcsRoot root, @NotNull List<ModificationData> changes) throws VcsException { if (changes.isEmpty()) return emptyList(); @@ -312,7 +312,7 @@ } - private List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull OperationContext ctx, @NotNull List<ModificationData> mainRootChanges) throws VcsException { + private List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull CollectChangesContext ctx, @NotNull List<ModificationData> mainRootChanges) throws VcsException { List<HgSubrepoConfigChange> subrepoConfigChanges = new ArrayList<HgSubrepoConfigChange>(); for (ModificationData m : mainRootChanges) { subrepoConfigChanges.addAll(getSubrepoConfigChanges(ctx, m)); @@ -322,7 +322,7 @@ @NotNull - private List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull OperationContext ctx, @NotNull ModificationData m) throws VcsException { + private List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull CollectChangesContext ctx, @NotNull ModificationData m) throws VcsException { List<HgSubrepoConfigChange> configChanges = new ArrayList<HgSubrepoConfigChange>(); HgVcsRoot mainRoot = myHgVcsRootFactory.createHgRoot(m.getVcsRoot()); @@ -352,7 +352,7 @@ @NotNull - private Map<String, String> getAttributes(@NotNull OperationContext ctx, + private Map<String, String> getAttributes(@NotNull CollectChangesContext ctx, @NotNull VcsRoot mainRoot, @NotNull HgVcsRoot mainHgRoot, @NotNull ModificationData m) throws VcsException {
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialModificationInfoBuilder.java Fri Oct 03 22:48:45 2014 +0200 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialModificationInfoBuilder.java Sat Oct 04 23:16:32 2014 +0200 @@ -48,7 +48,7 @@ @NotNull final ChangesConsumer consumer) throws VcsException { final HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root); - final OperationContext ctx = new OperationContext(myVcs, + final CollectChangesContext ctx = new CollectChangesContext(myVcs, myRepoFactory, Collections.<String>emptyList(), RepositoryStateData.createVersionState("", Collections.<String, String>emptyMap()));
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java Fri Oct 03 22:48:45 2014 +0200 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java Sat Oct 04 23:16:32 2014 +0200 @@ -779,7 +779,7 @@ } @NotNull - public ServerHgRepo createRepo(@NotNull OperationContext ctx, @NotNull HgVcsRoot root) throws VcsException { + public ServerHgRepo createRepo(@NotNull CollectChangesContext ctx, @NotNull HgVcsRoot root) throws VcsException { return ctx.createRepo(root, getWorkingDir(root)); }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ModificationDataFactory.java Fri Oct 03 22:48:45 2014 +0200 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ModificationDataFactory.java Sat Oct 04 23:16:32 2014 +0200 @@ -36,7 +36,7 @@ */ public class ModificationDataFactory { @NotNull - public static ModificationData createModificationData(@NotNull final OperationContext ctx, + public static ModificationData createModificationData(@NotNull final CollectChangesContext ctx, @NotNull final ChangeSet cset, @NotNull final VcsRoot root, @NotNull final CheckoutRules checkoutRules) throws VcsException { @@ -67,7 +67,7 @@ @NotNull - public static List<VcsChange> toVcsChanges(@NotNull OperationContext ctx, + public static List<VcsChange> toVcsChanges(@NotNull CollectChangesContext ctx, @NotNull List<FileStatus> modifiedFiles, @NotNull String prevVer, @NotNull String curVer,
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OperationContext.java Fri Oct 03 22:48:45 2014 +0200 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OperationContext.java Sat Oct 04 23:16:32 2014 +0200 @@ -1,328 +1,59 @@ -/* - * 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 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 RepositoryStateData fromState, - @NotNull RepositoryStateData toState) { - this(vcs, repoFactory, new HashSet<String>(fromState.getBranchRevisions().values()), toState); - } - - public OperationContext(@NotNull MercurialVcsSupport vcs, - @NotNull RepoFactory repoFactory, - @NotNull String fromVersion, - @NotNull String toVersion) { - this(vcs, repoFactory, setOf(fromVersion), RepositoryStateData.createSingleVersionState(toVersion)); - } - - public OperationContext(@NotNull MercurialVcsSupport vcs, - @NotNull RepoFactory repoFactory, - @NotNull Collection<String> fromVersions, - @NotNull RepositoryStateData toState) { - myVcs = vcs; - myRepoFactory = repoFactory; - 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; - } -} +/* + * 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 jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +public class OperationContext { + + protected final MercurialVcsSupport myVcs; + protected final RepoFactory myRepoFactory; + protected final MercurialProgress myProgress; + private final Map<String, HgVersion> myHgVersions = new HashMap<String, HgVersion>(); + + public OperationContext(@NotNull MercurialVcsSupport vcs, + @NotNull RepoFactory repoFactory, + @NotNull MercurialProgress progress) { + myVcs = vcs; + myRepoFactory = repoFactory; + myProgress = progress; + } + + + @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; + } + + + @NotNull + public ServerHgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir) throws VcsException { + return myRepoFactory.createRepo(root, workingDir); + } +}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgRepo.java Fri Oct 03 22:48:45 2014 +0200 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgRepo.java Sat Oct 04 23:16:32 2014 +0200 @@ -43,7 +43,7 @@ protected final MercurialClasspathTemplate myLogNoFilesTemplate = new MercurialClasspathTemplate("/buildServerResources/log.no.files.template", "hg.short.log.template"); protected final MercurialClasspathTemplate myDagTemplate = new MercurialClasspathTemplate("/buildServerResources/dag.template", "hg.dag.template"); protected final MercurialClasspathTemplate myFastLogTemplate = new MercurialClasspathTemplate("/buildServerResources/fastlog.template", "hg.fastlog.template"); - private OperationContext myContext; + private CollectChangesContext myContext; public ServerHgRepo(@NotNull CommandSettingsFactory commandSettingsFactory, @NotNull ServerPluginConfig config, @@ -55,7 +55,7 @@ myConfig = config; } - public void setOperationContext(@NotNull OperationContext context) { + public void setOperationContext(@NotNull CollectChangesContext context) { myContext = context; }