/*******************************************************************************
* Copyright (C) 2010, 2011 Mathias Kinzler <mathias.kinzler@sap.com> 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.ui.internal.merge;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.structuremergeviewer.DiffNode;
import org.eclipse.compare.structuremergeviewer.Differencer;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.core.internal.CompareCoreUtils;
import org.eclipse.egit.core.internal.storage.GitFileRevision;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.internal.CompareUtils;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.dialogs.CompareTreeView;
import org.eclipse.egit.ui.internal.revision.FileRevisionTypedElement;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.filter.OrTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
/**
* A Git-specific {@link CompareEditorInput}
*/
public class GitCompareEditorInput extends CompareEditorInput {
private static final Image FOLDER_IMAGE = PlatformUI.getWorkbench()
.getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER);
private final String baseVersion;
private final String compareVersion;
private final IResource[] resources;
private final List<String> filterPathStrings = new ArrayList<>();
private final Map<IPath, IDiffContainer> diffRoots = new HashMap<>();
private Repository repository;
/**
* @param compareVersion
* (shown on the left side in compare); currently only commit IDs
* are supported
* @param baseVersion
* (shown on the right side in compare); currently only commit
* IDs are supported
* @param repository
* repository where resources are coming from
* @param resources
* as selected by the user
*/
public GitCompareEditorInput(String compareVersion, String baseVersion,
Repository repository, IResource... resources) {
super(new CompareConfiguration());
this.repository = repository;
this.resources = convertResourceInput(resources);
this.baseVersion = baseVersion;
this.compareVersion = compareVersion;
}
/**
* @param compareVersion
* (shown on the left side in compare); currently only commit IDs
* are supported
* @param baseVersion
* (shown on the right side in compare); currently only commit
* IDs are supported
* @param repository
* as selected by the user
*/
public GitCompareEditorInput(String compareVersion, String baseVersion,
Repository repository) {
super(new CompareConfiguration());
this.resources = new IResource[0];
this.baseVersion = baseVersion;
this.compareVersion = compareVersion;
this.repository = repository;
}
@Override
protected Object prepareInput(IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException {
// make sure all resources belong to the same repository
try (RevWalk rw = new RevWalk(repository)) {
monitor.beginTask(
UIText.GitCompareEditorInput_CompareResourcesTaskName,
IProgressMonitor.UNKNOWN);
for (IResource resource : resources) {
RepositoryMapping map = RepositoryMapping.getMapping(resource);
if (map == null) {
throw new InvocationTargetException(
new IllegalStateException(
UIText.GitCompareEditorInput_ResourcesInDifferentReposMessagge));
}
if (repository != null && repository != map.getRepository())
throw new InvocationTargetException(
new IllegalStateException(
UIText.GitCompareEditorInput_ResourcesInDifferentReposMessagge));
String repoRelativePath = map.getRepoRelativePath(resource);
filterPathStrings.add(repoRelativePath);
DiffNode node = new DiffNode(Differencer.NO_CHANGE) {
@Override
public Image getImage() {
return FOLDER_IMAGE;
}
};
diffRoots.put(new Path(map.getRepoRelativePath(resource)),
node);
repository = map.getRepository();
}
if (repository == null)
throw new InvocationTargetException(new IllegalStateException(
UIText.GitCompareEditorInput_ResourcesInDifferentReposMessagge));
if (monitor.isCanceled())
throw new InterruptedException();
final RevCommit baseCommit;
try {
try {
baseCommit = rw
.parseCommit(repository.resolve(baseVersion));
} catch (IOException e) {
throw new InvocationTargetException(e);
}
final RevCommit compareCommit;
if (compareVersion == null) {
compareCommit = null;
} else {
try {
compareCommit = rw.parseCommit(
repository.resolve(compareVersion));
} catch (IOException e) {
throw new InvocationTargetException(e);
}
}
if (monitor.isCanceled())
throw new InterruptedException();
// set the labels
CompareConfiguration config = getCompareConfiguration();
config.setLeftLabel(compareVersion);
config.setRightLabel(baseVersion);
// set title and icon
if (resources.length == 0) {
Object[] titleParameters = new Object[] {
Activator.getDefault().getRepositoryUtil()
.getRepositoryName(repository),
CompareUtils.truncatedRevision(compareVersion),
CompareUtils.truncatedRevision(baseVersion) };
setTitle(NLS.bind(UIText.GitCompareEditorInput_EditorTitle,
titleParameters));
} else if (resources.length == 1) {
Object[] titleParameters = new Object[] {
resources[0].getFullPath().makeRelative()
.toString(),
CompareUtils.truncatedRevision(compareVersion),
CompareUtils.truncatedRevision(baseVersion) };
setTitle(NLS.bind(
UIText.GitCompareEditorInput_EditorTitleSingleResource,
titleParameters));
} else {
setTitle(NLS
.bind(UIText.GitCompareEditorInput_EditorTitleMultipleResources,
CompareUtils.truncatedRevision(
compareVersion),
CompareUtils.truncatedRevision(baseVersion)));
}
// build the nodes
try {
return buildDiffContainer(baseCommit, compareCommit,
monitor);
} catch (IOException e) {
throw new InvocationTargetException(e);
}
} finally {
monitor.done();
}
}
}
@Override
protected void contentsCreated() {
super.contentsCreated();
// select the first conflict
getNavigator().selectChange(true);
}
@Override
protected void handleDispose() {
super.handleDispose();
// we do NOT dispose the images, as these are shared
}
private IDiffContainer buildDiffContainer(RevCommit baseCommit,
RevCommit compareCommit, IProgressMonitor monitor)
throws IOException, InterruptedException {
boolean useIndex = compareVersion.equals(CompareTreeView.INDEX_VERSION);
boolean checkIgnored = false;
IDiffContainer result = new DiffNode(Differencer.CONFLICTING);
try (TreeWalk tw = new TreeWalk(repository)) {
// filter by selected resources
if (filterPathStrings.size() > 1) {
List<TreeFilter> suffixFilters = new ArrayList<>();
for (String filterPath : filterPathStrings)
suffixFilters.add(PathFilter.create(filterPath));
TreeFilter otf = OrTreeFilter.create(suffixFilters);
tw.setFilter(otf);
} else if (filterPathStrings.size() > 0) {
String path = filterPathStrings.get(0);
if (path.length() != 0)
tw.setFilter(PathFilter.create(path));
}
tw.setRecursive(true);
int baseTreeIndex;
if (baseCommit == null) {
// compare workspace with something
checkIgnored = true;
baseTreeIndex = tw.addTree(new FileTreeIterator(repository));
} else
baseTreeIndex = tw.addTree(new CanonicalTreeParser(null,
repository.newObjectReader(), baseCommit.getTree()));
int compareTreeIndex;
if (!useIndex)
compareTreeIndex = tw.addTree(new CanonicalTreeParser(null,
repository.newObjectReader(), compareCommit.getTree()));
else
// compare something with the index
compareTreeIndex = tw.addTree(new DirCacheIterator(repository
.readDirCache()));
while (tw.next()) {
if (monitor.isCanceled())
throw new InterruptedException();
AbstractTreeIterator compareVersionIterator = tw.getTree(
compareTreeIndex, AbstractTreeIterator.class);
AbstractTreeIterator baseVersionIterator = tw.getTree(
baseTreeIndex, AbstractTreeIterator.class);
if (checkIgnored
&& baseVersionIterator != null
&& ((WorkingTreeIterator) baseVersionIterator)
.isEntryIgnored())
continue;
if (compareVersionIterator != null
&& baseVersionIterator != null) {
boolean equalContent = compareVersionIterator
.getEntryObjectId().equals(
baseVersionIterator.getEntryObjectId());
if (equalContent)
continue;
}
String encoding = null;
GitFileRevision compareRev = null;
if (compareVersionIterator != null) {
String entryPath = compareVersionIterator.getEntryPathString();
encoding = CompareCoreUtils.getResourceEncoding(repository, entryPath);
if (!useIndex)
compareRev = GitFileRevision.inCommit(repository,
compareCommit, entryPath,
tw.getObjectId(compareTreeIndex));
else
compareRev = GitFileRevision.inIndex(repository,
entryPath);
}
GitFileRevision baseRev = null;
if (baseVersionIterator != null) {
String entryPath = baseVersionIterator.getEntryPathString();
if (encoding == null) {
encoding = CompareCoreUtils.getResourceEncoding(repository, entryPath);
}
baseRev = GitFileRevision.inCommit(repository, baseCommit,
entryPath, tw.getObjectId(baseTreeIndex));
}
if (compareVersionIterator != null
&& baseVersionIterator != null) {
monitor.setTaskName(baseVersionIterator
.getEntryPathString());
// content exists on both sides
add(result, baseVersionIterator.getEntryPathString(),
new DiffNode(new FileRevisionTypedElement(compareRev, encoding),
new FileRevisionTypedElement(baseRev, encoding)));
} else if (baseVersionIterator != null
&& compareVersionIterator == null) {
monitor.setTaskName(baseVersionIterator
.getEntryPathString());
// only on base side
add(result, baseVersionIterator.getEntryPathString(),
new DiffNode(Differencer.DELETION | Differencer.RIGHT, null, null,
new FileRevisionTypedElement(baseRev, encoding)));
} else if (compareVersionIterator != null
&& baseVersionIterator == null) {
monitor.setTaskName(compareVersionIterator
.getEntryPathString());
// only on compare side
add(result, compareVersionIterator.getEntryPathString(),
new DiffNode(Differencer.ADDITION | Differencer.RIGHT, null,
new FileRevisionTypedElement(compareRev, encoding), null));
}
if (monitor.isCanceled())
throw new InterruptedException();
}
return result;
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((baseVersion == null) ? 0 : baseVersion.hashCode());
result = prime * result
+ ((compareVersion == null) ? 0 : compareVersion.hashCode());
result = prime
* result
+ ((repository == null) ? 0 : repository.getDirectory()
.hashCode());
result = prime * result + Arrays.hashCode(resources);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
GitCompareEditorInput other = (GitCompareEditorInput) obj;
if (baseVersion == null) {
if (other.baseVersion != null)
return false;
} else if (!baseVersion.equals(other.baseVersion))
return false;
if (compareVersion == null) {
if (other.compareVersion != null)
return false;
} else if (!compareVersion.equals(other.compareVersion))
return false;
if (repository == null) {
if (other.repository != null)
return false;
} else if (other.repository == null || !repository.getDirectory().equals(
other.repository.getDirectory()))
return false;
if (!Arrays.equals(resources, other.resources))
return false;
return true;
}
private void add(IDiffContainer result, String filePath, DiffNode diffNode) {
IDiffContainer container = getFileParent(result, filePath);
container.add(diffNode);
diffNode.setParent(container);
}
private IDiffContainer getFileParent(IDiffContainer root, String filePath) {
IPath path = new Path(filePath);
IDiffContainer child = root;
if (diffRoots.isEmpty()) {
for (int i = 0; i < path.segmentCount() - 1; i++)
child = getOrCreateChild(child, path.segment(i));
return child;
} else {
for (Entry<IPath, IDiffContainer> entry : diffRoots.entrySet()) {
if (entry.getKey().isPrefixOf(path)) {
for (int i = entry.getKey().segmentCount(); i < path
.segmentCount() - 1; i++)
child = getOrCreateChild(child, path.segment(i));
return child;
}
}
return null;
}
}
private DiffNode getOrCreateChild(IDiffContainer parent, final String name) {
for (IDiffElement child : parent.getChildren()) {
if (child.getName().equals(name)) {
return ((DiffNode) child);
}
}
DiffNode child = new DiffNode(parent, Differencer.NO_CHANGE) {
@Override
public String getName() {
return name;
}
@Override
public Image getImage() {
return FOLDER_IMAGE;
}
};
return child;
}
private IResource[] convertResourceInput(final IResource[] input) {
if (input.length > 0) {
// we must make sure to only show the topmost resources as roots
List<IResource> resourceList = new ArrayList<>(
input.length);
List<IPath> allPaths = new ArrayList<>(input.length);
for (IResource originalInput : input) {
allPaths.add(originalInput.getFullPath());
}
for (IResource originalInput : input) {
boolean skip = false;
for (IPath path : allPaths) {
if (path.isPrefixOf(originalInput.getFullPath())
&& path.segmentCount() < originalInput
.getFullPath().segmentCount()) {
skip = true;
break;
}
}
if (!skip)
resourceList.add(originalInput);
}
return resourceList.toArray(new IResource[resourceList.size()]);
} else
return input;
}
}