jetbrains.buildServer.buildTriggers.vcs.git.GitMapFullPath.java Source code

Java tutorial

Introduction

Here is the source code for jetbrains.buildServer.buildTriggers.vcs.git.GitMapFullPath.java

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.git;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import jetbrains.buildServer.parameters.ReferencesResolverUtil;
import jetbrains.buildServer.vcs.VcsException;
import jetbrains.buildServer.vcs.VcsRootEntry;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.URIish;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.*;

import static com.intellij.openapi.util.text.StringUtil.isEmpty;

/**
* @author kir
*/
public class GitMapFullPath {

    private static final Logger LOG = Logger.getInstance(GitMapFullPath.class.getName());
    private final ServerPluginConfig myConfig;
    private final RevisionsCache myCache;
    private CommitLoader myCommitLoader;

    public GitMapFullPath(@NotNull ServerPluginConfig config, @NotNull RevisionsCache cache) {
        myConfig = config;
        myCache = cache;
    }

    public void setCommitLoader(@NotNull CommitLoader commitLoader) {
        myCommitLoader = commitLoader;
    }

    @NotNull
    public Collection<String> mapFullPath(@NotNull OperationContext context, @NotNull VcsRootEntry rootEntry,
            @NotNull String path) throws VcsException, IOException {
        GitVcsRoot root = context.getGitRoot();
        if (LOG.isDebugEnabled())
            LOG.debug("MapFullPath root: " + LogUtil.describe(root) + ", path " + path);
        FullPath fullPath = new FullPath(path);
        if (!fullPath.isValid()) {
            LOG.warn("Invalid path: " + path);
            return Collections.emptySet();
        }

        //match by revision
        if (fullPath.containsRevision()) {
            if (fullPath.containsHintRevision()) {
                //if full path has a hint revision, first check if repository contains it;
                //a hint revision should rarely change and most likely will be cached
                if (repositoryContainsRevision(context, rootEntry, fullPath.getHintRevision(),
                        RevisionCacheType.HINT_CACHE)
                        && repositoryContainsRevision(context, rootEntry, fullPath.getRevision(),
                                RevisionCacheType.COMMIT_CACHE))
                    return fullPath.getMappedPaths();
            } else {
                if (repositoryContainsRevision(context, rootEntry, fullPath.getRevision(),
                        RevisionCacheType.COMMIT_CACHE))
                    return fullPath.getMappedPaths();
            }
        }

        //match by url only if path doesn't have revision
        if (!fullPath.containsRevision() && urlsMatch(root, fullPath))
            return fullPath.getMappedPaths();

        return Collections.emptySet();
    }

    private boolean repositoryContainsRevision(@NotNull OperationContext context, @NotNull VcsRootEntry rootEntry,
            @NotNull String revision, @NotNull RevisionCacheType type) throws VcsException, IOException {
        GitVcsRoot root = context.getGitRoot();
        RepositoryRevisionCache repositoryCache = myCache.getRepositoryCache(root.getRepositoryDir(), type);
        long resetCounter = repositoryCache.getResetCounter();
        Boolean hasRevision = repositoryCache.hasRevision(revision);
        if (hasRevision != null) {
            if (LOG.isDebugEnabled())
                LOG.debug("RevisionCache hit: root " + LogUtil.describe(rootEntry.getVcsRoot())
                        + (hasRevision ? "contains " : "doesn't contain ") + "revision " + revision);
            return hasRevision;
        } else {
            if (LOG.isDebugEnabled())
                LOG.debug("RevisionCache miss: root " + LogUtil.describe(rootEntry.getVcsRoot()) + ", revision "
                        + revision + ", lookup commit in repository");
            hasRevision = myCommitLoader.findCommit(context.getRepository(), revision) != null;
            if (LOG.isDebugEnabled())
                LOG.debug("Root " + LogUtil.describe(rootEntry.getVcsRoot()) + ", revision " + revision
                        + (hasRevision ? " was found" : " wasn't found") + ", cache the result");
            repositoryCache.saveRevision(revision, hasRevision, resetCounter);
            return hasRevision;
        }
    }

    private boolean urlsMatch(@NotNull GitVcsRoot root, @NotNull FullPath fullPath) {
        String url = removeBranch(fullPath.getRepositoryUrl());

        final URIish uri;
        try {
            uri = new URIish(url);
        } catch (final URISyntaxException e) {
            if (ReferencesResolverUtil.containsReference(url)) {
                LOG.warn("Unresolved parameter in url " + url + ", root " + LogUtil.describe(root));
            } else {
                LOG.warnAndDebugDetails(
                        "Error while parsing VCS root url " + url + ", root " + LogUtil.describe(root), e);
            }
            return false;
        }

        final URIish settingsUrl = root.getRepositoryFetchURL();
        if (settingsUrl == null) {
            return false;
        }
        if (uri.getHost() == null && settingsUrl.getHost() != null
                || uri.getHost() != null && !uri.getHost().equals(settingsUrl.getHost())) {
            return false;
        }
        if (uri.getPort() != settingsUrl.getPort()) {
            return false;
        }
        if (uri.getPath() == null && settingsUrl.getPath() != null
                || uri.getPath() != null && !uri.getPath().equals(settingsUrl.getPath())) {
            return false;
        }

        return true;
    }

