mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
@@ -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));
|
||||
}
|
||||
|
||||
+15
-24
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-37
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user