package net.certware.history.egit.handlers;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import net.certware.core.ICertWareConstants;
import net.certware.core.ui.log.CertWareLog;
import net.certware.history.egit.Activator;
import net.certware.history.egit.preferences.PreferenceConstants;
import net.certware.measurement.sco.ArtifactCommit;
import net.certware.measurement.sco.ArtifactIdentifier;
import net.certware.measurement.sco.CommitHistory;
import net.certware.measurement.sco.ScoFactory;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.egit.core.GitProvider;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.history.IFileHistory;
import org.eclipse.team.core.history.IFileHistoryProvider;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
/**
* Gather history from EGit repository and create an SCO file.
* Uses a preference to identify the destination file name.
* Presumes the input selection is a project container which has EGit repository attached.
* @author mrb
* @since 1.1
*/
@SuppressWarnings("restriction")
public class GatherHistory implements ICertWareConstants {
/** model file encoding as XML resource */
private static final String FILE_ENCODING = "UTF-8";
/**
* Process the EGit history associated with a given project.
* @param selectedProject selected project, presumably an object contribution selection
* @throws CoreException
* @throws IOException
*/
public void processHistory(IProject selectedProject,IProgressMonitor monitor) throws CoreException, IOException {
// find the repository mapping for the project
// if none found, return
RepositoryMapping repositoryMapping = RepositoryMapping.getMapping((IResource) selectedProject);
if ( repositoryMapping == null ) {
CertWareLog.logWarning(
String.format("%s %s","Missing repository for project",
selectedProject.getName()));
return;
}
// build the commit history model, load it from the tree walk
final CommitHistory commitHistory = ScoFactory.eINSTANCE.createCommitHistory();
Repository repo = repositoryMapping.getRepository();
RevWalk revWalk = new RevWalk(repo);
ObjectId headObject = repo.resolve("HEAD");
revWalk.markStart(revWalk.parseCommit(headObject));
final Set<String> repositoryPaths =
Collections.singleton(repositoryMapping.getRepoRelativePath(selectedProject));
revWalk.setTreeFilter(PathFilterGroup.createFromStrings(repositoryPaths));
for ( RevCommit commit : revWalk ) {
String commitName = commit.getName();
ArtifactCommit artifactCommit = ScoFactory.eINSTANCE.createArtifactCommit();
artifactCommit.setCommitIdentifier( commitName );
commitHistory.getCommitRecord().add(artifactCommit);
}
// revWalk.dispose();
revWalk.close();
// use the Git provider to find the file history, then converge into the model
GitProvider provider = (GitProvider)RepositoryProvider.getProvider(selectedProject);
IFileHistoryProvider fileHistoryProvider = provider.getFileHistoryProvider();
IResource[] projectMembers = selectedProject.members();
monitor.beginTask("Processing project resources", projectMembers.length);
for ( IResource resource : projectMembers ) {
processResource(resource,fileHistoryProvider,commitHistory,monitor);
monitor.worked(1);
if ( monitor.isCanceled() ) {
return;
}
}
// model complete with commit history and associated file sizes
// write the resulting model to an SCO file
// expecting preference to have no extension, so add it if necessary
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
String fileName = store.getString(PreferenceConstants.P_FILENAME_SCO);
if ( fileName.endsWith( ICertWareConstants.SCO_EXTENSION) == false ) {
fileName = fileName + '.' + ICertWareConstants.SCO_EXTENSION;
}
// fully specify the path to the new file given the container project
final String modelFile =
selectedProject.getFullPath().toPortableString() + IPath.SEPARATOR + fileName;
// create the resource in a workspace modify operation
WorkspaceModifyOperation operation =
new WorkspaceModifyOperation() {
@Override
protected void execute(IProgressMonitor progressMonitor) {
try {
// create a resource set and resource for a new file
ResourceSet resourceSet = new ResourceSetImpl();
URI fileURI = URI.createPlatformResourceURI(modelFile, true);
Resource resource = resourceSet.createResource(fileURI);
resource.getContents().add(commitHistory);
// save the contents of the resource to the file system
Map<Object, Object> options = new HashMap<Object, Object>();
options.put(XMLResource.OPTION_ENCODING, FILE_ENCODING);
resource.save(options);
}
catch (Exception e) {
CertWareLog.logError(String.format("%s %s","Saving SCO file",modelFile),e);
}
}
};
// modify the workspace
try {
operation.run(monitor);
} catch (Exception e) {
CertWareLog.logError(String.format("%s %s","Modifying workspace for",
selectedProject.getName()),e);
}
monitor.done();
}
/**
* Process a resource. For each revision find its commit and add to the new list.
* @param resource to recover historical record
* @param fileHistoryProvider
* @param commitHistory commit history records to populate
* @param monitor progress monitor
*/
private void processResource(IResource resource, IFileHistoryProvider fileHistoryProvider, CommitHistory commitHistory, IProgressMonitor monitor) {
IFileHistory fileHistory = fileHistoryProvider.getFileHistoryFor(resource,
IFileHistoryProvider.SINGLE_LINE_OF_DESCENT,
monitor);
// process file revisions
IFileRevision[] fileRevisions = fileHistory.getFileRevisions();
for ( IFileRevision fr : fileRevisions ) {
processFileRevision(fr,commitHistory,monitor);
} // revisions
}
/**
* Computes and results the line count for a file revision using its storage.
* Uses a line number reader to compute the line count.
* @param fr file revision
* @param monitor progress monitor for storage access
* @return line count or zero, exceptions caught and reported to log
*/
protected int getLineCount(IFileRevision fr, IProgressMonitor monitor) {
int lineCount = 0;
try {
IStorage storage = fr.getStorage(monitor);
InputStream is = storage.getContents();
LineNumberReader lineNumberReader = new LineNumberReader(new InputStreamReader(is));
while( lineNumberReader.ready() ) {
lineNumberReader.readLine();
lineCount++;
}
lineNumberReader.close();
is.close();
} catch (Exception e) {
CertWareLog.logError(String.format("%s %s",
"Gathering line count for revision",
fr.getName()), e);
}
return lineCount;
}
/**
* Computes and results the byte count for a file revision using its storage.
* Uses an input stream reader to count bytes read.
* @param fr file revision
* @param monitor progress monitor for storage access
* @return line count or zero, exceptions caught and reported to log
*/
protected int getByteCount(IFileRevision fr, IProgressMonitor monitor) {
int byteCount = 0;
try {
IStorage storage = fr.getStorage(monitor);
InputStream is = storage.getContents();
InputStreamReader isr = new InputStreamReader(is);
while( isr.read() != -1 ) {
byteCount++;
}
isr.close();
is.close();
} catch (Exception e) {
CertWareLog.logError(String.format("%s %s","Gathering byte count for revision",fr.getName()),e);
}
return byteCount;
}
/**
* Process file revision
* @param fr file revision
* @param ch commit history, already populated with artifact history records
* @param monitor progress monitor
*/
private void processFileRevision(IFileRevision fr, CommitHistory ch, IProgressMonitor monitor) {
String commitId = fr.getContentIdentifier();
String name = fr.getName();
int lineCount = getLineCount(fr,monitor);
// create artifact identifier and add it to the existing commit
ArtifactIdentifier ai = ScoFactory.eINSTANCE.createArtifactIdentifier();
ai.setCurrentLineCount(lineCount);
ai.setResourceName(name);
// add the resource revision identifier to the corresponding commit record
ArtifactCommit ac = findCommit(ch, commitId);
if ( ac != null ) {
ac.getArtifactIdentifiers().add(ai);
} else {
CertWareLog.logWarning(String.format("%s %s","Could not find commit for file",fr.getName()));
}
}
/**
* Find the artifact commit list associated with a commit ID.
* @param ch commit history, already populated with commit ID records
* @param id commit identifier
* @return commit matching the given ID, or null
*/
private ArtifactCommit findCommit(CommitHistory ch, String id ) {
if ( ch != null ) {
for ( ArtifactCommit ac : ch.getCommitRecord() ) {
if ( ac.getCommitIdentifier().equals(id)) {
return ac;
}
}
}
return null;
}
}