    @NotNull
    private String removeBranch(@NotNull final String url) {
        int branchSeparatorIndex = url.indexOf("#");
        return (branchSeparatorIndex > 0) ? url.substring(0, branchSeparatorIndex) : url;
    }

    public void invalidateRevisionsCache(@NotNull Repository db, @NotNull Map<String, Ref> oldRefs,
            @NotNull Map<String, Ref> newRefs) throws IOException {
        try {
            if (myConfig.ignoreFetchedCommits()) {
                myCache.resetNegativeEntries(db.getDirectory());
            } else {
                Set<String> newCommits = getNewCommits(db, oldRefs, newRefs);
                myCache.resetNegativeEntries(db.getDirectory(), newCommits);
            }
        } catch (IOException e) {
            LOG.warn("Error while resetting commits cache for repository " + db.getDirectory(), e);
        }
    }

    private Set<String> getNewCommits(@NotNull Repository db, @NotNull Map<String, Ref> oldRefs,
            @NotNull Map<String, Ref> newRefs) throws IOException {
        Set<ObjectId> updatedHeads = new HashSet<ObjectId>();
        Set<ObjectId> uninteresting = new HashSet<ObjectId>();
        for (Map.Entry<String, Ref> e : newRefs.entrySet()) {
            String refName = e.getKey();
            if (!refName.startsWith("refs/"))
                continue;
            Ref newRef = e.getValue();
            Ref oldRef = oldRefs.get(refName);
            if (oldRef == null || !oldRef.getObjectId().equals(newRef.getObjectId()))
                updatedHeads.add(newRef.getObjectId());
            if (oldRef != null)
                uninteresting.add(oldRef.getObjectId());
        }

        RevWalk revWalk = new RevWalk(db);
        try {
            revWalk.sort(RevSort.TOPO);
            for (ObjectId id : updatedHeads) {
                RevObject obj = revWalk.parseAny(id);
                if (obj.getType() == Constants.OBJ_COMMIT)
                    revWalk.markStart((RevCommit) obj);
            }
            for (ObjectId id : uninteresting) {
                RevObject obj = revWalk.parseAny(id);
                if (obj.getType() == Constants.OBJ_COMMIT)
                    revWalk.markUninteresting((RevCommit) obj);
            }
            Set<String> newCommits = new HashSet<String>();
            RevCommit newCommit = null;
            while ((newCommit = revWalk.next()) != null) {
                newCommits.add(newCommit.name());
            }
            return newCommits;
        } finally {
            revWalk.dispose();
        }
    }

    //Format: <hint revision>-<git revision hash>|<repository url>|<file relative path>
    private static class FullPath {
        private final String myPath;
        private final int myFirstSeparatorIdx;
        private final int myLastSeparatorIdx;
        private final boolean myValid;
        private final String myRevision;
        private final String myHintRevision;

        private FullPath(@NotNull String path) {
            myPath = path;
            myFirstSeparatorIdx = path.indexOf("|");
            myLastSeparatorIdx = path.lastIndexOf("|");
            myValid = myFirstSeparatorIdx >= 0 && myLastSeparatorIdx > myFirstSeparatorIdx;
            Pair<String, String> revisions = parseRevisions();
            myHintRevision = revisions.first;
            myRevision = revisions.second;
        }

        private Pair<String, String> parseRevisions() {
            if (!myValid)
                return Pair.create(null, null);
            String revisions = myPath.substring(0, myFirstSeparatorIdx).trim();
            int idx = revisions.indexOf("-");
            if (idx <= 0)
                return Pair.create(null, revisions);
            return Pair.create(revisions.substring(0, idx), revisions.substring(idx + 1, revisions.length()));
        }

        boolean isValid() {
            return myValid;
        }

        @NotNull
        String getRevision() {
            if (!myValid)
                throw new IllegalStateException("Invalid path " + myPath);
            return myRevision;
        }

        boolean containsRevision() {
            if (!myValid)
                throw new IllegalStateException("Invalid path " + myPath);
            return !isEmpty(myRevision);
        }

        @Nullable
        String getHintRevision() {
            return myHintRevision;
        }

        boolean containsHintRevision() {
            if (!myValid)
                throw new IllegalStateException("Invalid path " + myPath);
            return myHintRevision != null;
        }

        @NotNull
        String getRepositoryUrl() {
            return myPath.substring(myFirstSeparatorIdx + 1, myLastSeparatorIdx).trim();
        }

        @NotNull
        Collection<String> getMappedPaths() {
            return Collections.singleton(myPath.substring(myLastSeparatorIdx + 1).trim());
        }
    }
}