blob: 39241b3660f8dd604c3377e6b82c2726cadf75e7 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package git4idea.annotate;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.FilePathImpl;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.annotate.*;
import com.intellij.openapi.vcs.history.VcsAbstractHistorySession;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.vcsUtil.VcsUtil;
import git4idea.GitFileRevision;
import git4idea.GitRevisionNumber;
import git4idea.GitUtil;
import git4idea.commands.GitCommand;
import git4idea.commands.GitSimpleHandler;
import git4idea.util.StringScanner;
import git4idea.history.GitHistoryUtils;
import git4idea.i18n.GitBundle;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GitAnnotationProvider implements AnnotationProvider, VcsCacheableAnnotationProvider {
private final Project myProject;
@NonNls private static final String AUTHOR_KEY = "author";
@NonNls private static final String COMMITTER_TIME_KEY = "committer-time";
private static final Logger LOG = Logger.getInstance(GitAnnotationProvider.class);
public GitAnnotationProvider(@NotNull Project project) {
myProject = project;
}
public FileAnnotation annotate(@NotNull VirtualFile file) throws VcsException {
return annotate(file, null);
}
public FileAnnotation annotate(@NotNull final VirtualFile file, final VcsFileRevision revision) throws VcsException {
if (file.isDirectory()) {
throw new VcsException("Cannot annotate a directory");
}
final FileAnnotation[] annotation = new FileAnnotation[1];
final Exception[] exception = new Exception[1];
Runnable command = new Runnable() {
public void run() {
final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
try {
final FilePath currentFilePath = VcsUtil.getFilePath(file.getPath());
final FilePath realFilePath;
if (progress != null) {
progress.setText(GitBundle.message("getting.history", file.getName()));
}
final List<VcsFileRevision> revisions = GitHistoryUtils.history(myProject, currentFilePath);
if (revision == null) {
realFilePath = GitHistoryUtils.getLastCommitName(myProject, currentFilePath);
}
else {
realFilePath = ((GitFileRevision)revision).getPath();
}
if (progress != null) {
progress.setText(GitBundle.message("computing.annotation", file.getName()));
}
final GitFileAnnotation result = annotate(realFilePath, revision, revisions, file);
annotation[0] = result;
}
catch (Exception e) {
exception[0] = e;
}
}
};
if (ApplicationManager.getApplication().isDispatchThread()) {
ProgressManager.getInstance().runProcessWithProgressSynchronously(command, GitBundle.getString("annotate.action.name"), false,
myProject);
}
else {
command.run();
}
if (exception[0] != null) {
throw new VcsException("Failed to annotate: " + exception[0], exception[0]);
}
return annotation[0];
}
private GitFileAnnotation annotate(final FilePath repositoryFilePath,
final VcsFileRevision revision,
final List<VcsFileRevision> revisions,
final VirtualFile file) throws VcsException {
GitSimpleHandler h = new GitSimpleHandler(myProject, GitUtil.getGitRoot(repositoryFilePath), GitCommand.BLAME);
h.setStdoutSuppressed(true);
h.setCharset(file.getCharset());
h.addParameters("-p", "-l", "-t", "-w");
if (revision == null) {
h.addParameters("HEAD");
}
else {
h.addParameters(revision.getRevisionNumber().asString());
}
h.endOptions();
h.addRelativePaths(repositoryFilePath);
String output = h.run();
GitFileAnnotation annotation = new GitFileAnnotation(myProject, file, revision == null ? null : revision.getRevisionNumber());
class CommitInfo {
Date date;
String author;
GitRevisionNumber revision;
}
HashMap<String, CommitInfo> commits = new HashMap<String, CommitInfo>();
for (StringScanner s = new StringScanner(output); s.hasMoreData();) {
// parse header line
String commitHash = s.spaceToken();
if (commitHash.equals(GitRevisionNumber.NOT_COMMITTED_HASH)) {
commitHash = null;
}
s.spaceToken(); // skip revision line number
String s1 = s.spaceToken();
int lineNum = Integer.parseInt(s1);
s.nextLine();
// parse commit information
CommitInfo commit = commits.get(commitHash);
if (commit != null) {
while (s.hasMoreData() && !s.startsWith('\t')) {
s.nextLine();
}
}
else {
commit = new CommitInfo();
while (s.hasMoreData() && !s.startsWith('\t')) {
String key = s.spaceToken();
String value = s.line();
if (commitHash != null && AUTHOR_KEY.equals(key)) {
commit.author = value;
}
if (commitHash != null && COMMITTER_TIME_KEY.equals(key)) {
commit.date = GitUtil.parseTimestampWithNFEReport(value, h, output);
commit.revision = new GitRevisionNumber(commitHash, commit.date);
}
}
commits.put(commitHash, commit);
}
// parse line
if (!s.hasMoreData()) {
// if the file is empty, the next line will not start with tab and it will be
// empty.
continue;
}
s.skipChars(1);
String line = s.line(true);
annotation.appendLineInfo(commit.date, commit.revision, commit.author, line, lineNum);
}
annotation.addLogEntries(revisions);
return annotation;
}
@Override
public VcsAnnotation createCacheable(FileAnnotation fileAnnotation) {
final GitFileAnnotation gitFileAnnotation = (GitFileAnnotation) fileAnnotation;
final int size = gitFileAnnotation.getNumLines();
final VcsUsualLineAnnotationData basicData = new VcsUsualLineAnnotationData(size);
for (int i = 0; i < size; i++) {
basicData.put(i, gitFileAnnotation.getLineRevisionNumber(i));
}
return new VcsAnnotation(new FilePathImpl(gitFileAnnotation.getFile()), basicData, null);
}
@Override
public FileAnnotation restore(VcsAnnotation vcsAnnotation,
VcsAbstractHistorySession session,
String annotatedContent,
boolean forCurrentRevision, VcsRevisionNumber revisionNumber) {
final GitFileAnnotation gitFileAnnotation =
new GitFileAnnotation(myProject, vcsAnnotation.getFilePath().getVirtualFile(), revisionNumber);
gitFileAnnotation.addLogEntries(session.getRevisionList());
final VcsLineAnnotationData basicAnnotation = vcsAnnotation.getBasicAnnotation();
final int size = basicAnnotation.getNumLines();
final Map<VcsRevisionNumber,VcsFileRevision> historyAsMap = session.getHistoryAsMap();
final List<String> lines = StringUtil.split(StringUtil.convertLineSeparators(annotatedContent), "\n", false, false);
for (int i = 0; i < size; i++) {
final VcsRevisionNumber revision = basicAnnotation.getRevision(i);
final VcsFileRevision vcsFileRevision = historyAsMap.get(revision);
if (vcsFileRevision == null) {
return null;
}
try {
gitFileAnnotation.appendLineInfo(vcsFileRevision.getRevisionDate(), (GitRevisionNumber) revision, vcsFileRevision.getAuthor(),
lines.get(i), i + 1);
}
catch (VcsException e) {
return null;
}
}
return gitFileAnnotation;
}
public boolean isAnnotationValid(VcsFileRevision rev) {
return true;
}
}