/*******************************************************************************
* Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2013, Laurent Goubet <laurent.goubet@obeo.fr>
* Copyright (C) 2015, IBM Corporation (Dani Megert <daniel_megert@ch.ibm.com>)
*
* 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.internal.storage;
import java.io.IOException;
import java.util.Collections;
import java.util.Map.Entry;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.internal.CoreText;
import org.eclipse.egit.core.internal.Utils;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.core.synchronize.GitRemoteResource;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.history.IFileHistoryProvider;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.core.history.provider.FileHistory;
import org.eclipse.team.core.variants.IResourceVariant;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
/**
* A list of revisions for a specific resource according to some filtering
* criterion. Though git really does not do file tracking, this corresponds to
* listing all files with the same path.
*/
class GitFileHistory extends FileHistory implements IAdaptable {
private static final IFileRevision[] NO_REVISIONS = {};
private static final int BATCH_SIZE = 256;
private final IResource resource;
private String gitPath;
private final Repository db;
private final RevWalk walk;
private final IFileRevision[] revisions;
GitFileHistory(final IResource rsrc, final int flags,
final IProgressMonitor monitor) {
resource = rsrc;
final RepositoryMapping rm = RepositoryMapping.getMapping(resource);
if (rm == null) {
IProject project = resource.getProject();
String projectName = project != null ? project.getName() : ""; //$NON-NLS-1$
Activator.logError(NLS.bind(CoreText.GitFileHistory_gitNotAttached,
projectName), null);
db = null;
walk = null;
} else {
db = rm.getRepository();
walk = new KidWalk(db);
gitPath = rm.getRepoRelativePath(resource);
if (gitPath == null || gitPath.length() == 0) {
walk.setTreeFilter(TreeFilter.ANY_DIFF);
} else {
walk.setTreeFilter(AndTreeFilter.create(PathFilterGroup
.createFromStrings(Collections.singleton(gitPath)),
TreeFilter.ANY_DIFF));
}
}
revisions = buildRevisions(monitor, flags);
}
private IFileRevision[] buildRevisions(final IProgressMonitor monitor,
final int flags) {
if (walk == null)
return NO_REVISIONS;
final RevCommit root;
try {
final AnyObjectId headId = db.resolve(Constants.HEAD);
if (headId == null) {
IProject project = resource.getProject();
String projectName = project != null? project.getName() : ""; //$NON-NLS-1$
Activator.logError(NLS.bind(
CoreText.GitFileHistory_noHeadRevisionAvailable,
projectName), null);
return NO_REVISIONS;
}
root = walk.parseCommit(headId);
if ((flags & IFileHistoryProvider.SINGLE_REVISION) != 0) {
// If all Eclipse wants is one revision it probably is
// for the editor "quick diff" feature. We can pass off
// just the repository HEAD, even though it may not be
// the revision that most recently modified the path.
//
final CommitFileRevision single;
single = new CommitFileRevision(db, root, gitPath);
return new IFileRevision[] { single };
}
markStartAllRefs(walk, Constants.R_HEADS);
markStartAllRefs(walk, Constants.R_REMOTES);
markStartAllRefs(walk, Constants.R_TAGS);
walk.markStart(root);
} catch (IOException e) {
IProject project = resource.getProject();
String projectName = project != null? project.getName() : ""; //$NON-NLS-1$
Activator.logError(NLS.bind(
CoreText.GitFileHistory_invalidHeadRevision, projectName), e);
return NO_REVISIONS;
}
final KidCommitList list = new KidCommitList();
list.source(walk);
try {
for (;;) {
final int oldsz = list.size();
list.fillTo(oldsz + BATCH_SIZE - 1);
if (oldsz == list.size())
break;
if (monitor != null && monitor.isCanceled())
break;
}
} catch (IOException e) {
Activator.logError(NLS.bind(
CoreText.GitFileHistory_errorParsingHistory, resource
.getFullPath()), e);
return NO_REVISIONS;
}
final IFileRevision[] r = new IFileRevision[list.size()];
for (int i = 0; i < r.length; i++)
r[i] = new CommitFileRevision(db, list.get(i), gitPath);
return r;
}
private void markStartAllRefs(RevWalk theWalk, String prefix)
throws IOException, MissingObjectException,
IncorrectObjectTypeException {
for (Entry<String, Ref> refEntry : db.getRefDatabase().getRefs(prefix)
.entrySet()) {
Ref ref = refEntry.getValue();
if (ref.isSymbolic())
continue;
markStartRef(theWalk, ref);
}
}
private void markStartRef(RevWalk theWalk, Ref ref) throws IOException,
IncorrectObjectTypeException {
try {
Object refTarget = theWalk.parseAny(ref.getLeaf().getObjectId());
if (refTarget instanceof RevCommit)
theWalk.markStart((RevCommit) refTarget);
} catch (MissingObjectException e) {
// If there is a ref which points to Nirvana then we should simply
// ignore this ref. We should not let a corrupt ref cause that the
// history view is not filled at all
}
}
@Override
public IFileRevision[] getContributors(final IFileRevision ifr) {
String path = getGitPath(ifr);
RevCommit commit = getRevCommit(ifr);
if (path != null && commit != null) {
final IFileRevision[] r = new IFileRevision[commit.getParentCount()];
for (int i = 0; i < r.length; i++)
r[i] = new CommitFileRevision(db, commit.getParent(i), path);
return r;
}
return NO_REVISIONS;
}
@Override
public IFileRevision[] getTargets(final IFileRevision ifr) {
String path = getGitPath(ifr);
RevCommit commit = getRevCommit(ifr);
if (path != null && commit instanceof KidCommit) {
final KidCommit c = (KidCommit) commit;
final IFileRevision[] r = new IFileRevision[c.children.length];
for (int i = 0; i < r.length; i++)
r[i] = new CommitFileRevision(db, c.children[i], path);
return r;
}
return NO_REVISIONS;
}
private String getGitPath(IFileRevision revision) {
if (revision instanceof CommitFileRevision)
return ((CommitFileRevision) revision).getGitPath();
else if (revision instanceof IAdaptable) {
final IResourceVariant variant = Utils.getAdapter(((IAdaptable) revision), IResourceVariant.class);
if (variant instanceof GitRemoteResource)
return ((GitRemoteResource) variant).getPath();
}
return null;
}
private RevCommit getRevCommit(IFileRevision revision) {
if (revision instanceof CommitFileRevision)
return ((CommitFileRevision) revision).getRevCommit();
else if (revision instanceof IAdaptable) {
final IResourceVariant variant = Utils.getAdapter(((IAdaptable) revision), IResourceVariant.class);
if (variant instanceof GitRemoteResource) {
final RevCommit commit = ((GitRemoteResource) variant)
.getCommitId();
try {
return walk.parseCommit(commit);
} catch (IOException e) {
Activator.logError(NLS.bind(
CoreText.GitFileHistory_invalidCommit,
commit.getName(), resource.getName()), e);
}
}
}
return null;
}
@Override
public IFileRevision getFileRevision(final String id) {
if (id == null || id.equals("") //$NON-NLS-1$
|| GitFileRevision.WORKSPACE.equals(id))
return new WorkspaceFileRevision(resource);
if (GitFileRevision.INDEX.equals(id))
return new IndexFileRevision(db, gitPath);
// Only return a revision if it was matched by this filtered history
for (IFileRevision r : revisions) {
if (r.getContentIdentifier().equals(id))
return r;
}
return null;
}
@Override
public IFileRevision[] getFileRevisions() {
final IFileRevision[] r = new IFileRevision[revisions.length];
System.arraycopy(revisions, 0, r, 0, r.length);
return r;
}
@Override
public Object getAdapter(Class adapter) {
return null;
}
}