blob: f6a2204065521f953b9efda4b4dc47fc823b7317 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2015, Obeo.
*
* 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
*
* Laurent Goubet <laurent.goubet@obeo.fr> - initial API and implementation
* Axel Richard <axel.richard@obeo.fr> - Add GitPathToProjectPathConverter
*******************************************************************************/
package org.eclipse.emf.compare.egit.internal.merge;
//CHECKSTYLE:OFF
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.core.GitProvider;
import org.eclipse.egit.core.internal.util.ResourceUtil;
import org.eclipse.emf.compare.egit.internal.storage.TreeParserResourceVariant;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuildIterator;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.variants.IResourceVariantTree;
/**
* This will populate its three {@link IResourceVariantTree} by walking over a tree walk and caching the
* IResources it spans.
* <p>
* Files that are not located within the workspace will be ignored and thus will not be accessible through the
* trees created by this provider.
* </p>
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
@SuppressWarnings("restriction")
public class TreeWalkResourceVariantTreeProvider implements GitResourceVariantTreeProvider {
private final IResourceVariantTree baseTree;
private final IResourceVariantTree oursTree;
private final IResourceVariantTree theirsTree;
private final Set<IResource> roots;
private final Set<IResource> knownResources;
private final LinkedHashMap<IPath, IProject> map;
private TreeWalkResourceVariantTreeProvider(Builder builder) {
this.map = builder.map;
this.baseTree = builder.baseTree;
this.theirsTree = builder.theirsTree;
this.oursTree = builder.oursTree;
this.roots = builder.roots;
this.knownResources = builder.knownResources;
}
public IResourceVariantTree getBaseTree() {
return baseTree;
}
public IResourceVariantTree getRemoteTree() {
return theirsTree;
}
public IResourceVariantTree getSourceTree() {
return oursTree;
}
public Set<IResource> getKnownResources() {
return knownResources;
}
public Set<IResource> getRoots() {
return roots;
}
/**
* Returns a resource handle for this path in the workspace. Note that neither the resource nor the result
* need exist in the workspace : this may return inexistent or otherwise non-accessible IResources.
*
* @param repository
* The repository within which is tracked this file.
* @param repoRelativePath
* Repository-relative path of the file we need an handle for.
* @param isFolder
* <code>true</code> if the file being sought is a folder.
* @return The resource handle for the given path in the workspace.
*/
public IResource getResourceHandleForLocation(Repository repository, String repoRelativePath,
boolean isFolder) {
return getResourceHandleForLocation(repository, repoRelativePath, isFolder, map);
}
/**
* Returns a resource handle for this path in the workspace. Note that neither the resource nor the result
* need exist in the workspace : this may return inexistent or otherwise non-accessible IResources.
*
* @param repository
* The repository within which is tracked this file.
* @param repoRelativePath
* Repository-relative path of the file we need an handle for.
* @param isFolder
* <code>true</code> if the file being sought is a folder.
* @param map
* a to keeps tracks of IPath to IProject
* @return The resource handle for the given path in the workspace.
*/
private static IResource getResourceHandleForLocation(Repository repository, String repoRelativePath,
boolean isFolder, LinkedHashMap<IPath, IProject> map) {
IResource resource = null;
final String workDir = repository.getWorkTree().getAbsolutePath();
final IPath path = new Path(workDir + '/' + repoRelativePath);
final File file = path.toFile();
if (file.exists()) {
if (isFolder) {
resource = ResourceUtil.getContainerForLocation(path, false);
} else {
resource = ResourceUtil.getFileForLocation(path, false);
}
}
if (repoRelativePath.endsWith(".project")) { //$NON-NLS-1$
IPath parentPath = path.removeLastSegments(1);
IProject p = ResourcesPlugin.getWorkspace().getRoot()
.getProject(parentPath.lastSegment().toString());
if (map.get(parentPath) == null) {
map.put(parentPath, p);
}
}
if (resource == null) {
// It may be a file that only exists on remote side. We need to
// create an IResource for it.
// If it is a project file, then create an IProject.
final List<IPath> list = new ArrayList<IPath>(map.keySet());
for (int i = list.size() - 1; i >= 0; i--) {
IPath projectPath = list.get(i);
if (projectPath.isPrefixOf(path) && !projectPath.equals(path)) {
final IPath projectRelativePath = path.makeRelativeTo(projectPath);
if (isFolder) {
resource = map.get(projectPath).getFolder(projectRelativePath);
} else {
resource = map.get(projectPath).getFile(projectRelativePath);
}
break;
}
}
}
if (resource == null) {
// This is a file that no longer exists locally, yet we still need
// to determine an IResource for it.
// Try and find a Project in the workspace which path is a prefix of
// the file we seek and which is mapped to the current repository.
final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
for (IProject project : root.getProjects()) {
if (RepositoryProvider.getProvider(project, GitProvider.ID) != null) {
final IPath projectLocation = project.getLocation();
if (projectLocation != null && projectLocation.isPrefixOf(path)) {
final IPath projectRelativePath = path.makeRelativeTo(projectLocation);
if (isFolder) {
resource = project.getFolder(projectRelativePath);
} else {
resource = project.getFile(projectRelativePath);
}
break;
}
}
}
}
return resource;
}
public static class Builder {
private Repository repository;
private AbstractTreeIterator aBaseTree;
private RevTree headTree;
private RevTree mergeTree;
private DirCache dircache;
private ObjectReader reader;
private IResourceVariantTree baseTree;
private IResourceVariantTree oursTree;
private IResourceVariantTree theirsTree;
private Set<IResource> roots;
private Set<IResource> knownResources;
private GitResourceVariantCache baseCache;
private GitResourceVariantCache theirsCache;
private GitResourceVariantCache oursCache;
private LinkedHashMap<IPath, IProject> map;
/**
* @param repository
* The repository this tree walk has been created for.
* @return
*/
public Builder setRepository(Repository repository) {
this.repository = repository;
return this;
}
public Builder setaBaseTree(AbstractTreeIterator aBaseTree) {
this.aBaseTree = aBaseTree;
return this;
}
public Builder setHeadTree(RevTree headTree) {
this.headTree = headTree;
return this;
}
public Builder setMergeTree(RevTree mergeTree) {
this.mergeTree = mergeTree;
return this;
}
public Builder setDircache(DirCache dircache) {
this.dircache = dircache;
return this;
}
public Builder setReader(ObjectReader reader) {
this.reader = reader;
return this;
}
public TreeWalkResourceVariantTreeProvider build() throws IOException {
DirCacheBuilder aBuilder = dircache.builder();
DirCacheBuildIterator buildIt = new DirCacheBuildIterator(aBuilder);
this.map = new LinkedHashMap<>();
try (NameConflictTreeWalk treeWalk = new NameConflictTreeWalk(repository, reader)) {
int baseIndex = treeWalk.addTree(aBaseTree);
int ourIndex = treeWalk.addTree(headTree);
int theirIndex = treeWalk.addTree(mergeTree);
int dciPos = treeWalk.addTree(buildIt);
FileTreeIterator aWorkingTreeIterator = new FileTreeIterator(repository);
treeWalk.addTree(aWorkingTreeIterator);
aWorkingTreeIterator.setDirCacheIterator(treeWalk, dciPos);
this.baseCache = new GitResourceVariantCache();
this.theirsCache = new GitResourceVariantCache();
this.oursCache = new GitResourceVariantCache();
while (treeWalk.next()) {
final int modeBase = treeWalk.getRawMode(baseIndex);
final int modeOurs = treeWalk.getRawMode(ourIndex);
final int modeTheirs = treeWalk.getRawMode(theirIndex);
if (!hasSignificantDifference(modeBase, modeOurs, modeTheirs)) {
// conflict on file modes, leave the default merger handle it
continue;
}
final CanonicalTreeParser base = treeWalk.getTree(baseIndex, CanonicalTreeParser.class);
final CanonicalTreeParser ours = treeWalk.getTree(ourIndex, CanonicalTreeParser.class);
final CanonicalTreeParser theirs = treeWalk.getTree(theirIndex,
CanonicalTreeParser.class);
final int nonZeroMode = modeBase != 0 ? modeBase : modeOurs != 0 ? modeOurs : modeTheirs;
final IResource resource = getResourceHandleForLocation(repository,
treeWalk.getPathString(), FileMode.fromBits(nonZeroMode) == FileMode.TREE, map);
// Resource variants only make sense for IResources.
if (resource != null) {
IPath workspacePath = resource.getFullPath();
if (modeBase != 0) {
baseCache.setVariant(resource,
TreeParserResourceVariant.create(repository, base, workspacePath));
}
if (modeOurs != 0) {
oursCache.setVariant(resource,
TreeParserResourceVariant.create(repository, ours, workspacePath));
}
if (modeTheirs != 0) {
theirsCache.setVariant(resource,
TreeParserResourceVariant.create(repository, theirs, workspacePath));
}
}
if (treeWalk.isSubtree()) {
treeWalk.enterSubtree();
}
}
baseTree = new GitCachedResourceVariantTree(baseCache);
theirsTree = new GitCachedResourceVariantTree(theirsCache);
oursTree = new GitCachedResourceVariantTree(oursCache);
roots = new LinkedHashSet<>();
roots.addAll(baseCache.getRoots());
roots.addAll(oursCache.getRoots());
roots.addAll(theirsCache.getRoots());
knownResources = new LinkedHashSet<>();
knownResources.addAll(baseCache.getKnownResources());
knownResources.addAll(oursCache.getKnownResources());
knownResources.addAll(theirsCache.getKnownResources());
} finally {
// Needs reset since it might be used elsewhere
aBaseTree.reset();
}
return new TreeWalkResourceVariantTreeProvider(this);
}
private boolean hasSignificantDifference(int modeBase, int modeOurs, int modeTheirs) {
if (modeBase == 0) {
if (FileMode.fromBits(modeOurs | modeTheirs) != FileMode.MISSING) {
return true;
} else {
return (FileMode.fromBits(modeOurs) == FileMode.TREE
&& FileMode.fromBits(modeTheirs) != FileMode.TREE)
|| (FileMode.fromBits(modeOurs) != FileMode.TREE
&& FileMode.fromBits(modeTheirs) == FileMode.TREE);
}
}
return FileMode.fromBits(modeBase & modeOurs) != FileMode.MISSING
|| FileMode.fromBits(modeBase & modeTheirs) != FileMode.MISSING;
}
}
}
// CHECKSTYLE:ON