| /******************************************************************************* |
| * Copyright (c) 2011 Research Group for Industrial Software (INSO), Vienna University of Technology |
| * 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 |
| * |
| * Contributors: |
| * Kilian Matt (Research Group for Industrial Software (INSO), Vienna University of Technology) - initial API and implementation |
| * Alvaro Sanchez-Leon - Resolve IResource information in generated artifacts |
| * Tasktop Technologies - Improvements |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.internal.git.core; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.filesystem.URIUtil; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.egit.core.GitProvider; |
| import org.eclipse.egit.core.RepositoryCache; |
| import org.eclipse.egit.core.project.RepositoryMapping; |
| import org.eclipse.jgit.api.Git; |
| import org.eclipse.jgit.diff.DiffEntry; |
| import org.eclipse.jgit.errors.AmbiguousObjectException; |
| import org.eclipse.jgit.errors.CorruptObjectException; |
| import org.eclipse.jgit.errors.IncorrectObjectTypeException; |
| import org.eclipse.jgit.errors.MissingObjectException; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.revwalk.RevTree; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.eclipse.jgit.treewalk.TreeWalk; |
| import org.eclipse.mylyn.versions.core.Change; |
| import org.eclipse.mylyn.versions.core.ChangeSet; |
| import org.eclipse.mylyn.versions.core.ChangeType; |
| import org.eclipse.mylyn.versions.core.ScmArtifact; |
| import org.eclipse.mylyn.versions.core.ScmRepository; |
| import org.eclipse.mylyn.versions.core.spi.ScmConnector; |
| import org.eclipse.mylyn.versions.core.spi.ScmResourceArtifact; |
| import org.eclipse.mylyn.versions.core.spi.ScmResourceUtils; |
| import org.eclipse.team.core.history.IFileRevision; |
| |
| import com.google.common.base.Function; |
| import com.google.common.collect.Iterators; |
| import com.google.common.collect.Lists; |
| |
| /** |
| * @author Kilian Matt |
| * @author Steffen Pingel |
| */ |
| public class GitConnector extends ScmConnector { |
| |
| static String PLUGIN_ID = "org.eclipse.mylyn.git.core"; //$NON-NLS-1$ |
| |
| @Override |
| public String getProviderId() { |
| return GitProvider.class.getName(); |
| } |
| |
| @Override |
| public ScmArtifact getArtifact(IResource resource) throws CoreException { |
| if (resource == null) { |
| return null; |
| } |
| |
| Repository repository; |
| RepositoryMapping m = RepositoryMapping.getMapping(resource); |
| try { |
| repository = getRepositoryCache().lookupRepository(m.getGitDirAbsolutePath().toFile()); |
| } catch (IOException e) { |
| // failed to resolve id |
| return null; |
| } |
| |
| //resolve revision associated to head |
| String resRepoRelPath = resolveRepoRelativePath(repository, resource); |
| String revision = null; |
| try { |
| revision = resolveObject(repository, resRepoRelPath); |
| } catch (Exception e) { |
| //Not able to resolve revision |
| } |
| |
| //Avoiding ScmResourceArtifact, see Bug 341733 |
| ScmArtifact artifact = null; |
| if (revision != null) { |
| GitRepository repo = getRepository(resource); |
| artifact = new GitArtifact(revision, resRepoRelPath, repo); |
| artifact.setProjectName(resource.getProject().getName()); |
| artifact.setProjectRelativePath(resource.getProjectRelativePath().toPortableString()); |
| } else { |
| artifact = getArtifact(resource, null); |
| } |
| |
| return artifact; |
| } |
| |
| @Override |
| public ScmArtifact getArtifact(IResource resource, String revision) throws CoreException { |
| return new ScmResourceArtifact(this, resource, revision); |
| } |
| |
| @Override |
| public ChangeSet getChangeSet(ScmRepository repository, IFileRevision revision, IProgressMonitor monitor) |
| throws CoreException { |
| GitRepository gitRepository = (GitRepository) repository; |
| Repository repository2 = gitRepository.getRepository(); |
| RevWalk walk = new RevWalk(repository2); |
| try { |
| RevCommit commit; |
| commit = walk.parseCommit(ObjectId.fromString(revision.getContentIdentifier())); |
| //diffCommit(repository, repository2, walk, commit); |
| return changeSet(commit, gitRepository); |
| } catch (Exception e) { |
| throw new CoreException(new Status(IStatus.ERROR, GitConnector.PLUGIN_ID, e.getMessage())); |
| } |
| } |
| |
| List<Change> diffCommit(ScmRepository repository, Repository repository2, RevWalk walk, RevCommit commit) |
| throws MissingObjectException, IOException, IncorrectObjectTypeException, CorruptObjectException { |
| |
| TreeWalk treeWalk = new TreeWalk(repository2); |
| for (RevCommit p : commit.getParents()) { |
| walk.parseHeaders(p); |
| walk.parseBody(p); |
| treeWalk.addTree(p.getTree()); |
| //we can compare with one parent only |
| break; |
| } |
| if (treeWalk.getTreeCount() == 0) { |
| //No parents found e.g. initial commit |
| //comparing against the same commit will flag all file entries as additions |
| treeWalk.addTree(commit.getTree()); |
| } |
| |
| treeWalk.addTree(commit.getTree()); |
| treeWalk.setRecursive(true); |
| |
| List<Change> changes = new ArrayList<Change>(); |
| List<DiffEntry> entries = DiffEntry.scan(treeWalk); |
| File repoDir = repository2.getWorkTree().getAbsoluteFile(); |
| |
| //define working area repo URI |
| IPath repoWorkAreaPath = new Path(repoDir.getAbsolutePath()).addTrailingSeparator(); |
| |
| for (DiffEntry d : entries) { |
| // FIXME - could not work for renaming |
| if (!d.getChangeType().equals(org.eclipse.jgit.diff.DiffEntry.ChangeType.RENAME) |
| && d.getOldId().equals(d.getNewId())) { |
| continue; |
| } |
| |
| //Create old and new artifacts with IResource information if available from the current workspace |
| ScmArtifact newArtifact = getArtifact(repository, d, false, repoWorkAreaPath); |
| ScmArtifact oldArtifact = getArtifact(repository, d, true, repoWorkAreaPath); |
| |
| changes.add(new Change(oldArtifact, newArtifact, mapChangeType(d.getChangeType()))); |
| } |
| return changes; |
| } |
| |
| /** |
| * @param repository |
| * @param d |
| * @param old |
| * - true => old entry, false => new entry |
| * @param repoWorkAreaPath |
| * @return |
| */ |
| private ScmArtifact getArtifact(ScmRepository repository, DiffEntry d, boolean old, IPath repoWorkAreaPath) { |
| ScmArtifact artifact = null; |
| String id = null; |
| String path = null; |
| |
| if (old) { |
| id = d.getOldId().name(); |
| path = d.getOldPath(); |
| } else { /* new */ |
| id = d.getNewId().name(); |
| path = d.getNewPath(); |
| } |
| |
| GitRepository gitRepository = (GitRepository) repository; |
| artifact = new GitArtifact(id, path, gitRepository); |
| |
| //Resolve path to workspace IFile |
| IFile ifile = null; |
| |
| //resolve absolute path to artifact i.e. repo abs path + relative to resource |
| IPath absPath = repoWorkAreaPath.append(path); |
| URI absURI = URIUtil.toURI(absPath); |
| IFile[] files = ScmResourceUtils.getWorkSpaceFiles(absURI); |
| if (files != null && files.length > 0) { |
| ifile = files[0]; |
| if (files.length > 1) { |
| //if more than one project is referring to the same file, pick the one selected / associated to the main project |
| //i.e. the one related to the creation of the GitRepository instance. |
| IProject mainProject = gitRepository.getMainWsProject(); |
| if (mainProject != null) { |
| for (IFile dfile : files) { |
| String fileProjectName = dfile.getProject().getName(); |
| if (mainProject.getName().equals(fileProjectName)) { |
| ifile = dfile; |
| break; |
| } |
| } |
| } else { |
| //There is no valid main project associated. Select one where the project folder is the root of the file (if any) |
| for (IFile file : files) { |
| IPath projPath = file.getProject().getLocation(); |
| if (projPath.isPrefixOf(absPath)) { |
| //This is the root project for the file i.e. not linked |
| ifile = file; |
| break; |
| } |
| } |
| } |
| } |
| |
| //Fill in the artifact with corresponding IResource information |
| artifact.setProjectName(ifile.getProject().getName()); |
| artifact.setProjectRelativePath(ifile.getProjectRelativePath().toPortableString()); |
| } |
| |
| return artifact; |
| } |
| |
| private ChangeType mapChangeType(org.eclipse.jgit.diff.DiffEntry.ChangeType change) { |
| switch (change) { |
| case ADD: |
| case COPY: |
| return ChangeType.ADDED; |
| case DELETE: |
| return ChangeType.DELETED; |
| case MODIFY: |
| return ChangeType.MODIFIED; |
| case RENAME: |
| return ChangeType.REPLACED; |
| } |
| return null; |
| } |
| |
| @Override |
| public List<ChangeSet> getChangeSets(final ScmRepository repository, final IProgressMonitor monitor) |
| throws CoreException { |
| return Lists.newArrayList(getChangeSetsIterator(repository, monitor)); |
| } |
| |
| @Override |
| public Iterator<ChangeSet> getChangeSetsIterator(ScmRepository repository, IProgressMonitor monitor) { |
| |
| final GitRepository gitRepository = (GitRepository) repository; |
| final Repository gitRepo = gitRepository.getRepository(); |
| Git git = new Git(gitRepo); |
| Iterable<RevCommit> revs; |
| try { |
| revs = git.log().call(); |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| |
| return Iterators.transform(revs.iterator(), new Function<RevCommit, ChangeSet>() { |
| public ChangeSet apply(RevCommit input) { |
| return changeSet(input, gitRepository); |
| } |
| }); |
| } |
| |
| private ChangeSet changeSet(RevCommit r, GitRepository repository) { |
| |
| ChangeSet changeSet = new LazyChangeSet(r, repository); |
| return changeSet; |
| } |
| |
| @Override |
| public List<ScmRepository> getRepositories(IProgressMonitor monitor) throws CoreException { |
| ArrayList<ScmRepository> repos = new ArrayList<ScmRepository>(); |
| for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { |
| GitRepository repository = this.getRepository(project, monitor); |
| if (repository != null) { |
| repos.add(repository); |
| } |
| } |
| return repos; |
| } |
| |
| @Override |
| public GitRepository getRepository(IResource resource, IProgressMonitor monitor) throws CoreException { |
| return getRepository(resource); |
| } |
| |
| public GitRepository getRepository(IResource resource) { |
| RepositoryMapping mapping = RepositoryMapping.getMapping(resource); |
| if (mapping == null) { |
| return null; |
| } |
| |
| return new GitRepository(this, mapping, resource.getProject()); |
| } |
| |
| protected RepositoryCache getRepositoryCache() { |
| return org.eclipse.egit.core.Activator.getDefault().getRepositoryCache(); |
| } |
| |
| private String resolveRepoRelativePath(Repository repo, IResource resource) { |
| if (repo == null || resource == null) { |
| return null; |
| } |
| |
| File workTree = repo.getWorkTree(); |
| IPath workTreePath = Path.fromOSString(workTree.getAbsolutePath()); |
| if (workTreePath.isPrefixOf(resource.getProject().getLocation())) { |
| IPath makeRelativeTo = resource.getLocation().makeRelativeTo(workTreePath); |
| String repoRelativePath = makeRelativeTo.toPortableString(); |
| return repoRelativePath; |
| } |
| return null; |
| } |
| |
| private String resolveObject(Repository repo, String repoRelativePath) throws AmbiguousObjectException, IOException { |
| //Validate |
| if (repo == null || repoRelativePath == null) { |
| return null; |
| } |
| |
| ObjectId headCommitId = repo.resolve(Constants.HEAD); |
| String id = null; |
| if (headCommitId != null) { |
| // Not an empty repo |
| RevWalk revWalk = new RevWalk(repo); |
| RevCommit headCommit = revWalk.parseCommit(headCommitId); |
| RevTree headTree = headCommit.getTree(); |
| TreeWalk resourceInRepo = TreeWalk.forPath(repo, repoRelativePath, headTree); |
| if (resourceInRepo != null) { |
| ObjectId objId = resourceInRepo.getObjectId(0); |
| id = objId.getName(); |
| } |
| revWalk.dispose(); |
| } |
| |
| return id; |
| } |
| |
| } |