git: annotations - speedup loading

* load file history asynchronously
* cache loaded annotations
* do not store annotated content - load it on demand
* use `git blame` output as much as we can
* the only usage for file history ATM - `HistoryIdColumn`, as we can't properly order commits without it
This commit is contained in:
Aleksey Pivovarov
2016-08-30 12:23:10 +03:00
parent c9e6b15c34
commit a7c864a9f4
12 changed files with 604 additions and 224 deletions
@@ -2906,6 +2906,12 @@ public class ContainerUtil extends ContainerUtilRt {
protected Map<K, Collection<V>> createMap(int initialCapacity, float loadFactor) {
return new TreeMap<K, Collection<V>>();
}
@NotNull
public NavigableSet<K> navigableKeySet() {
//noinspection unchecked
return ((TreeMap)myMap).navigableKeySet();
}
}
@NotNull
@@ -15,6 +15,7 @@
*/
package com.intellij.openapi.vcs.annotate;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.localVcs.UpToDateLineNumberProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.VcsKey;
@@ -23,20 +24,24 @@ import com.intellij.openapi.vcs.history.VcsFileRevision;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vcs.vfs.VcsVirtualFile;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Date;
import java.util.List;
import java.util.*;
/**
* Represents annotations ("vcs blame") for some file in a specific revision
* @see AnnotationProvider
*/
public abstract class FileAnnotation {
private static final Logger LOG = Logger.getInstance(FileAnnotation.class);
@NotNull private final Project myProject;
private Runnable myCloser;
private Runnable myReloader;
protected FileAnnotation(@NotNull Project project) {
myProject = project;
@@ -174,6 +179,13 @@ public abstract class FileAnnotation {
myCloser.run();
}
/**
* Notify that annotation information has changed, and UI should be updated.
*/
public final void reload() {
if (myReloader != null) myReloader.run();
}
/**
* @see #close()
*/
@@ -181,9 +193,144 @@ public abstract class FileAnnotation {
myCloser = closer;
}
/**
* @see #reload()
*/
public final void setReloader(@Nullable Runnable reloader) {
myReloader = reloader;
}
@Deprecated
public boolean revisionsNotEmpty() {
return true;
}
@Nullable
public CurrentFileRevisionProvider getCurrentFileRevisionProvider() {
return createDefaultCurrentFileRevisionProvider(this);
}
@Nullable
public PreviousFileRevisionProvider getPreviousFileRevisionProvider() {
return createDefaultPreviousFileRevisionProvider(this);
}
@Nullable
public AuthorsMappingProvider getAuthorsMappingProvider() {
return createDefaultAuthorsMappingProvider(this);
}
@Nullable
public RevisionsOrderProvider getRevisionsOrderProvider() {
return createDefaultRevisionsOrderProvider(this);
}
public interface CurrentFileRevisionProvider {
@Nullable
VcsFileRevision getRevision(int lineNumber);
}
public interface PreviousFileRevisionProvider {
@Nullable
VcsFileRevision getPreviousRevision(int lineNumber);
@Nullable
VcsFileRevision getLastRevision();
}
public interface AuthorsMappingProvider {
@NotNull
Map<VcsRevisionNumber, String> getAuthors();
}
public interface RevisionsOrderProvider {
@NotNull
List<List<VcsRevisionNumber>> getOrderedRevisions();
}
@Nullable
private static CurrentFileRevisionProvider createDefaultCurrentFileRevisionProvider(@NotNull FileAnnotation annotation) {
List<VcsFileRevision> revisions = annotation.getRevisions();
if (revisions == null) return null;
Map<VcsRevisionNumber, VcsFileRevision> map = new HashMap<>();
for (VcsFileRevision revision : revisions) {
map.put(revision.getRevisionNumber(), revision);
}
List<VcsFileRevision> lineToRevision = new ArrayList<>(annotation.getLineCount());
for (int i = 0; i < annotation.getLineCount(); i++) {
lineToRevision.add(map.get(annotation.getLineRevisionNumber(i)));
}
return (lineNumber) -> {
LOG.assertTrue(lineNumber >= 0 && lineNumber < lineToRevision.size());
return lineToRevision.get(lineNumber);
};
}
@Nullable
private static PreviousFileRevisionProvider createDefaultPreviousFileRevisionProvider(@NotNull FileAnnotation annotation) {
List<VcsFileRevision> revisions = annotation.getRevisions();
if (revisions == null) return null;
Map<VcsRevisionNumber, VcsFileRevision> map = new HashMap<>();
for (int i = 0; i < revisions.size(); i++) {
VcsFileRevision revision = revisions.get(i);
VcsFileRevision previousRevision = i + 1 < revisions.size() ? revisions.get(i + 1) : null;
map.put(revision.getRevisionNumber(), previousRevision);
}
List<VcsFileRevision> lineToRevision = new ArrayList<>(annotation.getLineCount());
for (int i = 0; i < annotation.getLineCount(); i++) {
lineToRevision.add(map.get(annotation.getLineRevisionNumber(i)));
}
VcsFileRevision lastRevision = ContainerUtil.getFirstItem(revisions);
return new PreviousFileRevisionProvider() {
@Nullable
@Override
public VcsFileRevision getPreviousRevision(int lineNumber) {
LOG.assertTrue(lineNumber >= 0 && lineNumber < lineToRevision.size());
return lineToRevision.get(lineNumber);
}
@Nullable
@Override
public VcsFileRevision getLastRevision() {
return lastRevision;
}
};
}
@Nullable
private static AuthorsMappingProvider createDefaultAuthorsMappingProvider(@NotNull FileAnnotation annotation) {
List<VcsFileRevision> revisions = annotation.getRevisions();
if (revisions == null) return null;
Map<VcsRevisionNumber, String> authorsMapping = new HashMap<>();
for (VcsFileRevision revision : revisions) {
authorsMapping.put(revision.getRevisionNumber(), revision.getAuthor());
}
return () -> authorsMapping;
}
@Nullable
private static RevisionsOrderProvider createDefaultRevisionsOrderProvider(@NotNull FileAnnotation annotation) {
List<VcsFileRevision> revisions = annotation.getRevisions();
if (revisions == null) return null;
List<List<VcsRevisionNumber>> orderedRevisions = ContainerUtil.map(revisions, (revision) -> {
return Collections.singletonList(revision.getRevisionNumber());
});
return () -> orderedRevisions;
}
}
@@ -23,7 +23,6 @@ import com.intellij.openapi.vcs.AbstractVcs;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.actions.VcsContextFactory;
import com.intellij.openapi.vcs.annotate.AnnotationProvider;
import com.intellij.openapi.vcs.annotate.FileAnnotation;
import com.intellij.openapi.vcs.annotate.VcsAnnotation;
@@ -31,6 +30,8 @@ import com.intellij.openapi.vcs.annotate.VcsCacheableAnnotationProvider;
import com.intellij.openapi.vcs.changes.ContentRevision;
import com.intellij.openapi.vcs.diff.DiffProvider;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ObjectUtils;
import com.intellij.vcsUtil.VcsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -87,13 +88,14 @@ public class VcsAnnotationCachedProxy implements AnnotationProvider {
final ThrowableComputable<FileAnnotation, VcsException> delegate) throws VcsException {
final AnnotationProvider annotationProvider = myAnnotationProvider;
final FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(file);
final FilePath filePath = VcsUtil.getFilePath(file);
final VcsCacheableAnnotationProvider cacheableAnnotationProvider = (VcsCacheableAnnotationProvider)annotationProvider;
VcsAnnotation vcsAnnotation = null;
if (revisionNumber != null) {
vcsAnnotation = myCache.get(VcsContextFactory.SERVICE.getInstance().createFilePathOn(file), myVcs.getKeyInstanceMethod(), revisionNumber);
Object cachedData = myCache.get(filePath, myVcs.getKeyInstanceMethod(), revisionNumber);
vcsAnnotation = ObjectUtils.tryCast(cachedData, VcsAnnotation.class);
}
if (vcsAnnotation != null) {
@@ -18,7 +18,6 @@ package com.intellij.openapi.vcs.history;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsKey;
import com.intellij.openapi.vcs.annotate.VcsAnnotation;
import com.intellij.util.Consumer;
import com.intellij.util.containers.SLRUMap;
import org.jetbrains.annotations.NotNull;
@@ -37,8 +36,7 @@ import java.util.Map;
public class VcsHistoryCache {
private final Object myLock;
private final SLRUMap<HistoryCacheBaseKey, CachedHistory> myHistoryCache;
private final SLRUMap<HistoryCacheWithRevisionKey, VcsAnnotation> myAnnotationCache;
//private final SLRUMap<HistoryCacheWithRevisionKey, String> myContentCache;
private final SLRUMap<HistoryCacheWithRevisionKey, Object> myAnnotationCache;
public VcsHistoryCache() {
myLock = new Object();
@@ -50,7 +48,6 @@ public class VcsHistoryCache {
myAnnotationCache = new SLRUMap<>(
preloadEnabled ? 50 : 10,
preloadEnabled ? 50 : 5);
//myContentCache = new SLRUMap<HistoryCacheWithRevisionKey, String>(20, 20);
}
public <C extends Serializable, T extends VcsAbstractHistorySession> void put(final FilePath filePath,
@@ -114,13 +111,13 @@ public class VcsHistoryCache {
}
public void put(@NotNull final FilePath filePath, @NotNull final VcsKey vcsKey, @NotNull final VcsRevisionNumber number,
@NotNull final VcsAnnotation vcsAnnotation) {
@NotNull final Object vcsAnnotation) {
synchronized (myLock) {
myAnnotationCache.put(new HistoryCacheWithRevisionKey(filePath, vcsKey, number), vcsAnnotation);
}
}
public VcsAnnotation get(@NotNull final FilePath filePath, @NotNull final VcsKey vcsKey, @NotNull final VcsRevisionNumber number) {
public Object get(@NotNull final FilePath filePath, @NotNull final VcsKey vcsKey, @NotNull final VcsRevisionNumber number) {
synchronized (myLock) {
return myAnnotationCache.get(new HistoryCacheWithRevisionKey(filePath, vcsKey, number));
}
@@ -1,44 +1,35 @@
package com.intellij.openapi.vcs.actions;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.vcs.AbstractVcs;
import com.intellij.openapi.vcs.annotate.FileAnnotation;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
class AnnotateCurrentRevisionAction extends AnnotateRevisionAction {
@Nullable private final List<VcsFileRevision> myRevisions;
@Nullable private final FileAnnotation.CurrentFileRevisionProvider myProvider;
public AnnotateCurrentRevisionAction(@NotNull FileAnnotation annotation, @NotNull AbstractVcs vcs) {
super("Annotate Revision", "Annotate selected revision in new tab", AllIcons.Actions.Annotate,
annotation, vcs);
List<VcsFileRevision> revisions = annotation.getRevisions();
if (revisions == null) {
myRevisions = null;
return;
}
Map<VcsRevisionNumber, VcsFileRevision> map = new HashMap<>();
for (VcsFileRevision revision : revisions) {
map.put(revision.getRevisionNumber(), revision);
}
myRevisions = new ArrayList<>(annotation.getLineCount());
for (int i = 0; i < annotation.getLineCount(); i++) {
myRevisions.add(map.get(annotation.getLineRevisionNumber(i)));
}
myProvider = annotation.getCurrentFileRevisionProvider();
}
@Override
public void update(@NotNull AnActionEvent e) {
if (myProvider == null) {
e.getPresentation().setEnabledAndVisible(false);
return;
}
super.update(e);
}
@Nullable
public List<VcsFileRevision> getRevisions() {
return myRevisions;
@Override
protected VcsFileRevision getRevision(int lineNumber) {
assert myProvider != null;
return myProvider.getRevision(lineNumber);
}
}
@@ -6,57 +6,36 @@ import com.intellij.openapi.localVcs.UpToDateLineNumberProvider;
import com.intellij.openapi.vcs.AbstractVcs;
import com.intellij.openapi.vcs.annotate.FileAnnotation;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
class AnnotatePreviousRevisionAction extends AnnotateRevisionAction {
@Nullable private final List<VcsFileRevision> myRevisions;
@Nullable private final VcsFileRevision myLastRevision;
@Nullable private final FileAnnotation.PreviousFileRevisionProvider myProvider;
public AnnotatePreviousRevisionAction(@NotNull FileAnnotation annotation, @NotNull AbstractVcs vcs) {
super("Annotate Previous Revision", "Annotate successor of selected revision in new tab", AllIcons.Actions.Annotate,
annotation, vcs);
List<VcsFileRevision> revisions = annotation.getRevisions();
if (revisions == null) {
myRevisions = null;
myLastRevision = null;
myProvider = annotation.getPreviousFileRevisionProvider();
}
@Override
public void update(@NotNull AnActionEvent e) {
if (myProvider == null) {
e.getPresentation().setEnabledAndVisible(false);
return;
}
Map<VcsRevisionNumber, VcsFileRevision> map = new HashMap<>();
for (int i = 0; i < revisions.size(); i++) {
VcsFileRevision revision = revisions.get(i);
VcsFileRevision previousRevision = i + 1 < revisions.size() ? revisions.get(i + 1) : null;
map.put(revision.getRevisionNumber(), previousRevision);
}
myRevisions = new ArrayList<>(annotation.getLineCount());
for (int i = 0; i < annotation.getLineCount(); i++) {
myRevisions.add(map.get(annotation.getLineRevisionNumber(i)));
}
myLastRevision = ContainerUtil.getFirstItem(revisions);
}
@Override
@Nullable
public List<VcsFileRevision> getRevisions() {
return myRevisions;
super.update(e);
}
@Nullable
@Override
protected VcsFileRevision getFileRevision(@NotNull AnActionEvent e) {
if (getCurrentLine() == UpToDateLineNumberProvider.ABSENT_LINE_NUMBER) {
return myLastRevision;
protected VcsFileRevision getRevision(int lineNumber) {
assert myProvider != null;
if (lineNumber == UpToDateLineNumberProvider.ABSENT_LINE_NUMBER) {
return myProvider.getLastRevision();
}
else {
return myProvider.getPreviousRevision(lineNumber);
}
return super.getFileRevision(e);
}
}
@@ -21,7 +21,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.List;
abstract class AnnotateRevisionAction extends AnnotateRevisionActionBase implements DumbAware, UpToDateLineNumberListener {
@NotNull private final FileAnnotation myAnnotation;
@@ -48,17 +47,12 @@ abstract class AnnotateRevisionAction extends AnnotateRevisionActionBase impleme
return;
}
if (getRevisions() == null) {
e.getPresentation().setEnabledAndVisible(false);
return;
}
e.getPresentation().setVisible(true);
super.update(e);
}
@Nullable
protected abstract List<VcsFileRevision> getRevisions();
protected abstract VcsFileRevision getRevision(int lineNumber);
@Nullable
protected AbstractVcs getVcs(@NotNull AnActionEvent e) {
@@ -89,11 +83,8 @@ abstract class AnnotateRevisionAction extends AnnotateRevisionActionBase impleme
@Nullable
@Override
protected VcsFileRevision getFileRevision(@NotNull AnActionEvent e) {
List<VcsFileRevision> revisions = getRevisions();
assert revisions != null;
if (currentLine < 0 || currentLine >= revisions.size()) return null;
return revisions.get(currentLine);
if (currentLine < 0 || currentLine >= myAnnotation.getLineCount()) return null;
return getRevision(currentLine);
}
@Override
@@ -112,8 +103,4 @@ abstract class AnnotateRevisionAction extends AnnotateRevisionActionBase impleme
public void consume(Integer integer) {
currentLine = integer;
}
public int getCurrentLine() {
return currentLine;
}
}
@@ -87,7 +87,7 @@ public class AnnotateToggleAction extends ToggleAction implements DumbAware {
@Nullable final VirtualFile currentFile,
@NotNull final FileAnnotation fileAnnotation,
@NotNull final AbstractVcs vcs,
@Nullable UpToDateLineNumberProvider getUpToDateLineNumber) {
@Nullable final UpToDateLineNumberProvider upToDateLineNumberProvider) {
if (fileAnnotation.getFile() != null && fileAnnotation.getFile().isInLocalFileSystem()) {
ProjectLevelVcsManager.getInstance(project).getAnnotationLocalChangesListener().registerAnnotation(fileAnnotation.getFile(), fileAnnotation);
}
@@ -101,11 +101,18 @@ public class AnnotateToggleAction extends ToggleAction implements DumbAware {
});
});
fileAnnotation.setReloader(() -> {
if (editor.getGutter().isAnnotationsShown()) {
doAnnotate(editor, project, currentFile, fileAnnotation, vcs, upToDateLineNumberProvider);
}
});
final EditorGutterComponentEx editorGutter = (EditorGutterComponentEx)editor.getGutter();
final List<AnnotationFieldGutter> gutters = new ArrayList<>();
final AnnotationSourceSwitcher switcher = fileAnnotation.getAnnotationSourceSwitcher();
if (getUpToDateLineNumber == null) getUpToDateLineNumber = new UpToDateLineNumberProviderImpl(editor.getDocument(), project);
UpToDateLineNumberProvider getUpToDateLineNumber = upToDateLineNumberProvider != null ?
upToDateLineNumberProvider :
new UpToDateLineNumberProviderImpl(editor.getDocument(), project);
final AnnotationPresentation presentation = new AnnotationPresentation(fileAnnotation, getUpToDateLineNumber, switcher);
if (currentFile != null && vcs.getCommittedChangesProvider() != null) {
@@ -194,39 +201,47 @@ public class AnnotateToggleAction extends ToggleAction implements DumbAware {
@Nullable
private static Couple<Map<VcsRevisionNumber, Color>> computeBgColors(@NotNull FileAnnotation fileAnnotation, @NotNull Editor editor) {
final List<VcsFileRevision> fileRevisionList = fileAnnotation.getRevisions();
if (ContainerUtil.isEmpty(fileRevisionList)) return null;
final Map<VcsRevisionNumber, Color> commitOrderColors = new HashMap<>();
final Map<VcsRevisionNumber, Color> commitAuthorColors = new HashMap<>();
final Map<String, Color> authorColors = new HashMap<>();
Map<VcsRevisionNumber, Color> commitOrderColors = new HashMap<>();
Map<VcsRevisionNumber, Color> commitAuthorColors = new HashMap<>();
EditorColorsScheme colorScheme = editor.getColorsScheme();
AnnotationsSettings settings = AnnotationsSettings.getInstance();
List<Color> authorsColorPalette = settings.getAuthorsColors(colorScheme);
List<Color> orderedColorPalette = settings.getOrderedColors(colorScheme);
final int revisionsCount = fileRevisionList.size();
for (int i = 0; i < fileRevisionList.size(); i++) {
VcsFileRevision revision = fileRevisionList.get(i);
final VcsRevisionNumber number = revision.getRevisionNumber();
final String author = revision.getAuthor();
if (number == null) continue;
FileAnnotation.AuthorsMappingProvider authorsMappingProvider = fileAnnotation.getAuthorsMappingProvider();
if (authorsMappingProvider != null) {
Map<VcsRevisionNumber, String> authorsMap = authorsMappingProvider.getAuthors();
if (!commitAuthorColors.containsKey(number)) {
if (author != null && !authorColors.containsKey(author)) {
final int index = authorColors.size();
Color color = authorsColorPalette.get(index % authorsColorPalette.size());
authorColors.put(author, color);
}
commitAuthorColors.put(number, authorColors.get(author));
Map<String, Color> authorColors = new HashMap<>();
for (String author : ContainerUtil.newTreeSet(authorsMap.values())) {
int index = authorColors.size();
Color color = authorsColorPalette.get(index % authorsColorPalette.size());
authorColors.put(author, color);
}
if (!commitOrderColors.containsKey(number)) {
Color color = orderedColorPalette.get(orderedColorPalette.size() * i / revisionsCount);
commitOrderColors.put(number, color);
for (Map.Entry<VcsRevisionNumber, String> entry : authorsMap.entrySet()) {
VcsRevisionNumber revision = entry.getKey();
String author = entry.getValue();
Color color = authorColors.get(author);
commitAuthorColors.put(revision, color);
}
}
FileAnnotation.RevisionsOrderProvider revisionsOrderProvider = fileAnnotation.getRevisionsOrderProvider();
if (revisionsOrderProvider != null) {
List<List<VcsRevisionNumber>> orderedRevisions = revisionsOrderProvider.getOrderedRevisions();
int revisionsCount = orderedRevisions.size();
for (int index = 0; index < revisionsCount; index++) {
Color color = orderedColorPalette.get(orderedColorPalette.size() * index / revisionsCount);
for (VcsRevisionNumber number : orderedRevisions.get(index)) {
commitOrderColors.put(number, color);
}
}
}
return Couple.of(commitOrderColors.size() > 1 ? commitOrderColors : null,
commitAuthorColors.size() > 1 ? commitAuthorColors : null);
}
+3 -4
View File
@@ -36,7 +36,6 @@ import com.intellij.openapi.vcs.changes.CommitExecutor;
import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
import com.intellij.openapi.vcs.diff.DiffProvider;
import com.intellij.openapi.vcs.diff.RevisionSelector;
import com.intellij.openapi.vcs.history.VcsHistoryProvider;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vcs.merge.MergeProvider;
import com.intellij.openapi.vcs.rollback.RollbackEnvironment;
@@ -102,7 +101,7 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
private final GitUpdateEnvironment myUpdateEnvironment;
private final GitAnnotationProvider myAnnotationProvider;
private final DiffProvider myDiffProvider;
private final VcsHistoryProvider myHistoryProvider;
private final GitHistoryProvider myHistoryProvider;
@NotNull private final Git myGit;
private final ProjectLevelVcsManager myVcsManager;
private final GitVcsApplicationSettings myAppSettings;
@@ -203,12 +202,12 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
@Override
@NotNull
public VcsHistoryProvider getVcsHistoryProvider() {
public GitHistoryProvider getVcsHistoryProvider() {
return myHistoryProvider;
}
@Override
public VcsHistoryProvider getVcsBlockHistoryProvider() {
public GitHistoryProvider getVcsBlockHistoryProvider() {
return myHistoryProvider;
}
@@ -15,30 +15,38 @@
*/
package git4idea.annotate;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Attachment;
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.Pair;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.annotate.FileAnnotation;
import com.intellij.openapi.vcs.history.VcsAbstractHistorySession;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import com.intellij.openapi.vcs.history.VcsHistoryCache;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vcs.vfs.VcsFileSystem;
import com.intellij.openapi.vcs.vfs.VcsVirtualFile;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Interner;
import com.intellij.vcs.AnnotationProviderEx;
import com.intellij.vcs.log.VcsUser;
import com.intellij.vcs.log.VcsUserRegistry;
import com.intellij.vcsUtil.VcsUtil;
import git4idea.GitFileRevision;
import git4idea.GitRevisionNumber;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.annotate.GitFileAnnotation.LineInfo;
import git4idea.commands.GitCommand;
import git4idea.commands.GitSimpleHandler;
import git4idea.history.GitHistoryProvider;
import git4idea.history.GitHistoryUtils;
import git4idea.i18n.GitBundle;
import git4idea.util.StringScanner;
@@ -46,54 +54,66 @@ import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
public class GitAnnotationProvider implements AnnotationProviderEx {
private final Project myProject;
@NonNls private static final String SUBJECT_KEY = "summary";
@NonNls private static final String FILENAME_KEY = "filename";
@NonNls private static final String PREVIOUS_KEY = "previous";
@NonNls private static final String AUTHOR_KEY = "author";
@NonNls private static final String AUTHOR_EMAIL_KEY = "author-mail";
@NonNls private static final String COMMITTER_TIME_KEY = "committer-time";
private static final Logger LOG = Logger.getInstance(GitAnnotationProvider.class);
@NotNull private final VcsHistoryCache myCache;
@NotNull private final VcsUserRegistry myUserRegistry;
public GitAnnotationProvider(@NotNull Project project) {
myProject = project;
myCache = ProjectLevelVcsManager.getInstance(myProject).getVcsHistoryCache();
myUserRegistry = ServiceManager.getService(project, VcsUserRegistry.class);
}
@Override
public boolean isCaching() {
return true;
}
@NotNull
public FileAnnotation annotate(@NotNull VirtualFile file) throws VcsException {
return annotate(file, null);
}
public FileAnnotation annotate(@NotNull final VirtualFile file, final VcsFileRevision revision) throws VcsException {
@NotNull
public FileAnnotation annotate(@NotNull final VirtualFile file, @Nullable final VcsFileRevision revision) throws VcsException {
if (file.isDirectory()) {
throw new VcsException("Cannot annotate a directory");
}
final FilePath currentFilePath = VcsUtil.getFilePath(file.getPath());
final FilePath realFilePath;
setProgressIndicatorText(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();
}
setProgressIndicatorText(GitBundle.message("computing.annotation", file.getName()));
VcsRevisionNumber revisionNumber = revision != null ? revision.getRevisionNumber() : null;
return annotate(realFilePath, revisionNumber, revisions, file);
return annotate(realFilePath, revisionNumber, file);
}
@NotNull
@Override
public FileAnnotation annotate(@NotNull final FilePath path, @NotNull final VcsRevisionNumber revision) throws VcsException {
setProgressIndicatorText(GitBundle.message("getting.history", path.getName()));
List<VcsFileRevision> revisions = GitHistoryUtils.history(myProject, path, null, revision);
GitFileRevision fileRevision = new GitFileRevision(myProject, path, (GitRevisionNumber)revision);
VcsVirtualFile file = new VcsVirtualFile(path.getPath(), fileRevision, VcsFileSystem.getInstance());
setProgressIndicatorText(GitBundle.message("computing.annotation", path.getName()));
return annotate(path, revision, revisions, file);
return annotate(path, revision, file);
}
private static void setProgressIndicatorText(@Nullable String text) {
@@ -101,11 +121,36 @@ public class GitAnnotationProvider implements AnnotationProviderEx {
if (progress != null) progress.setText(text);
}
@NotNull
private GitFileAnnotation annotate(@NotNull final FilePath repositoryFilePath,
@Nullable final VcsRevisionNumber revision,
@NotNull final List<VcsFileRevision> revisions,
@NotNull final VirtualFile file) throws VcsException {
GitSimpleHandler h = new GitSimpleHandler(myProject, GitUtil.getGitRoot(repositoryFilePath), GitCommand.BLAME);
GitVcs vcs = GitVcs.getInstance(myProject);
assert vcs != null;
VcsRevisionNumber actualRevision = revision != null ? revision : vcs.getDiffProvider().getCurrentRevision(file);
if (actualRevision != null) {
Object annotatedData = myCache.get(repositoryFilePath, GitVcs.getKey(), actualRevision);
if (annotatedData instanceof CachedData) return restoreFromCache(file, revision, (CachedData)annotatedData);
}
GitFileAnnotation fileAnnotation = doAnnotate(repositoryFilePath, revision, file);
if (actualRevision != null) {
myCache.put(repositoryFilePath, GitVcs.getKey(), actualRevision, cacheData(fileAnnotation));
}
return fileAnnotation;
}
@NotNull
private GitFileAnnotation doAnnotate(@NotNull final FilePath repositoryFilePath,
@Nullable final VcsRevisionNumber revision,
@NotNull final VirtualFile file) throws VcsException {
setProgressIndicatorText(GitBundle.message("computing.annotation", file.getName()));
VirtualFile root = GitUtil.getGitRoot(repositoryFilePath);
GitSimpleHandler h = new GitSimpleHandler(myProject, root, GitCommand.BLAME);
h.setStdoutSuppressed(true);
h.setCharset(file.getCharset());
h.addParameters("--porcelain", "-l", "-t", "-w");
@@ -118,19 +163,71 @@ public class GitAnnotationProvider implements AnnotationProviderEx {
h.endOptions();
h.addRelativePaths(repositoryFilePath);
String output = h.run();
return parseAnnotations(revision, file, output, revisions);
GitFileAnnotation fileAnnotation = parseAnnotations(revision, file, root, output);
loadFileHistoryInBackground(fileAnnotation);
return fileAnnotation;
}
private void loadFileHistoryInBackground(@NotNull GitFileAnnotation fileAnnotation) {
ApplicationManager.getApplication().executeOnPooledThread(() -> {
try {
VirtualFile file = fileAnnotation.getFile();
FilePath filePath = VcsUtil.getFilePath(file);
VcsRevisionNumber currentRevision = fileAnnotation.getCurrentRevision();
List<VcsFileRevision> revisions;
if (file.isInLocalFileSystem() || currentRevision == null) {
revisions = loadFileHistory(filePath);
}
else {
revisions = GitHistoryUtils.history(myProject, filePath, null, currentRevision);
}
if (revisions == null) return;
ApplicationManager.getApplication().invokeLater(() -> {
fileAnnotation.setRevisions(revisions);
fileAnnotation.reload();
});
}
catch (VcsException e) {
LOG.error(e);
}
});
}
@Nullable
private List<VcsFileRevision> loadFileHistory(@NotNull FilePath filePath) throws VcsException {
GitVcs vcs = GitVcs.getInstance(myProject);
if (vcs == null) return null;
GitHistoryProvider historyProvider = vcs.getVcsHistoryProvider();
VcsAbstractHistorySession cachedSession = myCache.getFull(filePath, vcs.getKeyInstanceMethod(), historyProvider);
if (cachedSession != null && !ContainerUtil.isEmpty(cachedSession.getRevisionList())) {
return cachedSession.getRevisionList();
}
else {
VcsAbstractHistorySession session = historyProvider.createSessionFor(filePath);
if (session == null) return null;
myCache.put(filePath, null, vcs.getKeyInstanceMethod(), session, historyProvider, true);
return session.getRevisionList();
}
}
@NotNull
private GitFileAnnotation parseAnnotations(@Nullable VcsRevisionNumber revision,
@NotNull VirtualFile file,
@NotNull String output,
@NotNull List<VcsFileRevision> revisions) throws VcsException {
@NotNull VirtualFile root,
@NotNull String output) throws VcsException {
Interner<FilePath> pathInterner = new Interner<>();
try {
StringBuilder content = new StringBuilder();
List<LineInfo> lines = new ArrayList<>();
HashMap<String, LineInfo> commits = new HashMap<>();
final Map<VcsRevisionNumber, VcsFileRevision> historyAsMap = getRevisionMap(revisions);
for (StringScanner s = new StringScanner(output); s.hasMoreData(); ) {
// parse header line
String commitHash = s.spaceToken();
@@ -149,41 +246,70 @@ public class GitAnnotationProvider implements AnnotationProviderEx {
}
}
else {
GitRevisionNumber revisionNumber = null;
Date committerDate = null;
String author = null;
FilePath filePath = null;
String subject = null;
String authorName = null;
String authorEmail = null;
String previousRevision = null;
FilePath previousFilePath = null;
while (s.hasMoreData() && !s.startsWith('\t')) {
String key = s.spaceToken();
String value = s.line();
if (AUTHOR_KEY.equals(key)) {
author = value;
if (SUBJECT_KEY.equals(key)) {
subject = value;
}
if (COMMITTER_TIME_KEY.equals(key)) {
else if (AUTHOR_KEY.equals(key)) {
authorName = value;
}
else if (COMMITTER_TIME_KEY.equals(key)) {
committerDate = GitUtil.parseTimestamp(value);
revisionNumber = new GitRevisionNumber(commitHash, committerDate);
}
else if (FILENAME_KEY.equals(key)) {
filePath = VcsUtil.getFilePath(root, value);
}
else if (AUTHOR_EMAIL_KEY.equals(key)) {
authorEmail = value;
if (authorEmail.startsWith("<") && authorEmail.endsWith(">")) {
authorEmail = authorEmail.substring(1, authorEmail.length() - 1);
}
}
else if (PREVIOUS_KEY.equals(key)) {
int index = value.indexOf(' ');
if (index != -1) {
previousRevision = value.substring(0, index);
previousFilePath = VcsUtil.getFilePath(root, value.substring(index + 1, value.length()));
}
}
}
commit = new LineInfo(committerDate, revisionNumber, (GitFileRevision)historyAsMap.get(revisionNumber), author);
if (committerDate == null || filePath == null || authorName == null || authorEmail == null || subject == null) {
throw new VcsException("Output for line " + lineNum + " lacks necessary data");
}
GitRevisionNumber revisionNumber = new GitRevisionNumber(commitHash, committerDate);
VcsUser author = myUserRegistry.createUser(authorName, authorEmail);
GitRevisionNumber previousRevisionNumber = previousRevision != null ? new GitRevisionNumber(previousRevision) : null;
filePath = pathInterner.intern(filePath);
if (previousFilePath != null) previousFilePath = pathInterner.intern(previousFilePath);
commit = new LineInfo(myProject, revisionNumber, filePath, committerDate, author, subject,
previousRevisionNumber, previousFilePath);
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);
s.nextLine();
int expectedLineNum = lines.size() + 1;
if (lineNum != expectedLineNum) {
throw new VcsException("Adding for info for line " + lineNum + " but we are expecting it to be for " + expectedLineNum);
}
content.append(s.line(true));
lines.add(commit);
}
return new GitFileAnnotation(myProject, file, revision, content.toString(), lines, revisions);
return new GitFileAnnotation(myProject, file, revision, lines);
}
catch (Exception e) {
LOG.error("Couldn't parse annotation: " + e, new Attachment("output.txt", output));
@@ -192,13 +318,26 @@ public class GitAnnotationProvider implements AnnotationProviderEx {
}
@NotNull
private static Map<VcsRevisionNumber, VcsFileRevision> getRevisionMap(@NotNull List<VcsFileRevision> revisions) {
return ContainerUtil.map2Map(revisions,
new Function<VcsFileRevision, Pair<VcsRevisionNumber, VcsFileRevision>>() {
@Override
public Pair<VcsRevisionNumber, VcsFileRevision> fun(VcsFileRevision revision) {
return Pair.create(revision.getRevisionNumber(), revision);
}
});
private GitFileAnnotation restoreFromCache(@NotNull VirtualFile file,
@Nullable VcsRevisionNumber revisionNumber,
@NotNull CachedData data) {
GitFileAnnotation fileAnnotation = new GitFileAnnotation(myProject, file, revisionNumber, data.lines);
loadFileHistoryInBackground(fileAnnotation);
return fileAnnotation;
}
@NotNull
private CachedData cacheData(@NotNull GitFileAnnotation annotation) {
return new CachedData(annotation.getLines());
}
private static class CachedData {
public final List<LineInfo> lines;
public CachedData(List<LineInfo> lines) {
this.lines = lines;
}
}
}
@@ -16,16 +16,24 @@
package git4idea.annotate;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.VcsKey;
import com.intellij.openapi.vcs.annotate.FileAnnotation;
import com.intellij.openapi.vcs.annotate.LineAnnotationAspect;
import com.intellij.openapi.vcs.annotate.LineAnnotationAspectAdapter;
import com.intellij.openapi.vcs.annotate.ShowAllAffectedGenericAction;
import com.intellij.openapi.vcs.changes.ContentRevision;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.hash.HashMap;
import com.intellij.util.text.DateFormatUtil;
import com.intellij.vcs.log.VcsUser;
import com.intellij.vcsUtil.VcsUtil;
import git4idea.GitContentRevision;
import git4idea.GitFileRevision;
import git4idea.GitRevisionNumber;
import git4idea.GitVcs;
@@ -33,8 +41,7 @@ import git4idea.i18n.GitBundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Date;
import java.util.List;
import java.util.*;
public class GitFileAnnotation extends FileAnnotation {
private final Project myProject;
@@ -42,49 +49,41 @@ public class GitFileAnnotation extends FileAnnotation {
@NotNull private final GitVcs myVcs;
@Nullable private final VcsRevisionNumber myBaseRevision;
@NotNull private final String myAnnotatedContent;
@NotNull private final List<LineInfo> myLines;
@NotNull private final List<VcsFileRevision> myRevisions;
@Nullable private List<VcsFileRevision> myRevisions;
private final LineAnnotationAspect DATE_ASPECT = new GitAnnotationAspect(LineAnnotationAspect.DATE, true) {
@Override
public String doGetValue(LineInfo info) {
final Date date = info.getDate();
return date == null ? "" : DateFormatUtil.formatPrettyDate(date);
return DateFormatUtil.formatPrettyDate(info.getDate());
}
};
private final LineAnnotationAspect REVISION_ASPECT = new GitAnnotationAspect(LineAnnotationAspect.REVISION, false) {
@Override
protected String doGetValue(LineInfo lineInfo) {
final GitRevisionNumber revision = lineInfo.getRevision();
return revision == null ? "" : String.valueOf(revision.getShortRev());
return String.valueOf(lineInfo.getRevisionNumber().getShortRev());
}
};
private final LineAnnotationAspect AUTHOR_ASPECT = new GitAnnotationAspect(LineAnnotationAspect.AUTHOR, true) {
@Override
protected String doGetValue(LineInfo lineInfo) {
final String author = lineInfo.getAuthor();
return author == null ? "" : author;
return lineInfo.getAuthor();
}
};
public GitFileAnnotation(@NotNull final Project project,
@NotNull VirtualFile file,
@Nullable final VcsRevisionNumber revision,
@NotNull String annotatedContent,
@NotNull List<LineInfo> lines,
@NotNull List<VcsFileRevision> revisions) {
@NotNull List<LineInfo> lines) {
super(project);
myProject = project;
myFile = file;
myVcs = ObjectUtils.assertNotNull(GitVcs.getInstance(myProject));
myBaseRevision = revision == null ? (myVcs.getDiffProvider().getCurrentRevision(file)) : revision;
myAnnotatedContent = annotatedContent;
myLines = lines;
myRevisions = revisions;
}
@Override
@@ -96,25 +95,16 @@ public class GitFileAnnotation extends FileAnnotation {
return new LineAnnotationAspect[]{REVISION_ASPECT, DATE_ASPECT, AUTHOR_ASPECT};
}
@Override
public String getToolTip(final int lineNumber) {
if (myLines.size() <= lineNumber || lineNumber < 0) {
return "";
}
final LineInfo info = myLines.get(lineNumber);
VcsFileRevision fileRevision = info.myFileRevision;
if (fileRevision != null) {
return GitBundle.message("annotation.tool.tip", info.getRevision().asString(), info.getAuthor(),
DateFormatUtil.formatDateTime(info.getDate()), fileRevision.getCommitMessage());
}
else {
return "";
}
}
@Nullable
@Override
public String getAnnotatedContent() {
return myAnnotatedContent;
try {
ContentRevision revision = GitContentRevision.createRevision(myFile, myBaseRevision, myProject);
return revision.getContent();
}
catch (VcsException e) {
return null;
}
}
@Override
@@ -122,33 +112,49 @@ public class GitFileAnnotation extends FileAnnotation {
return myRevisions;
}
public void setRevisions(@NotNull List<VcsFileRevision> revisions) {
myRevisions = revisions;
}
@Override
public int getLineCount() {
return myLines.size();
}
@Nullable
public LineInfo getLineInfo(int lineNumber) {
if (lineNumberCheck(lineNumber)) return null;
return myLines.get(lineNumber);
}
@Nullable
@Override
public VcsRevisionNumber getLineRevisionNumber(final int lineNumber) {
if (lineNumberCheck(lineNumber)) {
return null;
}
return myLines.get(lineNumber).getRevision();
public String getToolTip(int lineNumber) {
LineInfo lineInfo = getLineInfo(lineNumber);
return lineInfo != null ? lineInfo.getTooltip() : null;
}
@Nullable
@Override
public VcsRevisionNumber getLineRevisionNumber(int lineNumber) {
LineInfo lineInfo = getLineInfo(lineNumber);
return lineInfo != null ? lineInfo.getRevisionNumber() : null;
}
@Nullable
@Override
public Date getLineDate(int lineNumber) {
LineInfo lineInfo = getLineInfo(lineNumber);
return lineInfo != null ? lineInfo.getDate() : null;
}
private boolean lineNumberCheck(int lineNumber) {
return myLines.size() <= lineNumber || lineNumber < 0;
}
@Override
public Date getLineDate(int lineNumber) {
if (lineNumberCheck(lineNumber)) {
return null;
}
return myLines.get(lineNumber).getDate();
}
public int getNumLines() {
return myLines.size();
@NotNull
public List<LineInfo> getLines() {
return myLines;
}
/**
@@ -175,37 +181,74 @@ public class GitFileAnnotation extends FileAnnotation {
protected void showAffectedPaths(int lineNum) {
if (lineNum >= 0 && lineNum < myLines.size()) {
LineInfo info = myLines.get(lineNum);
ShowAllAffectedGenericAction.showSubmittedFiles(myProject, info.getRevision(), myFile, GitVcs.getKey());
ShowAllAffectedGenericAction.showSubmittedFiles(myProject, info.getRevisionNumber(), myFile, GitVcs.getKey());
}
}
}
/**
* Line information
*/
static class LineInfo {
private final Date myDate;
private final GitRevisionNumber myRevision;
private final GitFileRevision myFileRevision;
private final String myAuthor;
@NotNull private final Project myProject;
@NotNull private final GitRevisionNumber myRevision;
@NotNull private final FilePath myFilePath;
@Nullable private final GitRevisionNumber myPreviousRevision;
@Nullable private final FilePath myPreviousFilePath;
@NotNull private final Date myDate;
@NotNull private final VcsUser myAuthor;
@NotNull private final String mySubject;
public LineInfo(Date date, GitRevisionNumber revision, GitFileRevision fileRevision, String author) {
myDate = date;
public LineInfo(@NotNull Project project,
@NotNull GitRevisionNumber revision,
@NotNull FilePath path,
@NotNull Date date,
@NotNull VcsUser author,
@NotNull String subject,
@Nullable GitRevisionNumber previousRevision,
@Nullable FilePath previousPath) {
myProject = project;
myRevision = revision;
myFileRevision = fileRevision;
myFilePath = path;
myPreviousRevision = previousRevision;
myPreviousFilePath = previousPath;
myDate = date;
myAuthor = author;
mySubject = subject;
}
@NotNull
public GitRevisionNumber getRevisionNumber() {
return myRevision;
}
@NotNull
public FilePath getFilePath() {
return myFilePath;
}
@NotNull
public VcsFileRevision getFileRevision() {
return new GitFileRevision(myProject, myFilePath, myRevision);
}
@Nullable
public VcsFileRevision getPreviousFileRevision() {
if (myPreviousRevision == null || myPreviousFilePath == null) return null;
return new GitFileRevision(myProject, myPreviousFilePath, myPreviousRevision);
}
@NotNull
public Date getDate() {
return myDate;
}
public GitRevisionNumber getRevision() {
return myRevision;
@NotNull
public String getAuthor() {
return myAuthor.getName();
}
public String getAuthor() {
return myAuthor;
@NotNull
public String getTooltip() {
return GitBundle.message("annotation.tool.tip", myRevision.asString(), myAuthor.getName(),
DateFormatUtil.formatDateTime(myDate), mySubject);
}
}
@@ -231,4 +274,79 @@ public class GitFileAnnotation extends FileAnnotation {
final VcsRevisionNumber currentCurrentRevision = myVcs.getDiffProvider().getCurrentRevision(myFile);
return myBaseRevision != null && ! myBaseRevision.equals(currentCurrentRevision);
}
@Nullable
@Override
public CurrentFileRevisionProvider getCurrentFileRevisionProvider() {
return (lineNumber) -> {
LineInfo lineInfo = getLineInfo(lineNumber);
return lineInfo != null ? lineInfo.getFileRevision() : null;
};
}
@Nullable
@Override
public PreviousFileRevisionProvider getPreviousFileRevisionProvider() {
return new PreviousFileRevisionProvider() {
@Nullable
@Override
public VcsFileRevision getPreviousRevision(int lineNumber) {
LineInfo lineInfo = getLineInfo(lineNumber);
return lineInfo != null ? lineInfo.getPreviousFileRevision() : null;
}
@Nullable
@Override
public VcsFileRevision getLastRevision() {
if (myBaseRevision instanceof GitRevisionNumber) {
return new GitFileRevision(myProject, VcsUtil.getFilePath(myFile), (GitRevisionNumber)myBaseRevision);
}
else {
return ContainerUtil.getFirstItem(getRevisions());
}
}
};
}
@Nullable
@Override
public AuthorsMappingProvider getAuthorsMappingProvider() {
Map<VcsRevisionNumber, String> authorsMap = new HashMap<>();
for (int i = 0; i < getLineCount(); i++) {
LineInfo lineInfo = getLineInfo(i);
if (lineInfo == null) continue;
if (!authorsMap.containsKey(lineInfo.getRevisionNumber())) {
authorsMap.put(lineInfo.getRevisionNumber(), lineInfo.getAuthor());
}
}
return () -> authorsMap;
}
@Nullable
@Override
public RevisionsOrderProvider getRevisionsOrderProvider() {
ContainerUtil.KeyOrderedMultiMap<Date, VcsRevisionNumber> dates = new ContainerUtil.KeyOrderedMultiMap<>();
for (int i = 0; i < getLineCount(); i++) {
LineInfo lineInfo = getLineInfo(i);
if (lineInfo == null) continue;
VcsRevisionNumber number = lineInfo.getRevisionNumber();
Date date = lineInfo.getDate();
dates.putValue(date, number);
}
List<List<VcsRevisionNumber>> orderedRevisions = new ArrayList<>();
NavigableSet<Date> orderedDates = dates.navigableKeySet();
for (Date date : orderedDates.descendingSet()) {
Collection<VcsRevisionNumber> revisionNumbers = dates.get(date);
orderedRevisions.add(new ArrayList<>(revisionNumbers));
}
return () -> orderedRevisions;
}
}
@@ -102,7 +102,7 @@ public class GitHistoryProvider implements VcsHistoryProviderEx,
}
@Nullable
public VcsHistorySession createSessionFor(final FilePath filePath) throws VcsException {
public VcsAbstractHistorySession createSessionFor(final FilePath filePath) throws VcsException {
List<VcsFileRevision> revisions = null;
try {
revisions = GitHistoryUtils.history(myProject, filePath);