dmitry@567: package jetbrains.buildServer.buildTriggers.vcs.mercurial; dmitry@567: dmitry@567: import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*; dmitry@567: import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnknownRevisionException; dmitry@567: import jetbrains.buildServer.log.Loggers; dmitry@567: import jetbrains.buildServer.vcs.*; dmitry@567: import jetbrains.buildServer.vcs.impl.VcsRootImpl; dmitry@567: import org.jetbrains.annotations.NotNull; dmitry@567: import org.jetbrains.annotations.Nullable; dmitry@567: dmitry@567: import java.net.URISyntaxException; dmitry@567: import java.util.*; dmitry@567: dmitry@567: import static java.util.Arrays.asList; dmitry@567: import static java.util.Collections.emptyList; dmitry@567: import static java.util.Collections.emptyMap; dmitry@567: dmitry@567: public class MercurialCollectChangesPolicy implements CollectChangesBetweenRoots, CollectChangesBetweenRepositories { dmitry@567: dmitry@600: private final static AscendingRevNums ASCENDING_REV_NUMS = new AscendingRevNums(); dmitry@600: dmitry@567: private final MercurialVcsSupport myVcs; dmitry@567: private final ServerPluginConfig myConfig; dmitry@567: private final HgVcsRootFactory myHgVcsRootFactory; dmitry@567: private final RepoFactory myRepoFactory; dmitry@567: private final HgPathProvider myHgPathProvider; dmitry@567: dmitry@567: dmitry@567: public MercurialCollectChangesPolicy(@NotNull MercurialVcsSupport vcs, dmitry@567: @NotNull ServerPluginConfig config, dmitry@567: @NotNull HgVcsRootFactory hgVcsRootFactory, dmitry@567: @NotNull RepoFactory repoFactory, dmitry@567: @NotNull HgPathProvider hgPathProvider) { dmitry@567: myVcs = vcs; dmitry@567: myConfig = config; dmitry@567: myHgVcsRootFactory = hgVcsRootFactory; dmitry@567: myRepoFactory = repoFactory; dmitry@567: myHgPathProvider = hgPathProvider; dmitry@567: } dmitry@567: dmitry@567: dmitry@567: @NotNull dmitry@567: public RepositoryStateData getCurrentState(@NotNull VcsRoot root) throws VcsException { dmitry@567: HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root); dmitry@567: myVcs.syncRepository(hgRoot); dmitry@649: boolean includeTags = myConfig.useTagsAsBranches() && hgRoot.useTagsAsBranches(); dmitry@649: Map revisions = myVcs.createRepo(hgRoot).getBranchRevisions(myConfig.bookmarksEnabled(), includeTags); dmitry@567: String defaultBranchName = hgRoot.getBranchName(); dmitry@567: if (revisions.get(defaultBranchName) == null) { dmitry@567: throw new VcsException("Cannot find revision of the default branch '" + dmitry@590: defaultBranchName + "' of vcs root " + myVcs.describeVcsRoot(root)); dmitry@567: } dmitry@567: return RepositoryStateData.createVersionState(defaultBranchName, revisions); dmitry@567: } dmitry@567: dmitry@567: dmitry@567: @NotNull dmitry@567: public List collectChanges(@NotNull VcsRoot fromRoot, dmitry@567: @NotNull RepositoryStateData fromState, dmitry@567: @NotNull VcsRoot toRoot, dmitry@567: @NotNull RepositoryStateData toState, dmitry@567: @NotNull CheckoutRules rules) throws VcsException { dmitry@567: return collectChanges(toRoot, fromState, toState, rules); dmitry@567: } dmitry@567: dmitry@567: dmitry@567: @NotNull dmitry@567: public List collectChanges(@NotNull VcsRoot root, dmitry@567: @NotNull RepositoryStateData fromState, dmitry@567: @NotNull RepositoryStateData toState, dmitry@567: @NotNull CheckoutRules rules) throws VcsException { dmitry@567: List changes = new ArrayList(); dmitry@567: HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root); dmitry@567: OperationContext ctx = new OperationContext(myVcs, myRepoFactory, myHgPathProvider, fromState, toState); dmitry@567: for (Map.Entry entry : toState.getBranchRevisions().entrySet()) { dmitry@567: String branch = entry.getKey(); dmitry@567: String toRevision = entry.getValue(); dmitry@567: String fromRevision = fromState.getBranchRevisions().get(branch); dmitry@567: if (fromRevision == null) dmitry@567: fromRevision = fromState.getBranchRevisions().get(fromState.getDefaultBranchName()); dmitry@567: if (toRevision.equals(fromRevision) || fromRevision == null) dmitry@567: continue; dmitry@567: dmitry@567: Collection fromRevisions = ctx.getFromRevisionsForBranch(hgRoot, fromRevision, toRevision); dmitry@567: List branchChanges = collectChanges(ctx, root, fromRevisions, toRevision, rules); dmitry@567: for (ModificationData change : branchChanges) { dmitry@567: if (!ctx.isReportedModification(change)) { dmitry@567: changes.add(change); dmitry@567: ctx.markAsReported(change); dmitry@567: } dmitry@567: } dmitry@567: } dmitry@567: changes.addAll(getSubrepoChanges(ctx, root, changes)); dmitry@567: return changes; dmitry@567: } dmitry@567: dmitry@567: dmitry@567: @NotNull dmitry@567: public List collectChanges(@NotNull VcsRoot fromRoot, dmitry@567: @NotNull String fromRootRevision, dmitry@567: @NotNull VcsRoot toRoot, dmitry@567: @Nullable String toRootRevision, dmitry@567: @NotNull CheckoutRules checkoutRules) throws VcsException { dmitry@567: HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(toRoot); dmitry@567: myVcs.syncRepository(hgRoot); dmitry@696: String toRevision = toRootRevision; dmitry@696: if (toRevision == null) { dmitry@696: RepositoryStateData state = myVcs.getCollectChangesPolicy().getCurrentState(toRoot); dmitry@696: toRevision = state.getBranchRevisions().get(state.getDefaultBranchName()); dmitry@696: } dmitry@567: String mergeBase = getMergeBase(hgRoot, fromRootRevision, toRevision); dmitry@567: if (mergeBase == null) dmitry@567: return Collections.emptyList(); dmitry@567: return collectChanges(toRoot, mergeBase, toRootRevision, checkoutRules); dmitry@567: } dmitry@567: dmitry@567: dmitry@567: public List collectChanges(@NotNull VcsRoot root, dmitry@567: @NotNull String fromVersion, dmitry@567: @Nullable String currentVersion, dmitry@567: @NotNull CheckoutRules checkoutRules) throws VcsException { dmitry@567: if (currentVersion == null) dmitry@567: return emptyList(); dmitry@567: OperationContext ctx = new OperationContext(myVcs, myRepoFactory, myHgPathProvider, fromVersion, currentVersion); dmitry@567: List changes = collectChanges(ctx, root, asList(fromVersion), currentVersion, checkoutRules); dmitry@567: changes.addAll(getSubrepoChanges(ctx, root, changes)); dmitry@567: return changes; dmitry@567: } dmitry@567: dmitry@567: dmitry@567: @Nullable dmitry@567: private String getMergeBase(@NotNull HgVcsRoot root, @NotNull String revision1, @NotNull String revision2) throws VcsException { dmitry@567: String result = myVcs.createRepo(root).mergeBase() dmitry@567: .revision1(revision1) dmitry@567: .revision2(revision2) dmitry@567: .call(); dmitry@567: if (result == null) dmitry@567: result = getMinusNthCommit(root, 10); dmitry@567: return result; dmitry@567: } dmitry@567: dmitry@567: dmitry@567: @Nullable dmitry@567: private String getMinusNthCommit(@NotNull HgVcsRoot root, int n) throws VcsException { dmitry@567: LogCommand log = myVcs.createRepo(root).log() dmitry@567: .inBranch(root.getBranchName()) dmitry@567: .toNamedRevision(root.getBranchName()); dmitry@567: if (n > 0) dmitry@567: log.setLimit(n); dmitry@567: List changeSets = log.call(); dmitry@567: if (changeSets.isEmpty()) dmitry@567: return null; dmitry@567: return changeSets.get(0).getId(); dmitry@567: } dmitry@567: dmitry@567: dmitry@656: public List collectChanges(@NotNull OperationContext ctx, dmitry@656: @NotNull VcsRoot root, dmitry@656: @NotNull Collection fromVersion, dmitry@656: @Nullable String currentVersion, dmitry@656: @NotNull CheckoutRules checkoutRules) throws VcsException { dmitry@567: HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root); dmitry@567: ctx.syncRepository(hgRoot); dmitry@567: List result = new ArrayList(); dmitry@600: List csets = getChangesets(ctx, hgRoot, fromVersion, currentVersion); dmitry@600: //When commit has no changes in subrepo configuration we can reuse dmitry@600: //subrepo revision table calculated for its parent commit(s). To do dmitry@600: //that parents should be processed before children: dmitry@600: Collections.sort(csets, ASCENDING_REV_NUMS); dmitry@600: for (ChangeSet cset : csets) { dmitry@567: result.add(createModificationData(ctx, cset, root, checkoutRules)); dmitry@567: } dmitry@567: return result; dmitry@567: } dmitry@567: dmitry@567: dmitry@567: @NotNull dmitry@567: private List getChangesets(@NotNull OperationContext ctx, dmitry@567: @NotNull final HgVcsRoot root, dmitry@567: @NotNull final Collection fromVersions, dmitry@567: @Nullable final String toVersion) throws VcsException { dmitry@567: if (toVersion == null) dmitry@567: return emptyList(); dmitry@567: List fromCommits = new ArrayList(); dmitry@567: for (String fromVersion : fromVersions) { dmitry@567: fromCommits.add(new ChangeSetRevision(fromVersion).getId()); dmitry@567: } dmitry@567: String toCommit = new ChangeSetRevision(toVersion).getId(); dmitry@567: try { dmitry@567: ServerHgRepo repo = myVcs.createRepo(ctx, root); dmitry@567: List csets = repo.collectChanges(root) dmitry@567: .fromRevision(fromCommits) dmitry@567: .toRevision(toCommit) dmitry@568: .includeFromRevision(ctx.includeFromRevisions()) dmitry@567: .call(); dmitry@568: if (!ctx.includeFromRevisions()) { dmitry@568: Iterator iter = csets.iterator(); dmitry@568: while (iter.hasNext()) { dmitry@568: ChangeSet cset = iter.next(); dmitry@568: if (fromVersions.contains(cset.getId())) dmitry@568: iter.remove(); dmitry@568: } dmitry@567: } dmitry@567: return csets; dmitry@567: } catch (UnknownRevisionException e) { dmitry@567: Loggers.VCS.warn("Revision '" + e.getRevision() + "' is unknown, will return no changes"); dmitry@567: return emptyList(); dmitry@567: } dmitry@567: } dmitry@567: eugene@691: private ModificationData createModificationData(@NotNull final OperationContext ctx, dmitry@567: @NotNull final ChangeSet cset, dmitry@567: @NotNull final VcsRoot root, dmitry@567: @NotNull final CheckoutRules checkoutRules) throws VcsException { eugene@691: final ModificationData result = ModificationDataFactory.createModificationData(ctx, cset, root, checkoutRules); dmitry@600: result.setAttributes(getAttributes(ctx, root, result)); dmitry@567: return result; dmitry@567: } dmitry@567: dmitry@567: @NotNull dmitry@567: private List getSubrepoChanges(@NotNull OperationContext ctx, @NotNull VcsRoot root, @NotNull List changes) throws VcsException { dmitry@567: if (changes.isEmpty()) dmitry@567: return emptyList(); dmitry@567: dmitry@568: ctx.setIncludeFromRevisions(true); dmitry@568: dmitry@567: HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root); dmitry@567: if (!detectSubrepoChanges(hgRoot)) dmitry@567: return emptyList(); dmitry@567: dmitry@595: List subrepoConfigChanges = getSubrepoConfigChanges(ctx, changes); dmitry@567: List subrepoChanges = new ArrayList(); dmitry@567: dmitry@595: for (HgSubrepoConfigChange configChange : subrepoConfigChanges) { dmitry@595: SubRepo current = configChange.getCurrent(); dmitry@595: assert current != null; dmitry@595: dmitry@595: String subrepoUrl = current.url(); dmitry@595: String curRevision = current.revision(); dmitry@595: List prevRevisions = new ArrayList(0); dmitry@595: for (SubRepo prevSubrepo : configChange.getPrevious()) { dmitry@595: prevRevisions.add(prevSubrepo.revision()); dmitry@595: } dmitry@595: String path = configChange.getPath(); dmitry@595: dmitry@595: Map subrepoParams = new HashMap(hgRoot.getProperties()); dmitry@595: subrepoParams.put(Constants.REPOSITORY_PROP, subrepoUrl); dmitry@595: subrepoParams.put("teamcity.internal.subrepo", "true"); dmitry@595: subrepoParams.put("teamcity.internal.subrepo.path", path); dmitry@595: dmitry@595: VcsRootImpl subrepo = new VcsRootImpl(root.getId(), subrepoParams); dmitry@595: if (ctx.isProcessedSubrepoChanges(subrepo, prevRevisions, curRevision)) dmitry@567: continue; dmitry@595: List subChanges = collectChanges(ctx, subrepo, prevRevisions, curRevision, CheckoutRules.DEFAULT); dmitry@567: for (ModificationData m : subChanges) { dmitry@567: if (!ctx.isReportedModification(m)) { dmitry@567: subrepoChanges.add(m); dmitry@567: ctx.markAsReported(m); dmitry@567: } dmitry@567: } dmitry@595: ctx.markProcessedSubrepoChanges(subrepo, prevRevisions, curRevision); dmitry@567: } dmitry@567: dmitry@567: List subSubrepoChanges = getSubrepoChanges(ctx, root, subrepoChanges); dmitry@567: subrepoChanges.addAll(subSubrepoChanges); dmitry@567: return subrepoChanges; dmitry@567: } dmitry@567: dmitry@567: dmitry@567: private boolean detectSubrepoChanges(@NotNull HgVcsRoot root) { dmitry@567: return myConfig.detectSubrepoChanges() && root.detectSubrepoChanges(); dmitry@567: } dmitry@567: dmitry@567: dmitry@595: private List getSubrepoConfigChanges(@NotNull OperationContext ctx, @NotNull List mainRootChanges) throws VcsException { dmitry@595: List subrepoConfigChanges = new ArrayList(); dmitry@567: for (ModificationData m : mainRootChanges) { dmitry@594: subrepoConfigChanges.addAll(getSubrepoConfigChanges(ctx, m)); dmitry@567: } dmitry@567: return subrepoConfigChanges; dmitry@567: } dmitry@567: dmitry@567: dmitry@594: @NotNull dmitry@595: private List getSubrepoConfigChanges(@NotNull OperationContext ctx, @NotNull ModificationData m) throws VcsException { dmitry@595: List configChanges = new ArrayList(); dmitry@594: dmitry@594: HgVcsRoot mainRoot = myHgVcsRootFactory.createHgRoot(m.getVcsRoot()); dmitry@594: ServerHgRepo repo = myVcs.createRepo(ctx, mainRoot); dmitry@600: for (HgSubrepoConfigChange c : repo.getSubrepoConfigChanges(m)) { dmitry@595: if (!(c.subrepoUrlChanged() || c.subrepoAdded() || c.subrepoRemoved())) {//report only changes in revisions, because we collect changes only for such changes dmitry@595: //map url and path, relative to the main repository dmitry@595: try { dmitry@594: SubRepo currentSubrepo = c.getCurrent(); dmitry@594: assert currentSubrepo != null; dmitry@594: String subrepoUrl = ctx.getStringFromPool(currentSubrepo.resolveUrl(mainRoot.getRepository())); dmitry@594: String curRevision = ctx.getStringFromPool(currentSubrepo.revision()); dmitry@595: List prevSubrepos = new ArrayList(0); dmitry@594: String path = ctx.getStringFromPool(mainRoot.expandSubrepoPath(c.getPath())); dmitry@595: for (SubRepo prevSubrepo : c.getPrevious()) { dmitry@595: prevSubrepos.add(new SubRepo(path, subrepoUrl, ctx.getStringFromPool(prevSubrepo.revision()))); dmitry@595: } dmitry@595: configChanges.add(new HgSubrepoConfigChange(path, prevSubrepos, new SubRepo(path, subrepoUrl, curRevision))); dmitry@595: } catch (URISyntaxException e) { dmitry@595: throw new VcsException(e); dmitry@594: } dmitry@594: } dmitry@567: } dmitry@567: return configChanges; dmitry@567: } dmitry@567: dmitry@567: dmitry@567: @NotNull dmitry@600: private Map getAttributes(@NotNull OperationContext ctx, @NotNull VcsRoot mainRoot, @NotNull ModificationData m) throws VcsException { dmitry@567: Map attributes = new HashMap(); dmitry@567: HgVcsRoot root = myHgVcsRootFactory.createHgRoot(mainRoot); dmitry@567: if (detectSubrepoChanges(root)) { dmitry@567: try { dmitry@567: ServerHgRepo repo = myVcs.createRepo(ctx, root); dmitry@593: SubrepoRevisionAttributesBuilder attrBuilder = new SubrepoRevisionAttributesBuilder(); dmitry@600: for (SubRepo s : repo.getSubrepositories(m).values()) { dmitry@641: attrBuilder.addSubrepo(new SubrepoConfig(mainRoot) dmitry@594: .setSubrepoPath(ctx.getStringFromPool(root.expandSubrepoPath(s.path()))) dmitry@594: .setSubrepoRootParamDiff(Constants.REPOSITORY_PROP, ctx.getStringFromPool(s.resolveUrl(root.getRepository()))) dmitry@594: .setSubrepoRootParamDiff("teamcity.internal.subrepo", "true") dmitry@594: .setSubrepoRootParamDiff("teamcity.internal.subrepo.path", ctx.getStringFromPool(root.expandSubrepoPath(s.path()))) dmitry@594: .setSubrepoRevision(ctx.getStringFromPool(s.revision()))); dmitry@593: } dmitry@593: attributes.putAll(attrBuilder.buildAttributes()); dmitry@567: } catch (Exception e) { dmitry@567: Loggers.VCS.warn("Error while reporting subrepo config changes", e); Pavel@611: if (e instanceof VcsException) dmitry@567: throw (VcsException) e; dmitry@567: throw new VcsException(e); dmitry@567: } dmitry@567: } dmitry@574: if (attributes.isEmpty()) dmitry@574: return emptyMap(); dmitry@567: return attributes; dmitry@567: } dmitry@600: dmitry@600: private final static class AscendingRevNums implements Comparator { dmitry@600: public int compare(ChangeSet o1, ChangeSet o2) { dmitry@600: int revnum1 = o1.getRevNumber(); dmitry@600: int revnum2 = o2.getRevNumber(); dmitry@600: if (revnum1 < revnum2) dmitry@600: return -1; dmitry@600: if (revnum1 > revnum2) dmitry@600: return 1; dmitry@600: return 0; dmitry@600: } dmitry@600: } dmitry@567: }