package org.lrg.outcode.eclipse.handlers;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
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.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CheckoutResult;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.diff.DiffConfig;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryBuilder;
import org.eclipse.jgit.revwalk.FollowFilter;
import org.eclipse.jgit.revwalk.RenameCallback;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.lrg.outcode.CountdownTimer;
import org.lrg.outcode.builder.ModelVistor;
import org.lrg.outcode.builder.db.GraphDatasource;
import org.outcode.git.Giterator.NullProgressMonitorExtension;
public class CurrentFileEvolution extends AbstractHandler {
private final RenameTracker renameTracker = new RenameTracker();
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
ISelection selection = activePage.getSelection();
IJavaElement elem = null;
if (selection instanceof ITextSelection) {
elem = JavaUI.getEditorInputJavaElement(activePage.getActiveEditor().getEditorInput());
if (elem instanceof ICompilationUnit) {
final ICompilationUnit unit = (ICompilationUnit) elem;
File gitDir = findRepoDir(unit);
if (gitDir == null)
return null;
RepositoryBuilder builder = new RepositoryBuilder();
Repository repository;
ModelVistor.parsedLOC = 0;
CountdownTimer.start("iterating");
try {
repository = builder.setGitDir(gitDir).readEnvironment().findGitDir().build();
final RepositoryMapping map = RepositoryMapping.getMapping(unit.getResource());
repository = map.getRepository();
RevWalk walk = new RevWalk(repository);
walk.sort(RevSort.COMMIT_TIME_DESC, true);
walk.sort(RevSort.BOUNDARY, true);
AnyObjectId headId = resolveHead(repository, true);
if (headId == null) {
walk.close();
return null;
}
FilterPath path = buildFilterPaths(unit.getResource(), repository);
walk.markStart(walk.parseCommit(headId));
final RevWalk fileWalker = createFileWalker(walk, repository, path);
RevCommit next2 = fileWalker.next();
ArrayList<RevCommit> revCommits = new ArrayList<RevCommit>();
while (next2 != null) {
System.out.println("commit time " + next2.getCommitTime() + " commit " + next2.name());
revCommits.add(next2);
next2 = fileWalker.next();
}
Job j = new Job("j") {
@Override
protected IStatus run(IProgressMonitor monitor) {
extractModelToDb(monitor, revCommits, map.getRepository(), unit);
try {
new EntityAccessesEvolution().execute(unit);
} catch (ExecutionException e) {
e.printStackTrace();
}
return Status.OK_STATUS;
}
};
j.schedule();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
private void refreshWs() throws CoreException {
NullProgressMonitorExtension nullProgressMonitorExtension = new NullProgressMonitorExtension();
ResourcesPlugin.getWorkspace().getRoot().refreshLocal(IResource.DEPTH_INFINITE, nullProgressMonitorExtension);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (!nullProgressMonitorExtension.isDone()) {
System.out.println("sleeping a sec");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void extractModelToDb(IProgressMonitor moni, ArrayList<RevCommit> revCommits, Repository repository, ICompilationUnit unit) {
File gitDir = repository.getDirectory();
SubMonitor monitor = SubMonitor.convert(moni, revCommits.size());
RevCommit commit = null;
Git git = new Git(repository);
RevWalk walk = new RevWalk(repository);
int k = 0;
int noK = 0;
for (RevCommit revCommit : revCommits) {
try {
CountdownTimer.start("parseCommit");
commit = walk.parseCommit(revCommit);
CountdownTimer.stop("parseCommit");
System.out.println("parsing commit with message " + revCommit.getName() + " " + revCommit.getFullMessage() + new Date(revCommit.getCommitTime()).toString());
String path = renameTracker.getPath(commit, unit.getResource().getFullPath().toPortableString());
if (GraphDatasource.INSTANCE.createdNewCommitNode(commit.getName() + path)) {
CheckoutCommand checkout = git.checkout();
try {
CountdownTimer.start("call");
checkout.setName(commit.getName()).call();
CountdownTimer.stop("call");
CheckoutResult result = checkout.getResult();
IProject javaProject = unit.getResource().getProject();
if (result.getStatus() == CheckoutResult.Status.OK) {
refreshWs();
if (k == 0) {
RevTree tree = commit.getTree();
System.out.println("modified " + tree);
System.out.println("commit name " + commit.getName() + " " + commit.getId().name());
new ModelVistor(commit.getCommitTime(), commit.getName(), repository).visitIJavaProject(javaProject, path);
} else {
if (commit.getParentCount() > 0) {
RevCommit parent = walk.parseCommit(commit.getParent(0).getId());
ByteArrayOutputStream out = new ByteArrayOutputStream();
DiffFormatter df = new DiffFormatter(out);
df.setRepository(repository);
df.setDiffComparator(RawTextComparator.WS_IGNORE_ALL);
df.setDetectRenames(true);
List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree());
df.close();
String version = commit.getCommitTime() + "000";
System.out.println("------- version " + new Date(Long.parseLong(version)).toString() + " -------");
String absolutepath = gitDir.getAbsolutePath().replace("/.git", "/");
new ModelVistor(commit.getCommitTime(), commit.getName(), repository).visitIJavaProject(javaProject, absolutepath, diffs, version, commit.getName(), path);
}
}
k++;
} else {
System.err.println("checkout failed " + result.getStatus());
}
} catch (Exception ex) {
CountdownTimer.stop("call");
ex.printStackTrace();
}
} else
noK++;
if (monitor.isCanceled())
break;
monitor.worked(1);
} catch (Exception e) {
e.printStackTrace();
}
git.close();
monitor.done();
}
walk.close();
}
private RevWalk createFileWalker(RevWalk walk, Repository db, FilterPath paths) {
TreeFilter followFilter = createFollowFilterFor(db, paths.getPath());
walk.setTreeFilter(followFilter);
walk.sort(RevSort.REVERSE);
walk.setRevFilter(renameTracker.getFilter());
return walk;
}
/**
* Creates a filter for the given files, will make sure that renames/copies of all files will be followed.
*
* @param paths
* the list of files to follow, must not be <code>null</code> or empty
* @return the ORed list of {@link FollowFilter follow filters}
*/
private TreeFilter createFollowFilterFor(Repository db, String paths) {
if (paths == null || paths.isEmpty())
throw new IllegalArgumentException("paths must not be null nor empty"); //$NON-NLS-1$
DiffConfig diffConfig = db.getConfig().get(DiffConfig.KEY);
return createFollowFilter(paths, diffConfig);
}
private FollowFilter createFollowFilter(String path, DiffConfig diffConfig) {
FollowFilter followFilter = FollowFilter.create(path, diffConfig);
followFilter.setRenameCallback(new RenameCallback() {
@Override
public void renamed(DiffEntry entry) {
renameTracker.getCallback().renamed(entry);
}
});
return followFilter;
}
private FilterPath buildFilterPaths(final IResource inResource, final Repository db) throws IllegalStateException {
if (inResource != null) {
final RepositoryMapping map = RepositoryMapping.getMapping(inResource);
if (map != null) {
if (db != map.getRepository())
throw new IllegalStateException(UIText.RepositoryAction_multiRepoSelection);
final String path = map.getRepoRelativePath(inResource);
if (path != null && path.length() > 0)
return new FilterPath(path, inResource.getType() == IResource.FILE);
}
}
return null;
}
private AnyObjectId resolveHead(Repository db, boolean acceptNull) {
AnyObjectId headId;
try {
headId = db.resolve(Constants.HEAD);
} catch (IOException e) {
throw new IllegalStateException(e);
}
if (headId == null && !acceptNull)
throw new IllegalStateException("error parsing head");
return headId;
}
private File findRepoDir(ICompilationUnit unit) {
IProject iProject = unit.getJavaProject().getProject();
IPath location = iProject.getLocation();
if (location.append("/.git").toFile().exists())
return location.append(".git").toFile();
while (location.segmentCount() > 0 && !location.append("/.git").toFile().exists())
location = location.removeLastSegments(1);
if (location.append("/.git").toFile().exists()) {
IPath finalLocation = location.append("/.git");
System.out.println("repo found " + finalLocation.toPortableString());
return finalLocation.toFile();
}
return null;
}
/**
* This class defines a couple that associates two pieces of information: the file path, and whether it is a regular file (or a directory).
*/
private static class FilterPath {
private String path;
private boolean regularFile;
public FilterPath(String path, boolean regularFile) {
super();
this.path = path;
this.regularFile = regularFile;
}
/** @return the file path */
public String getPath() {
return path;
}
/**
* @return <code>true</code> if the file is a regular file, and <code>false</code> otherwise (directory, project)
*/
public boolean isRegularFile() {
return regularFile;
}
/**
* In {@link FilterPath} class, equality is based on {@link #getPath path} equality.
*/
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof FilterPath))
return false;
FilterPath other = (FilterPath) obj;
if (path == null)
return other.path == null;
return path.equals(other.path);
}
@Override
public int hashCode() {
if (path != null)
return path.hashCode();
return super.hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("Path: "); //$NON-NLS-1$
builder.append(getPath());
builder.append("regular: "); //$NON-NLS-1$
builder.append(isRegularFile());
return builder.toString();
}
}
}