/******************************************************************************* * Copyright (C) 2011, 2015 Dariusz Luksza <dariusz@luksza.org> and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package org.eclipse.egit.core.synchronize; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.egit.core.Activator; import org.eclipse.egit.core.synchronize.dto.GitSynchronizeData; import org.eclipse.egit.core.synchronize.dto.GitSynchronizeDataSet; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.treewalk.EmptyTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter; import org.eclipse.jgit.treewalk.filter.OrTreeFilter; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; /** * Simple and thin tree cache for git meta data about resources in repository. */ class GitSyncCache { private final Map<File, GitSyncObjectCache> cache; public static GitSyncCache getAllData(GitSynchronizeDataSet gsds, IProgressMonitor monitor) { Map<GitSynchronizeData, Collection<String>> updateRequests = new HashMap<GitSynchronizeData, Collection<String>>(); for (GitSynchronizeData data : gsds) updateRequests.put(data, Collections.<String> emptyList()); return getAllData(updateRequests, monitor); } public static GitSyncCache getAllData( Map<GitSynchronizeData, Collection<String>> updateRequests, IProgressMonitor monitor) { GitSyncCache cache = new GitSyncCache(); mergeAllDataIntoCache(updateRequests, monitor, cache); return cache; } public static void mergeAllDataIntoCache( Map<GitSynchronizeData, Collection<String>> updateRequests, IProgressMonitor monitor, GitSyncCache cache) { SubMonitor m = SubMonitor.convert(monitor, updateRequests.size()); for (Entry<GitSynchronizeData, Collection<String>> entry : updateRequests .entrySet()) { Collection<String> paths = entry.getValue(); GitSyncCache partialCache = getAllData(entry.getKey(), paths); cache.merge(partialCache, new HashSet<String>(paths)); m.worked(1); } m.done(); } private static GitSyncCache getAllData(GitSynchronizeData gsd, Collection<String> paths) { GitSyncCache cache = new GitSyncCache(); TreeFilter filter = paths.isEmpty() ? null : createPathFilter(paths); Repository repo = gsd.getRepository(); ObjectId baseTree = getTree(gsd.getSrcRevCommit()); ObjectId remoteTree = getTree(gsd.getDstRevCommit()); GitSyncObjectCache repoCache = cache.put(repo, baseTree, remoteTree); TreeFilter gsdFilter = gsd.getPathFilter(); if (filter == null) loadDataFromGit(gsd, gsdFilter, repoCache); else if (gsdFilter == null) loadDataFromGit(gsd, filter, repoCache); else loadDataFromGit(gsd, AndTreeFilter.create(filter, gsdFilter), repoCache); return cache; } private static TreeFilter createPathFilter(Collection<String> paths) { // do not use PathFilterGroup to create the filter, see bug 362430 List<TreeFilter> filters = new ArrayList<TreeFilter>(paths.size()); for (String path : paths) { if (path.length() == 0) return null; filters.add(PathFilter.create(path)); } if (filters.size() == 1) return filters.get(0); return OrTreeFilter.create(filters); } static boolean loadDataFromGit(GitSynchronizeData gsd, TreeFilter filter, GitSyncObjectCache repoCache) { Repository repo = gsd.getRepository(); try (TreeWalk tw = new TreeWalk(repo)) { if (filter != null) tw.setFilter(filter); // setup local tree FileTreeIterator fti = null; if (gsd.shouldIncludeLocal()) { fti = new FileTreeIterator(repo); tw.addTree(fti); if (filter != null) tw.setFilter(AndTreeFilter.create(filter, new NotIgnoredFilter(0))); else tw.setFilter(new NotIgnoredFilter(0)); } else if (gsd.getSrcRevCommit() != null) tw.addTree(gsd.getSrcRevCommit().getTree()); else tw.addTree(new EmptyTreeIterator()); // setup base tree if (gsd.getCommonAncestorRev() != null) tw.addTree(gsd.getCommonAncestorRev().getTree()); else tw.addTree(new EmptyTreeIterator()); // setup remote tree if (gsd.getDstRevCommit() != null) tw.addTree(gsd.getDstRevCommit().getTree()); else tw.addTree(new EmptyTreeIterator()); DirCacheIterator dci = null; if (fti != null) { dci = new DirCacheIterator(DirCache.read(repo)); tw.addTree(dci); fti.setDirCacheIterator(tw, 3); } List<ThreeWayDiffEntry> diffEntrys = ThreeWayDiffEntry .scan(tw, gsd); for (ThreeWayDiffEntry diffEntry : diffEntrys) repoCache.addMember(diffEntry); } catch (Exception e) { Activator.logError(e.getMessage(), e); return false; } return true; } private static ObjectId getTree(RevCommit commit) { if (commit != null) return commit.getTree(); else { return ObjectId.zeroId(); } } private GitSyncCache() { cache = new HashMap<File, GitSyncObjectCache>(); } /** * @param repo * instance of {@link Repository} for which mapping should be * obtained * @return instance of {@link GitSyncObjectCache} connected associated with * given repository or {@code null} when such mapping wasn't found */ public GitSyncObjectCache get(Repository repo) { return cache.get(repo.getDirectory()); } public void merge(GitSyncCache other, Set<String> filterPaths) { for (Entry<File, GitSyncObjectCache> entry : other.cache.entrySet()) { File key = entry.getKey(); if (cache.containsKey(key)) cache.get(key).merge(entry.getValue(), filterPaths); else cache.put(key, entry.getValue()); } } @Override public String toString() { StringBuilder builder = new StringBuilder(); for (Entry<File, GitSyncObjectCache> entry : cache.entrySet()) builder.append(entry.getKey().getPath()) .append(": ").append(entry.getValue()); //$NON-NLS-1$ return builder.toString(); } /** * Create mapping for given repository and returns object associated with * this repository. Any other mapping will be overwritten. * * @param repo * @param remoteTree * @param baseTree * @return new mapping object associated with given {@link Repository} */ private GitSyncObjectCache put(Repository repo, ObjectId baseTree, ObjectId remoteTree) { ThreeWayDiffEntry entry = new ThreeWayDiffEntry(); entry.baseId = AbbreviatedObjectId.fromObjectId(baseTree); entry.remoteId = AbbreviatedObjectId.fromObjectId(remoteTree); GitSyncObjectCache objectCache = new GitSyncObjectCache("", entry); //$NON-NLS-1$ cache.put(repo.getDirectory(), objectCache); return objectCache; } }