-/*
- * Copyright 2000-2011 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.ExtensionHolder;
-import jetbrains.buildServer.buildTriggers.vcs.git.submodules.SubmoduleAwareTreeIterator;
-import jetbrains.buildServer.serverSide.PropertiesProcessor;
-import jetbrains.buildServer.serverSide.impl.LogUtil;
-import jetbrains.buildServer.util.FileUtil;
-import jetbrains.buildServer.util.RecentEntriesCache;
-import jetbrains.buildServer.vcs.*;
-import jetbrains.buildServer.vcs.RepositoryState;
-import jetbrains.buildServer.vcs.impl.VcsRootImpl;
-import jetbrains.buildServer.vcs.patches.PatchBuilder;
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.errors.AmbiguousObjectException;
-import org.eclipse.jgit.errors.NotSupportedException;
-import org.eclipse.jgit.errors.TransportException;
-import org.eclipse.jgit.lib.*;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevSort;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.revwalk.filter.RevFilter;
-import org.eclipse.jgit.storage.file.WindowCache;
-import org.eclipse.jgit.storage.file.WindowCacheConfig;
-import org.eclipse.jgit.transport.*;
-import org.eclipse.jgit.treewalk.AbstractTreeIterator;
-import org.eclipse.jgit.treewalk.EmptyTreeIterator;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
-import org.eclipse.jgit.treewalk.filter.TreeFilter;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.*;
-import java.util.*;
-import java.util.concurrent.Callable;
-import java.util.concurrent.locks.Lock;
-import java.util.regex.Pattern;
-
-import static jetbrains.buildServer.buildTriggers.vcs.git.GitServerUtil.friendlyNotSupportedException;
-import static jetbrains.buildServer.buildTriggers.vcs.git.GitServerUtil.friendlyTransportException;
-
-
-/**
- * Git VCS support
- */
-public class GitVcsSupport extends ServerVcsSupport
- implements VcsPersonalSupport, LabelingSupport, VcsFileContentProvider, CollectChangesBetweenRoots, BuildPatchByCheckoutRules,
- TestConnectionSupport, BranchSupport, IncludeRuleBasedMappingProvider {
-
- private static Logger LOG = Logger.getInstance(GitVcsSupport.class.getName());
- private static Logger PERFORMANCE_LOG = Logger.getInstance(GitVcsSupport.class.getName() + ".Performance");
- /**
- * Current version cache (Pair<bare repository dir, branch name> -> current version).
- */
- private final RecentEntriesCache<Pair<File, String>, String> myCurrentVersionCache;
-
- private final ExtensionHolder myExtensionHolder;
- private volatile String myDisplayName = null;
- private final ServerPluginConfig myConfig;
- private final TransportFactory myTransportFactory;
- private final FetchCommand myFetchCommand;
- private final RepositoryManager myRepositoryManager;
-
-
- public GitVcsSupport(@NotNull final ServerPluginConfig config,
- @NotNull final TransportFactory transportFactory,
- @NotNull final FetchCommand fetchCommand,
- @NotNull final RepositoryManager repositoryManager,
- @Nullable final ExtensionHolder extensionHolder) {
- myConfig = config;
- myExtensionHolder = extensionHolder;
- myTransportFactory = transportFactory;
- myFetchCommand = fetchCommand;
- myRepositoryManager = repositoryManager;
- myCurrentVersionCache = new RecentEntriesCache<Pair<File, String>, String>(myConfig.getCurrentVersionCacheSize());
- setStreamFileThreshold();
- }
-
-
- private void setStreamFileThreshold() {
- WindowCacheConfig cfg = new WindowCacheConfig();
- cfg.setStreamFileThreshold(myConfig.getStreamFileThreshold() * WindowCacheConfig.MB);
- WindowCache.reconfigure(cfg);
- }
-
-
- @NotNull
- public List<ModificationData> collectChanges(@NotNull VcsRoot originalRoot,
- @NotNull String originalRootVersion,
- @NotNull VcsRoot branchRoot,
- @Nullable String branchRootVersion,
- @NotNull CheckoutRules checkoutRules) throws VcsException {
- LOG.debug("Collecting changes [" +LogUtil.describe(originalRoot) + "-" + originalRootVersion + "].." +
- "[" + LogUtil.describe(branchRoot) + "-" + branchRootVersion + "]");
- if (branchRootVersion == null) {
- LOG.warn("Branch root version is null for " + LogUtil.describe(branchRoot) + ", return empty list of changes");
- return Collections.emptyList();
- }
- String forkPoint = getLastCommonVersion(originalRoot, originalRootVersion, branchRoot, branchRootVersion);
- return collectChanges(branchRoot, forkPoint, branchRootVersion, checkoutRules);
- }
-
- @NotNull
- public List<ModificationData> collectChanges(@NotNull VcsRoot root,
- @NotNull String fromVersion,
- @Nullable String currentVersion,
- @NotNull CheckoutRules checkoutRules) throws VcsException {
- List<ModificationData> result = new ArrayList<ModificationData>();
- OperationContext context = createContext(root, "collecting changes");
- try {
- LOG.debug("Collecting changes " + fromVersion + ".." + currentVersion + " for " + context.getSettings().debugInfo());
- if (currentVersion == null) {
- LOG.warn("Current version is null for " + context.getSettings().debugInfo() + ", return empty list of changes");
- return result;
- }
- String upperBoundSHA = GitUtils.versionRevision(currentVersion);
- ensureRevCommitLoaded(context, context.getSettings(), upperBoundSHA);
- String lowerBoundSHA = GitUtils.versionRevision(fromVersion);
- Repository r = context.getRepository();
- result.addAll(getModifications(context, r, upperBoundSHA, lowerBoundSHA, GitUtils.versionTime(fromVersion)));
- } catch (Exception e) {
- throw context.wrapException(e);
- } finally {
- context.close();
- }
- return result;
- }
-
-
- private List<ModificationData> getModifications(@NotNull final OperationContext context, @NotNull final Repository r, @NotNull final String upperBoundSHA, @NotNull final String lowerBoundSHA, final long timeLowerBound) throws VcsException, IOException {
- List<ModificationData> modifications = new ArrayList<ModificationData>();
- ModificationDataRevWalk revWalk = new ModificationDataRevWalk(context, myConfig.getFixedSubmoduleCommitSearchDepth());
- revWalk.sort(RevSort.TOPO);
- try {
- revWalk.markStart(revWalk.parseCommit(ObjectId.fromString(upperBoundSHA)));
- ObjectId lowerBoundId = ObjectId.fromString(lowerBoundSHA);
- if (r.hasObject(lowerBoundId)) {
- revWalk.markUninteresting(revWalk.parseCommit(lowerBoundId));
- } else {
- LOG.warn("From version " + lowerBoundSHA + " is not found, collecting changes based on commit time " + context.getSettings().debugInfo());
- revWalk.limitByCommitTime(timeLowerBound);
- }
- while (revWalk.next() != null) {
- modifications.add(revWalk.createModificationData());
- }
- return modifications;
- } finally {
- revWalk.release();
- }
- }
-
-
- @NotNull
- public String getRemoteRunOnBranchPattern() {
- return "refs/heads/remote-run/*";
- }
-
- @NotNull
- public RepositoryState getCurrentState(@NotNull VcsRoot root) throws VcsException {
- RepositoryState state = new RepositoryState();
- for (Ref ref : getRemoteRefs(root).values()) {
- state.addBranch(ref.getName(), ref.getObjectId().name());
- }
- return state;
- }
-
- @NotNull
- public Map<String, String> getBranchRootOptions(@NotNull VcsRoot root, @NotNull String branchName) {
- final Map<String, String> result = new HashMap<String, String>(root.getProperties());
- result.put(Constants.BRANCH_NAME, branchName);
- return result;
- }
-
-
- @Nullable
- public PersonalBranchDescription getPersonalBranchDescription(@NotNull VcsRoot original, @NotNull String branchName) throws VcsException {
- VcsRoot branchRoot = createBranchRoot(original, branchName);
- OperationContext context = createContext(branchRoot, "find fork version");
- PersonalBranchDescription result = null;
- RevWalk walk = null;
- try {
- String originalCommit = GitUtils.versionRevision(getCurrentVersion(original));
- String branchCommit = GitUtils.versionRevision(getCurrentVersion(branchRoot));
- Repository db = context.getRepository();
- walk = new RevWalk(db);
- walk.markStart(walk.parseCommit(ObjectId.fromString(branchCommit)));
- walk.markUninteresting(walk.parseCommit(ObjectId.fromString(originalCommit)));
- walk.sort(RevSort.TOPO);
- boolean lastCommit = true;
- String firstCommitInBranch = null;
- String lastCommitUser = null;
- RevCommit c;
- while ((c = walk.next()) != null) {
- if (lastCommit) {
- lastCommitUser = GitServerUtil.getUser(context.getSettings(), c);
- lastCommit = false;
- }
- firstCommitInBranch = c.name();
- }
- if (firstCommitInBranch != null && lastCommitUser != null)
- result = new PersonalBranchDescription(firstCommitInBranch, lastCommitUser);
- } catch (Exception e) {
- throw context.wrapException(e);
- } finally {
- try {
- if (walk != null)
- walk.release();
- } finally {
- context.close();
- }
- }
- return result;
- }
-
- private VcsRoot createBranchRoot(VcsRoot original, String branchName) {
- VcsRootImpl result = new VcsRootImpl(original.getId(), original.getVcsName());
- result.addAllProperties(original.getProperties());
- result.addProperty(Constants.BRANCH_NAME, branchName);
- return result;
- }
-
- private String getLastCommonVersion(VcsRoot baseRoot, String baseVersion, VcsRoot tipRoot, String tipVersion) throws VcsException {
- OperationContext context = createContext(tipRoot, "find fork version");
- Settings baseSettings = context.getSettings(baseRoot);
- Settings tipSettings = context.getSettings();
- LOG.debug("Find last common version between [" + baseSettings.debugInfo() + "-" + baseVersion + "].." +
- "[" + tipSettings.debugInfo() + "-" + tipVersion + "]");
- RevWalk walk = null;
- try {
- RevCommit baseCommit = ensureCommitLoaded(context, baseSettings, baseVersion);
- RevCommit tipCommit = ensureCommitLoaded(context, tipSettings, tipVersion);
- Repository tipRepository = context.getRepository(tipSettings);
- walk = new RevWalk(tipRepository);
- walk.setRevFilter(RevFilter.MERGE_BASE);
- walk.markStart(walk.parseCommit(baseCommit.getId()));
- walk.markStart(walk.parseCommit(tipCommit.getId()));
- final RevCommit base = walk.next();
- String result = GitServerUtil.makeVersion(base);
- LOG.debug("Last common revision between " + baseSettings.debugInfo() + " and " + tipSettings.debugInfo() + " is " + result);
- return result;
- } catch (Exception e) {
- throw context.wrapException(e);
- } finally {
- try {
- if (walk != null)
- walk.release();
- } finally {
- context.close();
- }
- }
- }
-
-
- public void buildPatch(@NotNull VcsRoot root,
- @Nullable final String fromVersion,
- @NotNull String toVersion,
- @NotNull final PatchBuilder builder,
- @NotNull CheckoutRules checkoutRules) throws IOException, VcsException {
- final OperationContext context = createContext(root, "patch building");
- final boolean debugFlag = LOG.isDebugEnabled();
- final boolean debugInfoOnEachCommit = myConfig.isPrintDebugInfoOnEachCommit();
- try {
- final Repository r = context.getRepository();
- VcsChangeTreeWalk tw = null;
- try {
- RevCommit toCommit = ensureRevCommitLoaded(context, context.getSettings(), GitUtils.versionRevision(toVersion));
- if (toCommit == null) {
- throw new VcsException("Missing commit for version: " + toVersion);
- }
- tw = new VcsChangeTreeWalk(r, context.getSettings().debugInfo());
- tw.setFilter(TreeFilter.ANY_DIFF);
- tw.setRecursive(true);
- context.addTree(tw, r, toCommit, false);
- if (fromVersion != null) {
- if (debugFlag) {
- LOG.debug("Creating patch " + fromVersion + ".." + toVersion + " for " + context.getSettings().debugInfo());
- }
- RevCommit fromCommit = getCommit(r, GitUtils.versionRevision(fromVersion));
- if (fromCommit == null) {
- throw new IncrementalPatchImpossibleException("The form commit " + fromVersion + " is not available in the repository");
- }
- context.addTree(tw, r, fromCommit, true);
- } else {
- if (debugFlag) {
- LOG.debug("Creating clean patch " + toVersion + " for " + context.getSettings().debugInfo());
- }
- tw.addTree(new EmptyTreeIterator());
- }
- final List<Callable<Void>> actions = new LinkedList<Callable<Void>>();
- while (tw.next()) {
- final String path = tw.getPathString();
- final String mapped = checkoutRules.map(path);
- if (mapped == null) {
- continue;
- }
- if (debugFlag && debugInfoOnEachCommit) {
- LOG.debug("File found " + tw.treeWalkInfo(path) + " for " + context.getSettings().debugInfo());
- }
- switch (tw.classifyChange()) {
- case UNCHANGED:
- // change is ignored
- continue;
- case MODIFIED:
- case ADDED:
- case FILE_MODE_CHANGED:
- if (!FileMode.GITLINK.equals(tw.getFileMode(0))) {
- final String mode = tw.getModeDiff();
- if (mode != null && LOG.isDebugEnabled())
- LOG.debug("The mode change " + mode + " is detected for " + tw.treeWalkInfo(path));
- final ObjectId id = tw.getObjectId(0);
- final Repository objRep = getRepository(r, tw, 0);
- final Callable<Void> action = new Callable<Void>() {
- public Void call() throws Exception {
- InputStream objectStream = null;
- try {
- final ObjectLoader loader = objRep.open(id);
- if (loader == null) {
- throw new IOException("Unable to find blob " + id + (path == null ? "" : "(" + path + ")") + " in repository " + r);
- }
- objectStream = loader.isLarge() ? loader.openStream() : new ByteArrayInputStream(loader.getCachedBytes());
- builder.changeOrCreateBinaryFile(GitUtils.toFile(mapped), mode, objectStream, loader.getSize());
- } catch (Error e) {
- LOG.error("Unable to load file: " + path + "(" + id.name() + ") from: " + context.getSettings().debugInfo());
- throw e;
- } catch (Exception e) {
- LOG.error("Unable to load file: " + path + "(" + id.name() + ") from: " + context.getSettings().debugInfo());
- throw e;
- } finally {
- if (objectStream != null) objectStream.close();
- }
- return null;
- }
- };
- if (fromVersion == null) {
- // clean patch, we aren't going to see any deletes
- action.call();
- } else {
- actions.add(action);
- }
- }
- break;
- case DELETED:
- if (!FileMode.GITLINK.equals(tw.getFileMode(0))) {
- builder.deleteFile(GitUtils.toFile(mapped), true);
- }
- break;
- default:
- throw new IllegalStateException("Unknown change type");
- }
- }
- for (Callable<Void> a : actions) {
- a.call();
- }
- } finally {
- if (tw != null) tw.release();
- }
- } catch (Exception e) {
- throw context.wrapException(e);
- } finally {
- context.close();
- }
- }
-
-
- @NotNull
- public byte[] getContent(@NotNull VcsModification vcsModification,
- @NotNull VcsChangeInfo change,
- @NotNull VcsChangeInfo.ContentType contentType,
- @NotNull VcsRoot vcsRoot)
- throws VcsException {
- String version = contentType == VcsChangeInfo.ContentType.BEFORE_CHANGE
- ? change.getBeforeChangeRevisionNumber()
- : change.getAfterChangeRevisionNumber();
- String file = change.getRelativeFileName();
- return getContent(file, vcsRoot, version);
- }
-
- @NotNull
- public byte[] getContent(@NotNull String filePath, @NotNull VcsRoot root, @NotNull String version) throws VcsException {
- OperationContext context = createContext(root, "retrieving content");
- try {
- final long start = System.currentTimeMillis();
- Repository r = context.getRepository();
- final TreeWalk tw = new TreeWalk(r);
- try {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Getting data from " + version + ":" + filePath + " for " + context.getSettings().debugInfo());
- }
- final String rev = GitUtils.versionRevision(version);
- RevCommit c = ensureRevCommitLoaded(context, context.getSettings(), rev);
- tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(filePath)));
- tw.setRecursive(tw.getFilter().shouldBeRecursive());
- context.addTree(tw, r, c, true);
- if (!tw.next()) {
- throw new VcsFileNotFoundException("The file " + filePath + " could not be found in " + rev + context.getSettings().debugInfo());
- }
- final byte[] data = loadObject(r, tw, 0);
- if (LOG.isDebugEnabled()) {
- LOG.debug(
- "File retrieved " + version + ":" + filePath + " (hash = " + tw.getObjectId(0) + ", length = " + data.length + ") for " +
- context.getSettings().debugInfo());
- }
- return data;
- } finally {
- final long finish = System.currentTimeMillis();
- if (PERFORMANCE_LOG.isDebugEnabled()) {
- PERFORMANCE_LOG.debug("[getContent] root=" + context.getSettings().debugInfo() + ", file=" + filePath + ", get object content: " + (finish - start) + "ms");
- }
- tw.release();
- }
- } catch (Exception e) {
- throw context.wrapException(e);
- } finally {
- context.close();
- }
- }
-
- /**
- * Load bytes that correspond to the position in the tree walker
- *
- * @param r the initial repository
- * @param tw the tree walker
- * @param nth the tree in the tree wailer
- * @return loaded bytes
- * @throws IOException if there is an IO error
- */
- private byte[] loadObject(Repository r, TreeWalk tw, final int nth) throws IOException {
- ObjectId id = tw.getObjectId(nth);
- Repository objRep = getRepository(r, tw, nth);
- final String path = tw.getPathString();
- return loadObject(objRep, path, id);
- }
-
- /**
- * Load object by blob ID
- *
- * @param r the repository
- * @param path the path (might be null)
- * @param id the object id
- * @return the object's bytes
- * @throws IOException in case of IO problem
- */
- private byte[] loadObject(Repository r, String path, ObjectId id) throws IOException {
- final ObjectLoader loader = r.open(id);
- if (loader == null) {
- throw new IOException("Unable to find blob " + id + (path == null ? "" : "(" + path + ")") + " in repository " + r);
- }
- if (loader.isLarge()) {
- assert loader.getSize() < Integer.MAX_VALUE;
- ByteArrayOutputStream output = new ByteArrayOutputStream((int) loader.getSize());
- loader.copyTo(output);
- return output.toByteArray();
- } else {
- return loader.getCachedBytes();
- }
- }
-
- private RevCommit ensureCommitLoaded(OperationContext context, Settings rootSettings, String commitWithDate) throws Exception {
- final String commit = GitUtils.versionRevision(commitWithDate);
- return ensureRevCommitLoaded(context, rootSettings, commit);
- }
-
- private RevCommit ensureRevCommitLoaded(OperationContext context, Settings settings, String commitSHA) throws Exception {
- Repository db = context.getRepository(settings);
- RevCommit result = null;
- try {
- final long start = System.currentTimeMillis();
- result = getCommit(db, commitSHA);
- final long finish = System.currentTimeMillis();
- if (PERFORMANCE_LOG.isDebugEnabled()) {
- PERFORMANCE_LOG.debug("[ensureCommitLoaded] root=" + settings.debugInfo() + ", commit=" + commitSHA + ", local commit lookup: " + (finish - start) + "ms");
- }
- } catch (IOException ex) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("IO problem for commit " + commitSHA + " in " + settings.debugInfo(), ex);
- }
- }
- if (result == null) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Commit " + commitSHA + " is not in the repository for " + settings.debugInfo() + ", fetching data... ");
- }
- fetchBranchData(settings, db);
- result = getCommit(db, commitSHA);
- if (result == null) {
- throw new VcsException("The version name could not be resolved " + commitSHA + "(" + settings.getRepositoryFetchURL().toString() + "#" + settings.getRef() + ")");
- }
- }
- return result;
- }
-
- RevCommit getCommit(Repository repository, String commitSHA) throws IOException {
- return getCommit(repository, ObjectId.fromString(commitSHA));
- }
-
- public RevCommit getCommit(Repository repository, ObjectId commitId) throws IOException {
- final long start = System.currentTimeMillis();
- RevWalk walk = new RevWalk(repository);
- try {
- return walk.parseCommit(commitId);
- } finally {
- walk.release();
- final long finish = System.currentTimeMillis();
- if (PERFORMANCE_LOG.isDebugEnabled()) {
- PERFORMANCE_LOG.debug("[RevWalk.parseCommit] repository=" + repository.getDirectory().getAbsolutePath() + ", commit=" + commitId.name() + ", took: " + (finish - start) + "ms");
- }
- }
- }
-
- @NotNull
- public String getName() {
- return Constants.VCS_NAME;
- }
-
- @NotNull
- public String getDisplayName() {
- initDisplayNameIfRequired();
- return myDisplayName;
- }
-
- private void initDisplayNameIfRequired() {
- if (myDisplayName == null) {
- if (myExtensionHolder != null) {
- boolean communityPluginFound = false;
- final Collection<VcsSupportContext> vcsPlugins = myExtensionHolder.getServices(VcsSupportContext.class);
- for (VcsSupportContext plugin : vcsPlugins) {
- if (plugin.getCore().getName().equals("git")) {
- communityPluginFound = true;
- }
- }
- if (communityPluginFound) {
- myDisplayName = "Git (Jetbrains plugin)";
- } else {
- myDisplayName = "Git";
- }
- } else {
- myDisplayName = "Git (Jetbrains plugin)";
- }
- }
- }
-
- public PropertiesProcessor getVcsPropertiesProcessor() {
- return new VcsPropertiesProcessor();
- }
-
- @NotNull
- public String getVcsSettingsJspFilePath() {
- return "gitSettings.jsp";
- }
-
- @NotNull
- public String describeVcsRoot(VcsRoot root) {
- final String branch = root.getProperty(Constants.BRANCH_NAME);
- return root.getProperty(Constants.FETCH_URL) + "#" + (branch == null ? "master" : branch);
- }
-
- public Map<String, String> getDefaultVcsProperties() {
- final HashMap<String, String> map = new HashMap<String, String>();
- map.put(Constants.BRANCH_NAME, "master");
- map.put(Constants.IGNORE_KNOWN_HOSTS, "true");
- return map;
- }
-
- public String getVersionDisplayName(@NotNull String version, @NotNull VcsRoot root) throws VcsException {
- return GitServerUtil.displayVersion(version);
- }
-
- @NotNull
- public Comparator<String> getVersionComparator() {
- return GitUtils.VERSION_COMPARATOR;
- }
-
- @NotNull
- public String getCurrentVersion(@NotNull VcsRoot root) throws VcsException {
- OperationContext context = createContext(root, "retrieving current version");
- Settings s = context.getSettings();
- try {
- Repository r = context.getRepository();
- String refName = GitUtils.expandRef(s.getRef());
-
- if (!myConfig.isSeparateProcessForFetch() || isRemoteRefUpdated(root, r, s, refName))
- fetchBranchData(s, r);
-
- Ref branchRef = r.getRef(refName);
- if (branchRef == null) {
- throw new VcsException("The ref '" + refName + "' could not be resolved");
- }
- String cachedCurrentVersion = getCachedCurrentVersion(s.getRepositoryDir(), s.getRef());
- if (cachedCurrentVersion != null && GitUtils.versionRevision(cachedCurrentVersion).equals(branchRef.getObjectId().name())) {
- return cachedCurrentVersion;
- } else {
- RevCommit c = getCommit(r, branchRef.getObjectId());
- if (c == null) {
- throw new VcsException("The ref '" + refName + "' could not be resolved");
- }
- if (LOG.isDebugEnabled()) {
- LOG.debug("Current version: " + c.getId().name() + " " + s.debugInfo());
- }
- final String currentVersion = GitServerUtil.makeVersion(c);
- setCachedCurrentVersion(s.getRepositoryDir(), s.getRef(), currentVersion);
- GitMapFullPath.invalidateRevisionsCache(root);
- return currentVersion;
- }
- } catch (Exception e) {
- throw context.wrapException(e);
- } finally {
- context.close();
- }
- }
-
-
- private boolean isRemoteRefUpdated(@NotNull VcsRoot root, @NotNull Repository db, @NotNull Settings s, @NotNull String refName) throws Exception {
- Map<String, Ref> remoteRefs = getRemoteRefs(root, db, s);
- Ref remoteRef = remoteRefs.get(refName);
- if (remoteRef == null)
- return true;
-
- String cachedCurrentVersion = getCachedCurrentVersion(s.getRepositoryDir(), s.getRef());
- return cachedCurrentVersion == null || !remoteRef.getObjectId().name().equals(GitUtils.versionRevision(cachedCurrentVersion));
- }
-
-
- /**
- * Return cached current version for branch in repository in specified dir, or null if no cache version found.
- */
- private String getCachedCurrentVersion(File repositoryDir, String branchName) {
- return myCurrentVersionCache.get(Pair.create(repositoryDir, branchName));
- }
-
- /**
- * Save current version for branch of repository in cache.
- */
- private void setCachedCurrentVersion(File repositoryDir, String branchName, String currentVersion) {
- myCurrentVersionCache.put(Pair.create(repositoryDir, branchName), currentVersion);
- }
-
- public boolean sourcesUpdatePossibleIfChangesNotFound(@NotNull VcsRoot root) {
- return true;
- }
-
- /**
- * Fetch data for the branch
- *
- * @param settings settings for the root
- * @param repository the repository
- * @throws Exception if there is a problem with fetching data
- */
- private void fetchBranchData(Settings settings, Repository repository) throws Exception {
- final String refName = GitUtils.expandRef(settings.getRef());
- RefSpec spec = new RefSpec().setSource(refName).setDestination(refName).setForceUpdate(true);
- fetch(repository, settings.getRepositoryFetchURL(), spec, settings.getAuthSettings());
- }
-
-
- public void fetch(Repository db, URIish fetchURI, Collection<RefSpec> refspecs, Settings.AuthSettings auth) throws NotSupportedException, VcsException, TransportException {
- File repositoryDir = db.getDirectory();
- assert repositoryDir != null : "Non-local repository";
- Lock rmLock = myRepositoryManager.getRmLock(repositoryDir).readLock();
- rmLock.lock();
- try {
- final long start = System.currentTimeMillis();
- synchronized (myRepositoryManager.getWriteLock(repositoryDir)) {
- final long finish = System.currentTimeMillis();
- PERFORMANCE_LOG.debug("[waitForWriteLock] repository: " + repositoryDir.getAbsolutePath() + ", took " + (finish - start) + "ms");
- myFetchCommand.fetch(db, fetchURI, refspecs, auth);
- }
- } finally {
- rmLock.unlock();
- }
- }
- /**
- * Make fetch into local repository (it.s getDirectory() should be != null)
- *
- * @param db repository
- * @param fetchURI uri to fetch
- * @param refspec refspec
- * @param auth auth settings
- */
- public void fetch(Repository db, URIish fetchURI, RefSpec refspec, Settings.AuthSettings auth) throws NotSupportedException, VcsException, TransportException {
- fetch(db, fetchURI, Collections.singletonList(refspec), auth);
- }
-
-
- public String testConnection(@NotNull VcsRoot vcsRoot) throws VcsException {
- OperationContext context = createContext(vcsRoot, "connection test");
- TestConnectionCommand command = new TestConnectionCommand(myTransportFactory, myRepositoryManager);
- try {
- return command.testConnection(context);
- } catch (Exception e) {
- throw context.wrapException(e);
- } finally {
- context.close();
- }
- }
-
-
- @Override
- public TestConnectionSupport getTestConnectionSupport() {
- return this;
- }
-
- public OperationContext createContext(VcsRoot root, String operation) {
- return new OperationContext(this, myRepositoryManager, root, operation);
- }
-
- public LabelingSupport getLabelingSupport() {
- return this;
- }
-
- @NotNull
- public VcsFileContentProvider getContentProvider() {
- return this;
- }
-
- @NotNull
- public CollectChangesPolicy getCollectChangesPolicy() {
- return this;
- }
-
- @NotNull
- public BuildPatchPolicy getBuildPatchPolicy() {
- return this;
- }
-
- public String label(@NotNull String label, @NotNull String version, @NotNull VcsRoot root, @NotNull CheckoutRules checkoutRules)
- throws VcsException {
- OperationContext context = createContext(root, "labelling");
- Settings s = context.getSettings();
- try {
- Repository r = context.getRepository();
- String commitSHA = GitUtils.versionRevision(version);
- RevCommit commit = ensureRevCommitLoaded(context, s, commitSHA);
- Git git = new Git(r);
- git.tag().setName(label).setObjectId(commit).call();
- String tagRef = GitUtils.tagName(label);
- if (LOG.isDebugEnabled()) {
- LOG.debug("Tag created " + label + "=" + version + " for " + s.debugInfo());
- }
- synchronized (myRepositoryManager.getWriteLock(s.getRepositoryDir())) {
- final Transport tn = myTransportFactory.createTransport(r, s.getRepositoryPushURL(), s.getAuthSettings());
- try {
- final PushConnection c = tn.openPush();
- try {
- RemoteRefUpdate ru = new RemoteRefUpdate(r, tagRef, tagRef, false, null, null);
- c.push(NullProgressMonitor.INSTANCE, Collections.singletonMap(tagRef, ru));
- LOG.info("Tag " + label + "=" + version + " pushed with status " + ru.getStatus() + " for " + s.debugInfo());
- switch (ru.getStatus()) {
- case UP_TO_DATE:
- case OK:
- break;
- default:
- throw new VcsException("The remote tag was not created (" + ru.getStatus() + "): " + label);
- }
- } finally {
- c.close();
- }
- return label;
- } finally {
- tn.close();
- }
- }
- } catch (Exception e) {
- throw context.wrapException(e);
- } finally {
- context.close();
- }
- }
-
- /**
- * Get repository from tree walker
- *
- * @param r the initial repository
- * @param tw the tree walker
- * @param nth the position
- * @return the actual repository
- */
- private Repository getRepository(Repository r, TreeWalk tw, int nth) {
- Repository objRep;
- AbstractTreeIterator ti = tw.getTree(nth, AbstractTreeIterator.class);
- if (ti instanceof SubmoduleAwareTreeIterator) {
- objRep = ((SubmoduleAwareTreeIterator)ti).getRepository();
- } else {
- objRep = r;
- }
- return objRep;
- }
-
- @Override
- public VcsPersonalSupport getPersonalSupport() {
- return this;
- }
-
- /**
- * Expected fullPath format:
- * <p/>
- * "<git revision hash>|<repository url>|<file relative path>"
- *
- * @param rootEntry indicates the association between VCS root and build configuration
- * @param fullPath change path from IDE patch
- * @return the mapped path
- */
- @NotNull
- public Collection<String> mapFullPath(@NotNull final VcsRootEntry rootEntry, @NotNull final String fullPath) {
- OperationContext context = createContext(rootEntry.getVcsRoot(), "map full path");
- try {
- return new GitMapFullPath(context, this, rootEntry, fullPath).mapFullPath();
- } catch (VcsException e) {
- LOG.error(e);
- return Collections.emptySet();
- } finally {
- context.close();
- }
- }
-
- @Override
- public boolean isAgentSideCheckoutAvailable() {
- return true;
- }
-
-
- @Override
- public UrlSupport getUrlSupport() {
- return new GitUrlSupport();
- }
-
-
- public List<String> getRemoteBranches(@NotNull final VcsRoot root, @NotNull final String pattern) throws VcsException {
- Collection<Ref> remotes = getRemoteRefs(root).values();
- Pattern p = Pattern.compile(pattern);
- List<String> result = new ArrayList<String>();
- for (Ref ref : remotes) {
- if (p.matcher(ref.getName()).matches()) {
- result.add(ref.getName());
- }
- }
- return result;
- }
-
- @NotNull
- private Map<String, Ref> getRemoteRefs(@NotNull final VcsRoot root) throws VcsException {
- OperationContext context = createContext(root, "list remote refs");
- Settings s = context.getSettings();
- File tmpDir = null;
- try {
- tmpDir = FileUtil.createTempDirectory("git-ls-remote", "");
- s.setUserDefinedRepositoryPath(tmpDir);
- Repository db = context.getRepository();
- return getRemoteRefs(root, db, s);
- } catch (Exception e) {
- throw context.wrapException(e);
- } finally {
- context.close();
- if (tmpDir != null) {
- myRepositoryManager.cleanLocksFor(tmpDir);
- FileUtil.delete(tmpDir);
- }
- }
- }
-
-
- @NotNull
- private Map<String, Ref> getRemoteRefs(@NotNull final VcsRoot root, @NotNull Repository db, @NotNull Settings s) throws Exception {
- Transport transport = null;
- FetchConnection connection = null;
- try {
- transport = myTransportFactory.createTransport(db, s.getRepositoryFetchURL(), s.getAuthSettings());
- connection = transport.openFetch();
- return connection.getRefsMap();
- } catch (NotSupportedException nse) {
- throw friendlyNotSupportedException(root, s, nse);
- } catch (TransportException te) {
- throw friendlyTransportException(te);
- } finally {
- if (connection != null) connection.close();
- if (transport != null) transport.close();
- }
- }
-
- @NotNull
- private ObjectId getVcsRootGitId(final @NotNull VcsRoot root) throws VcsException{
- final OperationContext context = createContext(root, "client-mapping");
- try {
- final Settings gitSettings = context.getSettings(root);
- final Repository gitRepo = context.getRepository(gitSettings);
- if(gitRepo == null){
- throw new VcsException(String.format("Could not find Git Repository for '%s'", root.getName()));
- }
- final ObjectId objectId = gitRepo.resolve(gitSettings.getRef());
- if(objectId == null){
- throw new VcsException(String.format("Could not resolve Git Reference '%s'", gitSettings.getRef()));
- }
- return objectId;
- } catch (AmbiguousObjectException e) {
- throw new VcsException(e);
- } catch (IOException e) {
- throw new VcsException(e);
- } finally {
- context.close();
- }
- }
-
- public Collection<VcsClientMapping> getClientMapping(final @NotNull VcsRoot root, final @NotNull IncludeRule rule) throws VcsException {
- final ObjectId gitObjId = getVcsRootGitId(root);
- return Collections.singletonList(new VcsClientMapping(String.format("%s||%s", gitObjId.name(), rule.getFrom()), rule.getTo()));
- }
-
- @Override
- public boolean isDAGBasedVcs() {
- return true;
- }
-}
+/*\r
+ * Copyright 2000-2011 JetBrains s.r.o.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package jetbrains.buildServer.buildTriggers.vcs.git;\r
+\r
+import com.intellij.openapi.diagnostic.Logger;\r
+import com.intellij.openapi.util.Pair;\r
+import jetbrains.buildServer.ExtensionHolder;\r
+import jetbrains.buildServer.buildTriggers.vcs.git.submodules.SubmoduleAwareTreeIterator;\r
+import jetbrains.buildServer.serverSide.PropertiesProcessor;\r
+import jetbrains.buildServer.serverSide.impl.LogUtil;\r
+import jetbrains.buildServer.util.FileUtil;\r
+import jetbrains.buildServer.util.RecentEntriesCache;\r
+import jetbrains.buildServer.vcs.*;\r
+import jetbrains.buildServer.vcs.RepositoryState;\r
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;\r
+import jetbrains.buildServer.vcs.patches.PatchBuilder;\r
+import org.eclipse.jgit.api.Git;\r
+import org.eclipse.jgit.errors.AmbiguousObjectException;\r
+import org.eclipse.jgit.errors.NotSupportedException;\r
+import org.eclipse.jgit.errors.TransportException;\r
+import org.eclipse.jgit.lib.*;\r
+import org.eclipse.jgit.revwalk.RevCommit;\r
+import org.eclipse.jgit.revwalk.RevSort;\r
+import org.eclipse.jgit.revwalk.RevWalk;\r
+import org.eclipse.jgit.revwalk.filter.RevFilter;\r
+import org.eclipse.jgit.storage.file.WindowCache;\r
+import org.eclipse.jgit.storage.file.WindowCacheConfig;\r
+import org.eclipse.jgit.transport.*;\r
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;\r
+import org.eclipse.jgit.treewalk.EmptyTreeIterator;\r
+import org.eclipse.jgit.treewalk.TreeWalk;\r
+import org.eclipse.jgit.treewalk.filter.PathFilterGroup;\r
+import org.eclipse.jgit.treewalk.filter.TreeFilter;\r
+import org.jetbrains.annotations.NotNull;\r
+import org.jetbrains.annotations.Nullable;\r
+\r
+import java.io.*;\r
+import java.util.*;\r
+import java.util.concurrent.Callable;\r
+import java.util.concurrent.locks.Lock;\r
+import java.util.regex.Pattern;\r
+\r
+import static jetbrains.buildServer.buildTriggers.vcs.git.GitServerUtil.friendlyNotSupportedException;\r
+import static jetbrains.buildServer.buildTriggers.vcs.git.GitServerUtil.friendlyTransportException;\r
+\r
+\r
+/**\r
+ * Git VCS support\r
+ */\r
+public class GitVcsSupport extends ServerVcsSupport\r
+ implements VcsPersonalSupport, LabelingSupport, VcsFileContentProvider, CollectChangesBetweenRoots, BuildPatchByCheckoutRules,\r
+ TestConnectionSupport, BranchSupport, IncludeRuleBasedMappingProvider {\r
+\r
+ private static Logger LOG = Logger.getInstance(GitVcsSupport.class.getName());\r
+ private static Logger PERFORMANCE_LOG = Logger.getInstance(GitVcsSupport.class.getName() + ".Performance");\r
+ /**\r
+ * Current version cache (Pair<bare repository dir, branch name> -> current version).\r
+ */\r
+ private final RecentEntriesCache<Pair<File, String>, String> myCurrentVersionCache;\r
+\r
+ private final ExtensionHolder myExtensionHolder;\r
+ private volatile String myDisplayName = null;\r
+ private final ServerPluginConfig myConfig;\r
+ private final TransportFactory myTransportFactory;\r
+ private final FetchCommand myFetchCommand;\r
+ private final RepositoryManager myRepositoryManager;\r
+\r
+\r
+ public GitVcsSupport(@NotNull final ServerPluginConfig config,\r
+ @NotNull final TransportFactory transportFactory,\r
+ @NotNull final FetchCommand fetchCommand,\r
+ @NotNull final RepositoryManager repositoryManager,\r
+ @Nullable final ExtensionHolder extensionHolder) {\r
+ myConfig = config;\r
+ myExtensionHolder = extensionHolder;\r
+ myTransportFactory = transportFactory;\r
+ myFetchCommand = fetchCommand;\r
+ myRepositoryManager = repositoryManager;\r
+ myCurrentVersionCache = new RecentEntriesCache<Pair<File, String>, String>(myConfig.getCurrentVersionCacheSize());\r
+ setStreamFileThreshold();\r
+ }\r
+\r
+\r
+ private void setStreamFileThreshold() {\r
+ WindowCacheConfig cfg = new WindowCacheConfig();\r
+ cfg.setStreamFileThreshold(myConfig.getStreamFileThreshold() * WindowCacheConfig.MB);\r
+ WindowCache.reconfigure(cfg);\r
+ }\r
+\r
+\r
+ @NotNull\r
+ public List<ModificationData> collectChanges(@NotNull VcsRoot originalRoot,\r
+ @NotNull String originalRootVersion,\r
+ @NotNull VcsRoot branchRoot,\r
+ @Nullable String branchRootVersion,\r
+ @NotNull CheckoutRules checkoutRules) throws VcsException {\r
+ LOG.debug("Collecting changes [" +LogUtil.describe(originalRoot) + "-" + originalRootVersion + "].." +\r
+ "[" + LogUtil.describe(branchRoot) + "-" + branchRootVersion + "]");\r
+ if (branchRootVersion == null) {\r
+ LOG.warn("Branch root version is null for " + LogUtil.describe(branchRoot) + ", return empty list of changes");\r
+ return Collections.emptyList();\r
+ }\r
+ String forkPoint = getLastCommonVersion(originalRoot, originalRootVersion, branchRoot, branchRootVersion);\r
+ return collectChanges(branchRoot, forkPoint, branchRootVersion, checkoutRules);\r
+ }\r
+\r
+ @NotNull\r
+ public List<ModificationData> collectChanges(@NotNull VcsRoot root,\r
+ @NotNull String fromVersion,\r
+ @Nullable String currentVersion,\r
+ @NotNull CheckoutRules checkoutRules) throws VcsException {\r
+ List<ModificationData> result = new ArrayList<ModificationData>();\r
+ OperationContext context = createContext(root, "collecting changes");\r
+ try {\r
+ LOG.debug("Collecting changes " + fromVersion + ".." + currentVersion + " for " + context.getSettings().debugInfo());\r
+ if (currentVersion == null) {\r
+ LOG.warn("Current version is null for " + context.getSettings().debugInfo() + ", return empty list of changes");\r
+ return result;\r
+ }\r
+ String upperBoundSHA = GitUtils.versionRevision(currentVersion);\r
+ ensureRevCommitLoaded(context, context.getSettings(), upperBoundSHA);\r
+ String lowerBoundSHA = GitUtils.versionRevision(fromVersion);\r
+ Repository r = context.getRepository();\r
+ result.addAll(getModifications(context, r, upperBoundSHA, lowerBoundSHA, GitUtils.versionTime(fromVersion)));\r
+ } catch (Exception e) {\r
+ throw context.wrapException(e);\r
+ } finally {\r
+ context.close();\r
+ }\r
+ return result;\r
+ }\r
+ \r
+ \r
+ private List<ModificationData> getModifications(@NotNull final OperationContext context, @NotNull final Repository r, @NotNull final String upperBoundSHA, @NotNull final String lowerBoundSHA, final long timeLowerBound) throws VcsException, IOException {\r
+ List<ModificationData> modifications = new ArrayList<ModificationData>();\r
+ ModificationDataRevWalk revWalk = new ModificationDataRevWalk(context, myConfig.getFixedSubmoduleCommitSearchDepth());\r
+ revWalk.sort(RevSort.TOPO);\r
+ try {\r
+ revWalk.markStart(revWalk.parseCommit(ObjectId.fromString(upperBoundSHA)));\r
+ ObjectId lowerBoundId = ObjectId.fromString(lowerBoundSHA);\r
+ if (r.hasObject(lowerBoundId)) {\r
+ revWalk.markUninteresting(revWalk.parseCommit(lowerBoundId));\r
+ } else {\r
+ LOG.warn("From version " + lowerBoundSHA + " is not found, collecting changes based on commit time " + context.getSettings().debugInfo());\r
+ revWalk.limitByCommitTime(timeLowerBound);\r
+ }\r
+ while (revWalk.next() != null) {\r
+ modifications.add(revWalk.createModificationData());\r
+ }\r
+ return modifications;\r
+ } finally {\r
+ revWalk.release();\r
+ } \r
+ }\r
+\r
+\r
+ @NotNull\r
+ public String getRemoteRunOnBranchPattern() {\r
+ return "refs/heads/remote-run/*";\r
+ }\r
+\r
+ @NotNull\r
+ public RepositoryState getCurrentState(@NotNull VcsRoot root) throws VcsException {\r
+ RepositoryState state = new RepositoryStateImpl();\r
+ for (Ref ref : getRemoteRefs(root).values()) {\r
+ state.addBranch(ref.getName(), ref.getObjectId().name());\r
+ }\r
+ return state;\r
+ }\r
+\r
+ @NotNull\r
+ public Map<String, String> getBranchRootOptions(@NotNull VcsRoot root, @NotNull String branchName) {\r
+ final Map<String, String> result = new HashMap<String, String>(root.getProperties());\r
+ result.put(Constants.BRANCH_NAME, branchName);\r
+ return result;\r
+ }\r
+\r
+\r
+ @Nullable\r
+ public PersonalBranchDescription getPersonalBranchDescription(@NotNull VcsRoot original, @NotNull String branchName) throws VcsException {\r
+ VcsRoot branchRoot = createBranchRoot(original, branchName);\r
+ OperationContext context = createContext(branchRoot, "find fork version");\r
+ PersonalBranchDescription result = null;\r
+ RevWalk walk = null;\r
+ try {\r
+ String originalCommit = GitUtils.versionRevision(getCurrentVersion(original));\r
+ String branchCommit = GitUtils.versionRevision(getCurrentVersion(branchRoot));\r
+ Repository db = context.getRepository();\r
+ walk = new RevWalk(db);\r
+ walk.markStart(walk.parseCommit(ObjectId.fromString(branchCommit)));\r
+ walk.markUninteresting(walk.parseCommit(ObjectId.fromString(originalCommit)));\r
+ walk.sort(RevSort.TOPO);\r
+ boolean lastCommit = true;\r
+ String firstCommitInBranch = null;\r
+ String lastCommitUser = null;\r
+ RevCommit c;\r
+ while ((c = walk.next()) != null) {\r
+ if (lastCommit) {\r
+ lastCommitUser = GitServerUtil.getUser(context.getSettings(), c);\r
+ lastCommit = false;\r
+ }\r
+ firstCommitInBranch = c.name();\r
+ }\r
+ if (firstCommitInBranch != null && lastCommitUser != null)\r
+ result = new PersonalBranchDescription(firstCommitInBranch, lastCommitUser);\r
+ } catch (Exception e) {\r
+ throw context.wrapException(e);\r
+ } finally {\r
+ try {\r
+ if (walk != null)\r
+ walk.release();\r
+ } finally {\r
+ context.close();\r
+ }\r
+ }\r
+ return result;\r
+ }\r
+\r
+ private VcsRoot createBranchRoot(VcsRoot original, String branchName) {\r
+ VcsRootImpl result = new VcsRootImpl(original.getId(), original.getVcsName());\r
+ result.addAllProperties(original.getProperties());\r
+ result.addProperty(Constants.BRANCH_NAME, branchName);\r
+ return result;\r
+ }\r
+\r
+ private String getLastCommonVersion(VcsRoot baseRoot, String baseVersion, VcsRoot tipRoot, String tipVersion) throws VcsException {\r
+ OperationContext context = createContext(tipRoot, "find fork version");\r
+ Settings baseSettings = context.getSettings(baseRoot);\r
+ Settings tipSettings = context.getSettings();\r
+ LOG.debug("Find last common version between [" + baseSettings.debugInfo() + "-" + baseVersion + "].." +\r
+ "[" + tipSettings.debugInfo() + "-" + tipVersion + "]");\r
+ RevWalk walk = null;\r
+ try {\r
+ RevCommit baseCommit = ensureCommitLoaded(context, baseSettings, baseVersion);\r
+ RevCommit tipCommit = ensureCommitLoaded(context, tipSettings, tipVersion);\r
+ Repository tipRepository = context.getRepository(tipSettings);\r
+ walk = new RevWalk(tipRepository);\r
+ walk.setRevFilter(RevFilter.MERGE_BASE);\r
+ walk.markStart(walk.parseCommit(baseCommit.getId()));\r
+ walk.markStart(walk.parseCommit(tipCommit.getId()));\r
+ final RevCommit base = walk.next();\r
+ String result = GitServerUtil.makeVersion(base);\r
+ LOG.debug("Last common revision between " + baseSettings.debugInfo() + " and " + tipSettings.debugInfo() + " is " + result);\r
+ return result;\r
+ } catch (Exception e) {\r
+ throw context.wrapException(e);\r
+ } finally {\r
+ try {\r
+ if (walk != null)\r
+ walk.release();\r
+ } finally {\r
+ context.close();\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ public void buildPatch(@NotNull VcsRoot root,\r
+ @Nullable final String fromVersion,\r
+ @NotNull String toVersion,\r
+ @NotNull final PatchBuilder builder,\r
+ @NotNull CheckoutRules checkoutRules) throws IOException, VcsException {\r
+ final OperationContext context = createContext(root, "patch building");\r
+ final boolean debugFlag = LOG.isDebugEnabled();\r
+ final boolean debugInfoOnEachCommit = myConfig.isPrintDebugInfoOnEachCommit();\r
+ try {\r
+ final Repository r = context.getRepository();\r
+ VcsChangeTreeWalk tw = null;\r
+ try {\r
+ RevCommit toCommit = ensureRevCommitLoaded(context, context.getSettings(), GitUtils.versionRevision(toVersion));\r
+ if (toCommit == null) {\r
+ throw new VcsException("Missing commit for version: " + toVersion);\r
+ }\r
+ tw = new VcsChangeTreeWalk(r, context.getSettings().debugInfo());\r
+ tw.setFilter(TreeFilter.ANY_DIFF);\r
+ tw.setRecursive(true);\r
+ context.addTree(tw, r, toCommit, false);\r
+ if (fromVersion != null) {\r
+ if (debugFlag) {\r
+ LOG.debug("Creating patch " + fromVersion + ".." + toVersion + " for " + context.getSettings().debugInfo());\r
+ }\r
+ RevCommit fromCommit = getCommit(r, GitUtils.versionRevision(fromVersion));\r
+ if (fromCommit == null) {\r
+ throw new IncrementalPatchImpossibleException("The form commit " + fromVersion + " is not available in the repository");\r
+ }\r
+ context.addTree(tw, r, fromCommit, true);\r
+ } else {\r
+ if (debugFlag) {\r
+ LOG.debug("Creating clean patch " + toVersion + " for " + context.getSettings().debugInfo());\r
+ }\r
+ tw.addTree(new EmptyTreeIterator());\r
+ }\r
+ final List<Callable<Void>> actions = new LinkedList<Callable<Void>>();\r
+ while (tw.next()) {\r
+ final String path = tw.getPathString();\r
+ final String mapped = checkoutRules.map(path);\r
+ if (mapped == null) {\r
+ continue;\r
+ }\r
+ if (debugFlag && debugInfoOnEachCommit) {\r
+ LOG.debug("File found " + tw.treeWalkInfo(path) + " for " + context.getSettings().debugInfo());\r
+ }\r
+ switch (tw.classifyChange()) {\r
+ case UNCHANGED:\r
+ // change is ignored\r
+ continue;\r
+ case MODIFIED:\r
+ case ADDED:\r
+ case FILE_MODE_CHANGED:\r
+ if (!FileMode.GITLINK.equals(tw.getFileMode(0))) {\r
+ final String mode = tw.getModeDiff();\r
+ if (mode != null && LOG.isDebugEnabled())\r
+ LOG.debug("The mode change " + mode + " is detected for " + tw.treeWalkInfo(path));\r
+ final ObjectId id = tw.getObjectId(0);\r
+ final Repository objRep = getRepository(r, tw, 0);\r
+ final Callable<Void> action = new Callable<Void>() {\r
+ public Void call() throws Exception {\r
+ InputStream objectStream = null;\r
+ try {\r
+ final ObjectLoader loader = objRep.open(id);\r
+ if (loader == null) {\r
+ throw new IOException("Unable to find blob " + id + (path == null ? "" : "(" + path + ")") + " in repository " + r);\r
+ }\r
+ objectStream = loader.isLarge() ? loader.openStream() : new ByteArrayInputStream(loader.getCachedBytes());\r
+ builder.changeOrCreateBinaryFile(GitUtils.toFile(mapped), mode, objectStream, loader.getSize());\r
+ } catch (Error e) {\r
+ LOG.error("Unable to load file: " + path + "(" + id.name() + ") from: " + context.getSettings().debugInfo());\r
+ throw e;\r
+ } catch (Exception e) {\r
+ LOG.error("Unable to load file: " + path + "(" + id.name() + ") from: " + context.getSettings().debugInfo());\r
+ throw e;\r
+ } finally {\r
+ if (objectStream != null) objectStream.close();\r
+ }\r
+ return null;\r
+ }\r
+ };\r
+ if (fromVersion == null) {\r
+ // clean patch, we aren't going to see any deletes\r
+ action.call();\r
+ } else {\r
+ actions.add(action);\r
+ }\r
+ }\r
+ break;\r
+ case DELETED:\r
+ if (!FileMode.GITLINK.equals(tw.getFileMode(0))) {\r
+ builder.deleteFile(GitUtils.toFile(mapped), true);\r
+ }\r
+ break;\r
+ default:\r
+ throw new IllegalStateException("Unknown change type");\r
+ }\r
+ }\r
+ for (Callable<Void> a : actions) {\r
+ a.call();\r
+ }\r
+ } finally {\r
+ if (tw != null) tw.release();\r
+ }\r
+ } catch (Exception e) {\r
+ throw context.wrapException(e);\r
+ } finally {\r
+ context.close();\r
+ }\r
+ }\r
+\r
+\r
+ @NotNull\r
+ public byte[] getContent(@NotNull VcsModification vcsModification,\r
+ @NotNull VcsChangeInfo change,\r
+ @NotNull VcsChangeInfo.ContentType contentType,\r
+ @NotNull VcsRoot vcsRoot)\r
+ throws VcsException {\r
+ String version = contentType == VcsChangeInfo.ContentType.BEFORE_CHANGE\r
+ ? change.getBeforeChangeRevisionNumber()\r
+ : change.getAfterChangeRevisionNumber();\r
+ String file = change.getRelativeFileName();\r
+ return getContent(file, vcsRoot, version);\r
+ }\r
+\r
+ @NotNull\r
+ public byte[] getContent(@NotNull String filePath, @NotNull VcsRoot root, @NotNull String version) throws VcsException {\r
+ OperationContext context = createContext(root, "retrieving content");\r
+ try {\r
+ final long start = System.currentTimeMillis();\r
+ Repository r = context.getRepository();\r
+ final TreeWalk tw = new TreeWalk(r);\r
+ try {\r
+ if (LOG.isDebugEnabled()) {\r
+ LOG.debug("Getting data from " + version + ":" + filePath + " for " + context.getSettings().debugInfo());\r
+ }\r
+ final String rev = GitUtils.versionRevision(version);\r
+ RevCommit c = ensureRevCommitLoaded(context, context.getSettings(), rev);\r
+ tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(filePath)));\r
+ tw.setRecursive(tw.getFilter().shouldBeRecursive());\r
+ context.addTree(tw, r, c, true);\r
+ if (!tw.next()) {\r
+ throw new VcsFileNotFoundException("The file " + filePath + " could not be found in " + rev + context.getSettings().debugInfo());\r
+ }\r
+ final byte[] data = loadObject(r, tw, 0);\r
+ if (LOG.isDebugEnabled()) {\r
+ LOG.debug(\r
+ "File retrieved " + version + ":" + filePath + " (hash = " + tw.getObjectId(0) + ", length = " + data.length + ") for " +\r
+ context.getSettings().debugInfo());\r
+ }\r
+ return data;\r
+ } finally {\r
+ final long finish = System.currentTimeMillis();\r
+ if (PERFORMANCE_LOG.isDebugEnabled()) {\r
+ PERFORMANCE_LOG.debug("[getContent] root=" + context.getSettings().debugInfo() + ", file=" + filePath + ", get object content: " + (finish - start) + "ms");\r
+ }\r
+ tw.release();\r
+ }\r
+ } catch (Exception e) {\r
+ throw context.wrapException(e);\r
+ } finally {\r
+ context.close();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Load bytes that correspond to the position in the tree walker\r
+ *\r
+ * @param r the initial repository\r
+ * @param tw the tree walker\r
+ * @param nth the tree in the tree wailer\r
+ * @return loaded bytes\r
+ * @throws IOException if there is an IO error\r
+ */\r
+ private byte[] loadObject(Repository r, TreeWalk tw, final int nth) throws IOException {\r
+ ObjectId id = tw.getObjectId(nth);\r
+ Repository objRep = getRepository(r, tw, nth);\r
+ final String path = tw.getPathString();\r
+ return loadObject(objRep, path, id);\r
+ }\r
+\r
+ /**\r
+ * Load object by blob ID\r
+ *\r
+ * @param r the repository\r
+ * @param path the path (might be null)\r
+ * @param id the object id\r
+ * @return the object's bytes\r
+ * @throws IOException in case of IO problem\r
+ */\r
+ private byte[] loadObject(Repository r, String path, ObjectId id) throws IOException {\r
+ final ObjectLoader loader = r.open(id);\r
+ if (loader == null) {\r
+ throw new IOException("Unable to find blob " + id + (path == null ? "" : "(" + path + ")") + " in repository " + r);\r
+ }\r
+ if (loader.isLarge()) {\r
+ assert loader.getSize() < Integer.MAX_VALUE;\r
+ ByteArrayOutputStream output = new ByteArrayOutputStream((int) loader.getSize());\r
+ loader.copyTo(output);\r
+ return output.toByteArray();\r
+ } else {\r
+ return loader.getCachedBytes();\r
+ }\r
+ }\r
+\r
+ private RevCommit ensureCommitLoaded(OperationContext context, Settings rootSettings, String commitWithDate) throws Exception {\r
+ final String commit = GitUtils.versionRevision(commitWithDate);\r
+ return ensureRevCommitLoaded(context, rootSettings, commit);\r
+ }\r
+\r
+ private RevCommit ensureRevCommitLoaded(OperationContext context, Settings settings, String commitSHA) throws Exception {\r
+ Repository db = context.getRepository(settings);\r
+ RevCommit result = null;\r
+ try {\r
+ final long start = System.currentTimeMillis();\r
+ result = getCommit(db, commitSHA);\r
+ final long finish = System.currentTimeMillis();\r
+ if (PERFORMANCE_LOG.isDebugEnabled()) {\r
+ PERFORMANCE_LOG.debug("[ensureCommitLoaded] root=" + settings.debugInfo() + ", commit=" + commitSHA + ", local commit lookup: " + (finish - start) + "ms");\r
+ }\r
+ } catch (IOException ex) {\r
+ if (LOG.isDebugEnabled()) {\r
+ LOG.debug("IO problem for commit " + commitSHA + " in " + settings.debugInfo(), ex);\r
+ }\r
+ }\r
+ if (result == null) {\r
+ if (LOG.isDebugEnabled()) {\r
+ LOG.debug("Commit " + commitSHA + " is not in the repository for " + settings.debugInfo() + ", fetching data... ");\r
+ }\r
+ fetchBranchData(settings, db);\r
+ result = getCommit(db, commitSHA);\r
+ if (result == null) {\r
+ throw new VcsException("The version name could not be resolved " + commitSHA + "(" + settings.getRepositoryFetchURL().toString() + "#" + settings.getRef() + ")");\r
+ }\r
+ }\r
+ return result;\r
+ }\r
+\r
+ RevCommit getCommit(Repository repository, String commitSHA) throws IOException {\r
+ return getCommit(repository, ObjectId.fromString(commitSHA));\r
+ }\r
+\r
+ public RevCommit getCommit(Repository repository, ObjectId commitId) throws IOException {\r
+ final long start = System.currentTimeMillis();\r
+ RevWalk walk = new RevWalk(repository);\r
+ try {\r
+ return walk.parseCommit(commitId);\r
+ } finally {\r
+ walk.release();\r
+ final long finish = System.currentTimeMillis();\r
+ if (PERFORMANCE_LOG.isDebugEnabled()) {\r
+ PERFORMANCE_LOG.debug("[RevWalk.parseCommit] repository=" + repository.getDirectory().getAbsolutePath() + ", commit=" + commitId.name() + ", took: " + (finish - start) + "ms");\r
+ }\r
+ }\r
+ }\r
+\r
+ @NotNull\r
+ public String getName() {\r
+ return Constants.VCS_NAME;\r
+ }\r
+\r
+ @NotNull\r
+ public String getDisplayName() {\r
+ initDisplayNameIfRequired();\r
+ return myDisplayName;\r
+ }\r
+\r
+ private void initDisplayNameIfRequired() {\r
+ if (myDisplayName == null) {\r
+ if (myExtensionHolder != null) {\r
+ boolean communityPluginFound = false;\r
+ final Collection<VcsSupportContext> vcsPlugins = myExtensionHolder.getServices(VcsSupportContext.class);\r
+ for (VcsSupportContext plugin : vcsPlugins) {\r
+ if (plugin.getCore().getName().equals("git")) {\r
+ communityPluginFound = true;\r
+ }\r
+ }\r
+ if (communityPluginFound) {\r
+ myDisplayName = "Git (Jetbrains plugin)";\r
+ } else {\r
+ myDisplayName = "Git";\r
+ }\r
+ } else {\r
+ myDisplayName = "Git (Jetbrains plugin)";\r
+ }\r
+ }\r
+ }\r
+\r
+ public PropertiesProcessor getVcsPropertiesProcessor() {\r
+ return new VcsPropertiesProcessor();\r
+ }\r
+\r
+ @NotNull\r
+ public String getVcsSettingsJspFilePath() {\r
+ return "gitSettings.jsp";\r
+ }\r
+\r
+ @NotNull\r
+ public String describeVcsRoot(VcsRoot root) {\r
+ final String branch = root.getProperty(Constants.BRANCH_NAME);\r
+ return root.getProperty(Constants.FETCH_URL) + "#" + (branch == null ? "master" : branch);\r
+ }\r
+\r
+ public Map<String, String> getDefaultVcsProperties() {\r
+ final HashMap<String, String> map = new HashMap<String, String>();\r
+ map.put(Constants.BRANCH_NAME, "master");\r
+ map.put(Constants.IGNORE_KNOWN_HOSTS, "true");\r
+ return map;\r
+ }\r
+\r
+ public String getVersionDisplayName(@NotNull String version, @NotNull VcsRoot root) throws VcsException {\r
+ return GitServerUtil.displayVersion(version);\r
+ }\r
+\r
+ @NotNull\r
+ public Comparator<String> getVersionComparator() {\r
+ return GitUtils.VERSION_COMPARATOR;\r
+ }\r
+\r
+ @NotNull\r
+ public String getCurrentVersion(@NotNull VcsRoot root) throws VcsException {\r
+ OperationContext context = createContext(root, "retrieving current version");\r
+ Settings s = context.getSettings();\r
+ try {\r
+ Repository r = context.getRepository();\r
+ String refName = GitUtils.expandRef(s.getRef());\r
+\r
+ if (!myConfig.isSeparateProcessForFetch() || isRemoteRefUpdated(root, r, s, refName))\r
+ fetchBranchData(s, r);\r
+\r
+ Ref branchRef = r.getRef(refName);\r
+ if (branchRef == null) {\r
+ throw new VcsException("The ref '" + refName + "' could not be resolved");\r
+ }\r
+ String cachedCurrentVersion = getCachedCurrentVersion(s.getRepositoryDir(), s.getRef());\r
+ if (cachedCurrentVersion != null && GitUtils.versionRevision(cachedCurrentVersion).equals(branchRef.getObjectId().name())) {\r
+ return cachedCurrentVersion;\r
+ } else {\r
+ RevCommit c = getCommit(r, branchRef.getObjectId());\r
+ if (c == null) {\r
+ throw new VcsException("The ref '" + refName + "' could not be resolved");\r
+ }\r
+ if (LOG.isDebugEnabled()) {\r
+ LOG.debug("Current version: " + c.getId().name() + " " + s.debugInfo());\r
+ }\r
+ final String currentVersion = GitServerUtil.makeVersion(c);\r
+ setCachedCurrentVersion(s.getRepositoryDir(), s.getRef(), currentVersion);\r
+ GitMapFullPath.invalidateRevisionsCache(root);\r
+ return currentVersion;\r
+ }\r
+ } catch (Exception e) {\r
+ throw context.wrapException(e);\r
+ } finally {\r
+ context.close();\r
+ }\r
+ }\r
+\r
+\r
+ private boolean isRemoteRefUpdated(@NotNull VcsRoot root, @NotNull Repository db, @NotNull Settings s, @NotNull String refName) throws Exception {\r
+ Map<String, Ref> remoteRefs = getRemoteRefs(root, db, s);\r
+ Ref remoteRef = remoteRefs.get(refName);\r
+ if (remoteRef == null)\r
+ return true;\r
+\r
+ String cachedCurrentVersion = getCachedCurrentVersion(s.getRepositoryDir(), s.getRef());\r
+ return cachedCurrentVersion == null || !remoteRef.getObjectId().name().equals(GitUtils.versionRevision(cachedCurrentVersion));\r
+ }\r
+\r
+\r
+ /**\r
+ * Return cached current version for branch in repository in specified dir, or null if no cache version found.\r
+ */\r
+ private String getCachedCurrentVersion(File repositoryDir, String branchName) {\r
+ return myCurrentVersionCache.get(Pair.create(repositoryDir, branchName));\r
+ }\r
+\r
+ /**\r
+ * Save current version for branch of repository in cache.\r
+ */\r
+ private void setCachedCurrentVersion(File repositoryDir, String branchName, String currentVersion) {\r
+ myCurrentVersionCache.put(Pair.create(repositoryDir, branchName), currentVersion);\r
+ }\r
+\r
+ public boolean sourcesUpdatePossibleIfChangesNotFound(@NotNull VcsRoot root) {\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Fetch data for the branch\r
+ *\r
+ * @param settings settings for the root\r
+ * @param repository the repository\r
+ * @throws Exception if there is a problem with fetching data\r
+ */\r
+ private void fetchBranchData(Settings settings, Repository repository) throws Exception {\r
+ final String refName = GitUtils.expandRef(settings.getRef());\r
+ RefSpec spec = new RefSpec().setSource(refName).setDestination(refName).setForceUpdate(true);\r
+ fetch(repository, settings.getRepositoryFetchURL(), spec, settings.getAuthSettings());\r
+ }\r
+\r
+\r
+ public void fetch(Repository db, URIish fetchURI, Collection<RefSpec> refspecs, Settings.AuthSettings auth) throws NotSupportedException, VcsException, TransportException {\r
+ File repositoryDir = db.getDirectory();\r
+ assert repositoryDir != null : "Non-local repository";\r
+ Lock rmLock = myRepositoryManager.getRmLock(repositoryDir).readLock();\r
+ rmLock.lock();\r
+ try {\r
+ final long start = System.currentTimeMillis();\r
+ synchronized (myRepositoryManager.getWriteLock(repositoryDir)) {\r
+ final long finish = System.currentTimeMillis();\r
+ PERFORMANCE_LOG.debug("[waitForWriteLock] repository: " + repositoryDir.getAbsolutePath() + ", took " + (finish - start) + "ms");\r
+ myFetchCommand.fetch(db, fetchURI, refspecs, auth);\r
+ }\r
+ } finally {\r
+ rmLock.unlock();\r
+ }\r
+ }\r
+ /**\r
+ * Make fetch into local repository (it.s getDirectory() should be != null)\r
+ *\r
+ * @param db repository\r
+ * @param fetchURI uri to fetch\r
+ * @param refspec refspec\r
+ * @param auth auth settings\r
+ */\r
+ public void fetch(Repository db, URIish fetchURI, RefSpec refspec, Settings.AuthSettings auth) throws NotSupportedException, VcsException, TransportException {\r
+ fetch(db, fetchURI, Collections.singletonList(refspec), auth);\r
+ }\r
+\r
+\r
+ public String testConnection(@NotNull VcsRoot vcsRoot) throws VcsException {\r
+ OperationContext context = createContext(vcsRoot, "connection test");\r
+ TestConnectionCommand command = new TestConnectionCommand(myTransportFactory, myRepositoryManager);\r
+ try {\r
+ return command.testConnection(context);\r
+ } catch (Exception e) {\r
+ throw context.wrapException(e);\r
+ } finally {\r
+ context.close();\r
+ }\r
+ }\r
+\r
+\r
+ @Override\r
+ public TestConnectionSupport getTestConnectionSupport() {\r
+ return this;\r
+ }\r
+\r
+ public OperationContext createContext(VcsRoot root, String operation) {\r
+ return new OperationContext(this, myRepositoryManager, root, operation);\r
+ }\r
+\r
+ public LabelingSupport getLabelingSupport() {\r
+ return this;\r
+ }\r
+\r
+ @NotNull\r
+ public VcsFileContentProvider getContentProvider() {\r
+ return this;\r
+ }\r
+\r
+ @NotNull\r
+ public CollectChangesPolicy getCollectChangesPolicy() {\r
+ return this;\r
+ }\r
+\r
+ @NotNull\r
+ public BuildPatchPolicy getBuildPatchPolicy() {\r
+ return this;\r
+ }\r
+\r
+ public String label(@NotNull String label, @NotNull String version, @NotNull VcsRoot root, @NotNull CheckoutRules checkoutRules)\r
+ throws VcsException {\r
+ OperationContext context = createContext(root, "labelling");\r
+ Settings s = context.getSettings();\r
+ try {\r
+ Repository r = context.getRepository();\r
+ String commitSHA = GitUtils.versionRevision(version);\r
+ RevCommit commit = ensureRevCommitLoaded(context, s, commitSHA);\r
+ Git git = new Git(r);\r
+ git.tag().setName(label).setObjectId(commit).call();\r
+ String tagRef = GitUtils.tagName(label);\r
+ if (LOG.isDebugEnabled()) {\r
+ LOG.debug("Tag created " + label + "=" + version + " for " + s.debugInfo());\r
+ }\r
+ synchronized (myRepositoryManager.getWriteLock(s.getRepositoryDir())) {\r
+ final Transport tn = myTransportFactory.createTransport(r, s.getRepositoryPushURL(), s.getAuthSettings());\r
+ try {\r
+ final PushConnection c = tn.openPush();\r
+ try {\r
+ RemoteRefUpdate ru = new RemoteRefUpdate(r, tagRef, tagRef, false, null, null);\r
+ c.push(NullProgressMonitor.INSTANCE, Collections.singletonMap(tagRef, ru));\r
+ LOG.info("Tag " + label + "=" + version + " pushed with status " + ru.getStatus() + " for " + s.debugInfo());\r
+ switch (ru.getStatus()) {\r
+ case UP_TO_DATE:\r
+ case OK:\r
+ break;\r
+ default:\r
+ throw new VcsException("The remote tag was not created (" + ru.getStatus() + "): " + label);\r
+ }\r
+ } finally {\r
+ c.close();\r
+ }\r
+ return label;\r
+ } finally {\r
+ tn.close();\r
+ }\r
+ }\r
+ } catch (Exception e) {\r
+ throw context.wrapException(e);\r
+ } finally {\r
+ context.close();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Get repository from tree walker\r
+ *\r
+ * @param r the initial repository\r
+ * @param tw the tree walker\r
+ * @param nth the position\r
+ * @return the actual repository\r
+ */\r
+ private Repository getRepository(Repository r, TreeWalk tw, int nth) {\r
+ Repository objRep;\r
+ AbstractTreeIterator ti = tw.getTree(nth, AbstractTreeIterator.class);\r
+ if (ti instanceof SubmoduleAwareTreeIterator) {\r
+ objRep = ((SubmoduleAwareTreeIterator)ti).getRepository();\r
+ } else {\r
+ objRep = r;\r
+ }\r
+ return objRep;\r
+ }\r
+\r
+ @Override\r
+ public VcsPersonalSupport getPersonalSupport() {\r
+ return this;\r
+ }\r
+\r
+ /**\r
+ * Expected fullPath format:\r
+ * <p/>\r
+ * "<git revision hash>|<repository url>|<file relative path>"\r
+ *\r
+ * @param rootEntry indicates the association between VCS root and build configuration\r
+ * @param fullPath change path from IDE patch\r
+ * @return the mapped path\r
+ */\r
+ @NotNull\r
+ public Collection<String> mapFullPath(@NotNull final VcsRootEntry rootEntry, @NotNull final String fullPath) {\r
+ OperationContext context = createContext(rootEntry.getVcsRoot(), "map full path");\r
+ try {\r
+ return new GitMapFullPath(context, this, rootEntry, fullPath).mapFullPath();\r
+ } catch (VcsException e) {\r
+ LOG.error(e);\r
+ return Collections.emptySet();\r
+ } finally {\r
+ context.close();\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public boolean isAgentSideCheckoutAvailable() {\r
+ return true;\r
+ }\r
+\r
+\r
+ @Override\r
+ public UrlSupport getUrlSupport() {\r
+ return new GitUrlSupport();\r
+ }\r
+\r
+\r
+ public List<String> getRemoteBranches(@NotNull final VcsRoot root, @NotNull final String pattern) throws VcsException {\r
+ Collection<Ref> remotes = getRemoteRefs(root).values();\r
+ Pattern p = Pattern.compile(pattern);\r
+ List<String> result = new ArrayList<String>();\r
+ for (Ref ref : remotes) {\r
+ if (p.matcher(ref.getName()).matches()) {\r
+ result.add(ref.getName());\r
+ }\r
+ }\r
+ return result;\r
+ }\r
+\r
+ @NotNull\r
+ private Map<String, Ref> getRemoteRefs(@NotNull final VcsRoot root) throws VcsException {\r
+ OperationContext context = createContext(root, "list remote refs");\r
+ Settings s = context.getSettings();\r
+ File tmpDir = null;\r
+ try {\r
+ tmpDir = FileUtil.createTempDirectory("git-ls-remote", "");\r
+ s.setUserDefinedRepositoryPath(tmpDir);\r
+ Repository db = context.getRepository();\r
+ return getRemoteRefs(root, db, s);\r
+ } catch (Exception e) {\r
+ throw context.wrapException(e);\r
+ } finally {\r
+ context.close();\r
+ if (tmpDir != null) {\r
+ myRepositoryManager.cleanLocksFor(tmpDir);\r
+ FileUtil.delete(tmpDir);\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ @NotNull\r
+ private Map<String, Ref> getRemoteRefs(@NotNull final VcsRoot root, @NotNull Repository db, @NotNull Settings s) throws Exception {\r
+ Transport transport = null;\r
+ FetchConnection connection = null;\r
+ try {\r
+ transport = myTransportFactory.createTransport(db, s.getRepositoryFetchURL(), s.getAuthSettings());\r
+ connection = transport.openFetch();\r
+ return connection.getRefsMap();\r
+ } catch (NotSupportedException nse) {\r
+ throw friendlyNotSupportedException(root, s, nse);\r
+ } catch (TransportException te) {\r
+ throw friendlyTransportException(te);\r
+ } finally {\r
+ if (connection != null) connection.close();\r
+ if (transport != null) transport.close();\r
+ }\r
+ }\r
+\r
+ @NotNull\r
+ private ObjectId getVcsRootGitId(final @NotNull VcsRoot root) throws VcsException{\r
+ final OperationContext context = createContext(root, "client-mapping");\r
+ try {\r
+ final Settings gitSettings = context.getSettings(root);\r
+ final Repository gitRepo = context.getRepository(gitSettings);\r
+ if(gitRepo == null){\r
+ throw new VcsException(String.format("Could not find Git Repository for '%s'", root.getName()));\r
+ }\r
+ final ObjectId objectId = gitRepo.resolve(gitSettings.getRef());\r
+ if(objectId == null){\r
+ throw new VcsException(String.format("Could not resolve Git Reference '%s'", gitSettings.getRef()));\r
+ }\r
+ return objectId;\r
+ } catch (AmbiguousObjectException e) {\r
+ throw new VcsException(e);\r
+ } catch (IOException e) {\r
+ throw new VcsException(e);\r
+ } finally {\r
+ context.close();\r
+ }\r
+ }\r
+\r
+ public Collection<VcsClientMapping> getClientMapping(final @NotNull VcsRoot root, final @NotNull IncludeRule rule) throws VcsException {\r
+ final ObjectId gitObjId = getVcsRootGitId(root);\r
+ return Collections.singletonList(new VcsClientMapping(String.format("%s||%s", gitObjId.name(), rule.getFrom()), rule.getTo()));\r
+ }\r
+\r
+ @Override\r
+ public boolean isDAGBasedVcs() {\r
+ return true;\r
+ }\r
+}\r