mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 06:50:54 +07:00
[vcs-log] commit message prefix navigation in VCS Log table (IJPL-89240)
GitOrigin-RevId: 3b0196284ac76797ee92b7f050643e01393850a5
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6100fb3643
commit
d641d05793
@@ -817,6 +817,10 @@ vcs.log.filter.text.on.the.fly=false
|
||||
vcs.log.filter.text.on.the.fly.description=If enabled, applies text filter to the Log while typing
|
||||
vcs.log.filter.text.highlight.matches=false
|
||||
vcs.log.filter.text.highlight.matches.description=Highlight text filter matches in the Log table
|
||||
vcs.log.render.commit.links=true
|
||||
vcs.log.render.commit.links.description=Render commit links (e.g., fixup!, squash!) in the Log table.
|
||||
vcs.log.render.commit.links.process.chunk=50
|
||||
vcs.log.render.commit.links.process.chunk.description=The number of commits from the Log table to resolve links.
|
||||
vcs.log.max.changes.shown=50000
|
||||
vcs.log.max.changes.shown.description=Limit for showing commits in the changes view.
|
||||
vcs.log.max.branches.shown=100
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
*f:com.intellij.codeInsight.hints.VcsCodeVisionLanguageContext$Companion
|
||||
- sf:EXTENSION:java.lang.String
|
||||
- f:getProvidersExtensionPoint():com.intellij.lang.LanguageExtension
|
||||
*:com.intellij.openapi.vcs.LinkDescriptor
|
||||
- a:getRange():com.intellij.openapi.util.TextRange
|
||||
*:com.intellij.openapi.vcs.changes.CommitExecutorWithRichDescription
|
||||
- com.intellij.openapi.vcs.changes.CommitExecutor
|
||||
- a:getText(com.intellij.vcs.commit.CommitWorkflowHandlerState):java.lang.String
|
||||
|
||||
@@ -281,6 +281,7 @@ c:com.intellij.openapi.vcs.IssueNavigationConfiguration
|
||||
- s:processTextWithLinks(java.lang.String,java.util.List,java.util.function.Consumer,java.util.function.BiConsumer):V
|
||||
- setLinks(java.util.List):V
|
||||
c:com.intellij.openapi.vcs.IssueNavigationConfiguration$LinkMatch
|
||||
- com.intellij.openapi.vcs.LinkDescriptor
|
||||
- java.lang.Comparable
|
||||
- <init>(com.intellij.openapi.util.TextRange,java.lang.String):V
|
||||
- compareTo(java.lang.Object):I
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
package com.intellij.openapi.vcs;
|
||||
|
||||
@@ -56,7 +56,7 @@ public class IssueNavigationConfiguration extends SimpleModificationTracker
|
||||
XmlSerializerUtil.copyBean(state, this);
|
||||
}
|
||||
|
||||
public static class LinkMatch implements Comparable {
|
||||
public static class LinkMatch implements LinkDescriptor, Comparable {
|
||||
private final TextRange myRange;
|
||||
private final String myTargetUrl;
|
||||
|
||||
@@ -65,6 +65,8 @@ public class IssueNavigationConfiguration extends SimpleModificationTracker
|
||||
myTargetUrl = targetUrl;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public TextRange getRange() {
|
||||
return myRange;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.vcs
|
||||
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
/**
|
||||
* Represent a link in some text.
|
||||
*
|
||||
* [range] - E.g., link text range.
|
||||
* E.g., a substring range of corresponding text with a link.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
interface LinkDescriptor {
|
||||
val range: TextRange
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.vcs.changes.issueLinks;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -7,6 +7,7 @@ import com.intellij.openapi.vcs.IssueNavigationConfiguration;
|
||||
import com.intellij.ui.SimpleColoredComponent;
|
||||
import com.intellij.ui.SimpleTextAttributes;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -53,7 +54,8 @@ public class IssueLinkRenderer {
|
||||
myColoredComponent.append(piece, baseStyle, new SimpleColoredComponent.BrowserLauncherTag(targetUrl));
|
||||
}
|
||||
|
||||
private static SimpleTextAttributes getLinkAttributes(@NotNull SimpleTextAttributes baseStyle) {
|
||||
@ApiStatus.Internal
|
||||
public static SimpleTextAttributes getLinkAttributes(@NotNull SimpleTextAttributes baseStyle) {
|
||||
Color color = baseStyle.getFgColor();
|
||||
int alpha = color != null ? color.getAlpha() : 255;
|
||||
Color linkColor = JBUI.CurrentTheme.Link.Foreground.ENABLED;
|
||||
|
||||
@@ -145,6 +145,19 @@
|
||||
- a:getValue(com.intellij.vcs.log.ui.table.GraphTableModel,I):java.lang.Object
|
||||
- a:isDynamic():Z
|
||||
- isResizable():Z
|
||||
*:com.intellij.vcs.log.ui.table.links.CommitLinksProvider
|
||||
- *sf:Companion:com.intellij.vcs.log.ui.table.links.CommitLinksProvider$Companion
|
||||
- a:getLinks(com.intellij.vcs.log.CommitId):java.util.List
|
||||
- s:getServiceOrNull(com.intellij.openapi.project.Project):com.intellij.vcs.log.ui.table.links.CommitLinksProvider
|
||||
- a:resolveLinks(java.lang.String,com.intellij.vcs.log.data.VcsLogData,com.intellij.vcs.log.visible.VisiblePack,I,I):V
|
||||
*f:com.intellij.vcs.log.ui.table.links.CommitLinksProvider$Companion
|
||||
- f:getServiceOrNull(com.intellij.openapi.project.Project):com.intellij.vcs.log.ui.table.links.CommitLinksProvider
|
||||
*:com.intellij.vcs.log.ui.table.links.CommitLinksResolveListener
|
||||
- java.util.EventListener
|
||||
- *sf:Companion:com.intellij.vcs.log.ui.table.links.CommitLinksResolveListener$Companion
|
||||
- sf:TOPIC:com.intellij.util.messages.Topic
|
||||
- a:onLinksResolved(java.lang.String):V
|
||||
*f:com.intellij.vcs.log.ui.table.links.CommitLinksResolveListener$Companion
|
||||
f:com.intellij.vcs.log.visible.VcsLogFilterUtilKt
|
||||
- *sf:filter(com.intellij.vcs.log.data.VcsLogData,com.intellij.vcs.log.VcsLogFilterCollection,com.intellij.vcs.log.visible.CommitCountStage):it.unimi.dsi.fastutil.ints.IntSet
|
||||
- *bs:filter$default(com.intellij.vcs.log.data.VcsLogData,com.intellij.vcs.log.VcsLogFilterCollection,com.intellij.vcs.log.visible.CommitCountStage,I,java.lang.Object):it.unimi.dsi.fastutil.ints.IntSet
|
||||
|
||||
@@ -1205,7 +1205,7 @@ c:com.intellij.vcs.log.ui.table.VcsLogGraphTable
|
||||
- com.intellij.openapi.actionSystem.UiCompatibleDataProvider
|
||||
- com.intellij.ui.table.JBTable
|
||||
- com.intellij.vcs.log.ui.table.VcsLogCommitList
|
||||
- <init>(java.lang.String,com.intellij.vcs.log.data.VcsLogData,com.intellij.vcs.log.impl.VcsLogUiProperties,com.intellij.vcs.log.ui.VcsLogColorManager,java.lang.Runnable,com.intellij.openapi.Disposable):V
|
||||
- <init>(com.intellij.vcs.log.ui.VcsLogUiEx,com.intellij.vcs.log.data.VcsLogData,com.intellij.vcs.log.impl.VcsLogUiProperties,com.intellij.vcs.log.ui.VcsLogColorManager,java.lang.Runnable,com.intellij.openapi.Disposable):V
|
||||
- addHighlighter(com.intellij.vcs.log.VcsLogHighlighter):V
|
||||
- p:appendActionToEmptyText(java.lang.String,java.lang.Runnable):V
|
||||
- applyHighlighters(java.awt.Component,I,I,Z,Z):com.intellij.ui.SimpleTextAttributes
|
||||
|
||||
@@ -44,5 +44,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.backend.observation" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.coroutines" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.io.storages" />
|
||||
<orderEntry type="library" scope="TEST" name="mockito" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -96,7 +96,7 @@ class FileHistoryPanel extends JPanel implements UiDataProvider, Disposable {
|
||||
myFileHistoryModel = fileHistoryModel;
|
||||
myProperties = logUi.getProperties();
|
||||
|
||||
myGraphTable = new VcsLogGraphTable(logUi.getId(), logData, logUi.getProperties(), colorManager,
|
||||
myGraphTable = new VcsLogGraphTable(logUi, logData, logUi.getProperties(), colorManager,
|
||||
() -> logUi.requestMore(EmptyRunnable.INSTANCE), disposable) {
|
||||
@Override
|
||||
protected void updateEmptyText() {
|
||||
|
||||
@@ -40,10 +40,7 @@ import com.intellij.vcs.log.impl.CommonUiProperties;
|
||||
import com.intellij.vcs.log.impl.MainVcsLogUiProperties;
|
||||
import com.intellij.vcs.log.impl.VcsLogNavigationUtil;
|
||||
import com.intellij.vcs.log.impl.VcsLogUiProperties;
|
||||
import com.intellij.vcs.log.ui.AbstractVcsLogUi;
|
||||
import com.intellij.vcs.log.ui.VcsLogActionIds;
|
||||
import com.intellij.vcs.log.ui.VcsLogColorManager;
|
||||
import com.intellij.vcs.log.ui.VcsLogInternalDataKeys;
|
||||
import com.intellij.vcs.log.ui.*;
|
||||
import com.intellij.vcs.log.ui.details.CommitDetailsListPanel;
|
||||
import com.intellij.vcs.log.ui.details.commit.CommitDetailsPanel;
|
||||
import com.intellij.vcs.log.ui.filter.VcsLogFilterUiEx;
|
||||
@@ -109,7 +106,7 @@ public class MainFrame extends JPanel implements UiDataProvider, Disposable {
|
||||
|
||||
myFilterUi = filterUi;
|
||||
|
||||
myGraphTable = new MyVcsLogGraphTable(logUi.getId(), logData, logUi.getProperties(), colorManager,
|
||||
myGraphTable = new MyVcsLogGraphTable(logUi, logData, logUi.getProperties(), colorManager,
|
||||
() -> logUi.getRefresher().onRefresh(), () -> logUi.requestMore(EmptyRunnable.INSTANCE),
|
||||
disposable);
|
||||
String vcsDisplayName = VcsLogUtil.getVcsDisplayName(logData.getProject(), logData.getLogProviders().values());
|
||||
@@ -405,11 +402,11 @@ public class MainFrame extends JPanel implements UiDataProvider, Disposable {
|
||||
private class MyVcsLogGraphTable extends VcsLogGraphTable {
|
||||
private final @NotNull Runnable myRefresh;
|
||||
|
||||
MyVcsLogGraphTable(@NotNull String logId, @NotNull VcsLogData logData,
|
||||
MyVcsLogGraphTable(@NotNull VcsLogUiEx logUi, @NotNull VcsLogData logData,
|
||||
@NotNull VcsLogUiProperties uiProperties, @NotNull VcsLogColorManager colorManager,
|
||||
@NotNull Runnable refresh, @NotNull Runnable requestMore,
|
||||
@NotNull Disposable disposable) {
|
||||
super(logId, logData, uiProperties, colorManager, requestMore, disposable);
|
||||
super(logUi, logData, uiProperties, colorManager, requestMore, disposable);
|
||||
myRefresh = refresh;
|
||||
IndexSpeedSearch speedSearch = new IndexSpeedSearch(myLogData.getProject(), myLogData.getIndex(), myLogData.getStorage(), this) {
|
||||
@Override
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.vcs.log.ui.render;
|
||||
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
import com.intellij.vcs.log.CommitId;
|
||||
import com.intellij.vcs.log.VcsRef;
|
||||
import com.intellij.vcs.log.graph.PrintElement;
|
||||
import com.intellij.vcs.log.ui.VcsBookmarkRef;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -18,14 +20,21 @@ public class GraphCommitCell {
|
||||
private final @NotNull Collection<VcsBookmarkRef> myBookmarksToThisCommit;
|
||||
private final @NotNull Collection<? extends PrintElement> myPrintElements;
|
||||
|
||||
public GraphCommitCell(@NotNull String text,
|
||||
private final @Nullable CommitId myCommitId;
|
||||
private final boolean myIsLoading;
|
||||
|
||||
public GraphCommitCell(@Nullable CommitId commitId,
|
||||
@NotNull String text,
|
||||
@NotNull Collection<VcsRef> refsToThisCommit,
|
||||
@NotNull Collection<VcsBookmarkRef> bookmarksToThisCommit,
|
||||
@NotNull Collection<? extends PrintElement> printElements) {
|
||||
@NotNull Collection<? extends PrintElement> printElements,
|
||||
boolean isLoading) {
|
||||
myCommitId = commitId;
|
||||
myText = text;
|
||||
myRefsToThisCommit = refsToThisCommit;
|
||||
myBookmarksToThisCommit = bookmarksToThisCommit;
|
||||
myPrintElements = printElements;
|
||||
myIsLoading = isLoading;
|
||||
}
|
||||
|
||||
public @NotNull @NlsSafe String getText() {
|
||||
@@ -44,6 +53,14 @@ public class GraphCommitCell {
|
||||
return myPrintElements;
|
||||
}
|
||||
|
||||
public @Nullable CommitId getCommitId() {
|
||||
return myCommitId;
|
||||
}
|
||||
|
||||
public boolean isLoading() {
|
||||
return myIsLoading;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myText;
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.intellij.ui.scale.JBUIScale;
|
||||
import com.intellij.ui.speedSearch.SpeedSearchUtil;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.StartupUiUtil;
|
||||
import com.intellij.vcs.log.CommitId;
|
||||
import com.intellij.vcs.log.VcsLogFilterCollection;
|
||||
import com.intellij.vcs.log.VcsLogTextFilter;
|
||||
import com.intellij.vcs.log.VcsRef;
|
||||
@@ -26,6 +27,7 @@ import com.intellij.vcs.log.ui.table.VcsLogCellRenderer;
|
||||
import com.intellij.vcs.log.ui.table.VcsLogGraphTable;
|
||||
import com.intellij.vcs.log.ui.table.column.Commit;
|
||||
import com.intellij.vcs.log.ui.table.column.VcsLogColumnManager;
|
||||
import com.intellij.vcs.log.ui.table.links.VcsLinksRenderer;
|
||||
import com.intellij.vcs.log.visible.filters.VcsLogTextFilterWithMatches;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -158,6 +160,7 @@ public class GraphCommitCellRenderer extends TypeSafeTableCellRenderer<GraphComm
|
||||
private final @NotNull VcsLogGraphTable myGraphTable;
|
||||
private final @NotNull GraphCellPainter myPainter;
|
||||
private final @NotNull IssueLinkRenderer myIssueLinkRenderer;
|
||||
private final @NotNull VcsLinksRenderer myVcsLinksRenderer;
|
||||
private final @NotNull VcsLogLabelPainter myReferencePainter;
|
||||
|
||||
private @NotNull Collection<? extends PrintElement> myPrintElements = Collections.emptyList();
|
||||
@@ -174,6 +177,7 @@ public class GraphCommitCellRenderer extends TypeSafeTableCellRenderer<GraphComm
|
||||
myGraphTable = table;
|
||||
|
||||
myReferencePainter = new VcsLogLabelPainter(data, table, iconCache);
|
||||
myVcsLinksRenderer = new VcsLinksRenderer(data.getProject(), this);
|
||||
myIssueLinkRenderer = new IssueLinkRenderer(data.getProject(), this);
|
||||
setCellState(new VcsLogTableCellState());
|
||||
|
||||
@@ -229,6 +233,7 @@ public class GraphCommitCellRenderer extends TypeSafeTableCellRenderer<GraphComm
|
||||
}
|
||||
|
||||
append(""); // appendTextPadding wont work without this
|
||||
boolean renderLinks = !cell.isLoading();
|
||||
if (myReferencePainter.isLeftAligned()) {
|
||||
myReferencePainter.customizePainter(refs, bookmarks, getBackground(), labelForeground, isSelected,
|
||||
getAvailableWidth(column, myGraphWidth));
|
||||
@@ -236,18 +241,32 @@ public class GraphCommitCellRenderer extends TypeSafeTableCellRenderer<GraphComm
|
||||
int referencesWidth = myReferencePainter.getSize().width;
|
||||
if (referencesWidth > 0) referencesWidth += LabelPainter.RIGHT_PADDING.get();
|
||||
appendTextPadding(myGraphWidth + referencesWidth);
|
||||
appendText(cell, style, isSelected);
|
||||
appendText(cell, style, isSelected, renderLinks);
|
||||
}
|
||||
else {
|
||||
appendTextPadding(myGraphWidth);
|
||||
appendText(cell, style, isSelected);
|
||||
appendText(cell, style, isSelected, renderLinks);
|
||||
myReferencePainter.customizePainter(refs, bookmarks, getBackground(), labelForeground, isSelected,
|
||||
getAvailableWidth(column, myGraphWidth));
|
||||
}
|
||||
}
|
||||
|
||||
private void appendText(@NotNull GraphCommitCell cell, @NotNull SimpleTextAttributes style, boolean isSelected) {
|
||||
myIssueLinkRenderer.appendTextWithLinks(StringUtil.replace(cell.getText(), "\t", " ").trim(), style);
|
||||
private void appendText(@NotNull GraphCommitCell cell, @NotNull SimpleTextAttributes style, boolean isSelected, boolean renderLinks) {
|
||||
String cellText = StringUtil.replace(cell.getText(), "\t", " ").trim();
|
||||
CommitId commitId = cell.getCommitId();
|
||||
|
||||
if (renderLinks) {
|
||||
if (VcsLinksRenderer.isEnabled()) {
|
||||
myVcsLinksRenderer.appendTextWithLinks(cellText, style, commitId);
|
||||
}
|
||||
else {
|
||||
myIssueLinkRenderer.appendTextWithLinks(cellText, style);
|
||||
}
|
||||
}
|
||||
else {
|
||||
append(cellText, style);
|
||||
}
|
||||
|
||||
SpeedSearchUtil.applySpeedSearchHighlighting(myGraphTable, this, false, isSelected);
|
||||
if (Registry.is("vcs.log.filter.text.highlight.matches")) {
|
||||
VcsLogTextFilter textFilter = myGraphTable.getModel().getVisiblePack().getFilters().get(VcsLogFilterCollection.TEXT_FILTER);
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.intellij.vcs.log.ui.table.column.VcsLogColumn;
|
||||
import com.intellij.vcs.log.ui.table.column.VcsLogColumnManager;
|
||||
import com.intellij.vcs.log.util.VcsLogUtil;
|
||||
import com.intellij.vcs.log.visible.VisiblePack;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -156,6 +157,12 @@ public final class GraphTableModel extends AbstractTableModel implements VcsLogC
|
||||
|
||||
public @Nullable CommitId getCommitId(int row) {
|
||||
VcsCommitMetadata metadata = getCommitMetadata(row);
|
||||
if (metadata instanceof LoadingDetails) return null;
|
||||
return getCommitId(metadata);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public @Nullable CommitId getCommitId(@NotNull VcsCommitMetadata metadata) {
|
||||
if (metadata instanceof LoadingDetails) return null;
|
||||
return new CommitId(metadata.getId(), metadata.getRoot());
|
||||
}
|
||||
|
||||
@@ -38,13 +38,18 @@ import com.intellij.vcs.log.graph.RowType;
|
||||
import com.intellij.vcs.log.graph.VisibleGraph;
|
||||
import com.intellij.vcs.log.graph.actions.GraphAnswer;
|
||||
import com.intellij.vcs.log.impl.CommonUiProperties;
|
||||
import com.intellij.vcs.log.impl.VcsLogNavigationUtil;
|
||||
import com.intellij.vcs.log.impl.VcsLogUiProperties;
|
||||
import com.intellij.vcs.log.statistics.VcsLogUsageTriggerCollector;
|
||||
import com.intellij.vcs.log.ui.VcsLogColorManager;
|
||||
import com.intellij.vcs.log.ui.VcsLogInternalDataKeys;
|
||||
import com.intellij.vcs.log.ui.VcsLogUiEx;
|
||||
import com.intellij.vcs.log.ui.render.GraphCommitCellRenderer;
|
||||
import com.intellij.vcs.log.ui.render.SimpleColoredComponentLinkMouseListener;
|
||||
import com.intellij.vcs.log.ui.table.column.*;
|
||||
import com.intellij.vcs.log.ui.table.links.CommitLinksProvider;
|
||||
import com.intellij.vcs.log.ui.table.links.NavigateToCommit;
|
||||
import com.intellij.vcs.log.ui.table.links.VcsLinksRenderer;
|
||||
import com.intellij.vcs.log.util.VcsLogUiUtil;
|
||||
import com.intellij.vcs.log.util.VcsLogUtil;
|
||||
import com.intellij.vcs.log.visible.VisiblePack;
|
||||
@@ -55,6 +60,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.TableModelEvent;
|
||||
import javax.swing.event.TableModelListener;
|
||||
import javax.swing.table.*;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
@@ -95,7 +101,7 @@ public class VcsLogGraphTable extends TableWithProgress
|
||||
private static final Color SELECTION_FOREGROUND_INACTIVE = JBColor.namedColor("VersionControl.Log.Commit.selectionInactiveForeground",
|
||||
NamedColorUtil.getListSelectionForeground(false));
|
||||
private final @NotNull VcsLogData myLogData;
|
||||
private final @NotNull String myId;
|
||||
private final @NotNull VcsLogUiEx myLogUi;
|
||||
private final @NotNull VcsLogUiProperties myProperties;
|
||||
private final @NotNull VcsLogColorManager myColorManager;
|
||||
|
||||
@@ -113,14 +119,14 @@ public class VcsLogGraphTable extends TableWithProgress
|
||||
|
||||
private boolean myDisposed = false;
|
||||
|
||||
public VcsLogGraphTable(@NotNull String logId, @NotNull VcsLogData logData,
|
||||
public VcsLogGraphTable(@NotNull VcsLogUiEx logUi, @NotNull VcsLogData logData,
|
||||
@NotNull VcsLogUiProperties uiProperties, @NotNull VcsLogColorManager colorManager,
|
||||
@NotNull Runnable requestMore, @NotNull Disposable disposable) {
|
||||
super(new GraphTableModel(logData, requestMore, uiProperties));
|
||||
Disposer.register(disposable, this);
|
||||
|
||||
myLogData = logData;
|
||||
myId = logId;
|
||||
myLogUi = logUi;
|
||||
myProperties = uiProperties;
|
||||
myColorManager = colorManager;
|
||||
|
||||
@@ -161,6 +167,31 @@ public class VcsLogGraphTable extends TableWithProgress
|
||||
|
||||
putClientProperty(BookmarksManager.ALLOWED, true);
|
||||
ScrollingUtil.installActions(this, false);
|
||||
registerResolveLinks();
|
||||
}
|
||||
|
||||
private void registerResolveLinks() {
|
||||
GraphTableModel model = getModel();
|
||||
model.addTableModelListener(new TableModelListener() {
|
||||
@Override
|
||||
public void tableChanged(TableModelEvent e) {
|
||||
resolveLinks();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void resolveLinks() {
|
||||
if (!VcsLinksRenderer.isEnabled()) return;
|
||||
|
||||
VisiblePack visiblePack = getModel().getVisiblePack();
|
||||
VisibleGraph<Integer> visibleGraph = visiblePack.getVisibleGraph();
|
||||
if (visibleGraph.getVisibleCommitCount() == 0) return;
|
||||
|
||||
CommitLinksProvider linksProvider = CommitLinksProvider.getServiceOrNull(getLogData().getProject());
|
||||
if (linksProvider != null) {
|
||||
Couple<Integer> visibleRows = ScrollingUtil.getVisibleRows(this);
|
||||
linksProvider.resolveLinks(getId(), myLogData, visiblePack, visibleRows.first, visibleRows.second);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,7 +201,7 @@ public class VcsLogGraphTable extends TableWithProgress
|
||||
}
|
||||
|
||||
public @NotNull @NonNls String getId() {
|
||||
return myId;
|
||||
return myLogUi.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -687,6 +718,9 @@ public class VcsLogGraphTable extends TableWithProgress
|
||||
model.fireTableChanged(evt);
|
||||
}
|
||||
}
|
||||
|
||||
resolveLinks();
|
||||
|
||||
mySelectionSnapshot = null;
|
||||
});
|
||||
}
|
||||
@@ -904,10 +938,18 @@ public class VcsLogGraphTable extends TableWithProgress
|
||||
getExpandableItemsHandler().setEnabled(true);
|
||||
}
|
||||
|
||||
private static class MyLinkMouseListener extends SimpleColoredComponentLinkMouseListener {
|
||||
private class MyLinkMouseListener extends SimpleColoredComponentLinkMouseListener {
|
||||
@Override
|
||||
public @Nullable Object getTagAt(@NotNull MouseEvent e) {
|
||||
return ObjectUtils.tryCast(super.getTagAt(e), SimpleColoredComponent.BrowserLauncherTag.class);
|
||||
public boolean onClick(@NotNull MouseEvent e, int clickCount) {
|
||||
Object tag = getTagAt(e);
|
||||
if ((tag instanceof Runnable)) return super.onClick(e, clickCount);
|
||||
|
||||
if (tag instanceof NavigateToCommit navigateToCommitTag) {
|
||||
VcsLogNavigationUtil.jumpToHash(VcsLogGraphTable.this.myLogUi, navigateToCommitTag.getTarget(), false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -941,7 +983,7 @@ public class VcsLogGraphTable extends TableWithProgress
|
||||
|
||||
@Override
|
||||
public void progressChanged(@NotNull Collection<? extends VcsLogProgress.ProgressKey> keys) {
|
||||
if (VcsLogUiUtil.isProgressVisible(keys, myId)) {
|
||||
if (VcsLogUiUtil.isProgressVisible(keys, myLogUi.getId())) {
|
||||
getEmptyText().setText(VcsLogBundle.message("vcs.log.loading.status"));
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.intellij.vcs.log.ui.frame.CommitPresentationUtil
|
||||
import com.intellij.vcs.log.ui.render.GraphCommitCell
|
||||
import com.intellij.vcs.log.ui.render.GraphCommitCellRenderer
|
||||
import com.intellij.vcs.log.ui.table.*
|
||||
import com.intellij.vcs.log.ui.table.links.CommitLinksResolveListener
|
||||
import com.intellij.vcs.log.util.VcsLogUtil
|
||||
import com.intellij.vcs.log.visible.VisiblePack
|
||||
import com.intellij.vcsUtil.VcsUtil
|
||||
@@ -82,11 +83,14 @@ internal object Commit : VcsLogDefaultColumn<GraphCommitCell>("Default.Subject",
|
||||
else model.getRowInfo(row).printElements
|
||||
|
||||
val metadata = model.getCommitMetadata(row, true)
|
||||
val commitId = model.getCommitId(metadata)
|
||||
return GraphCommitCell(
|
||||
commitId,
|
||||
getValue(model, metadata),
|
||||
model.getRefsAtRow(row),
|
||||
if (metadata !is LoadingDetails) getBookmarkRefs(model.logData.project, metadata.id, metadata.root) else emptyList(),
|
||||
printElements
|
||||
printElements,
|
||||
metadata is LoadingDetails
|
||||
)
|
||||
}
|
||||
|
||||
@@ -124,10 +128,17 @@ internal object Commit : VcsLogDefaultColumn<GraphCommitCell>("Default.Subject",
|
||||
}
|
||||
})
|
||||
|
||||
table.logData.project.messageBus.connect(table).subscribe(CommitLinksResolveListener.TOPIC, CommitLinksResolveListener { logId->
|
||||
if (logId == table.id) {
|
||||
table.repaint()
|
||||
}
|
||||
})
|
||||
return commitCellRenderer
|
||||
}
|
||||
|
||||
override fun getStubValue(model: GraphTableModel): GraphCommitCell = GraphCommitCell("", emptyList(), emptyList(), emptyList())
|
||||
override fun getStubValue(model: GraphTableModel): GraphCommitCell {
|
||||
return GraphCommitCell(null, "", emptyList(), emptyList(), emptyList(), true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.vcs.log.ui.table.links
|
||||
|
||||
import com.intellij.openapi.components.serviceOrNull
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vcs.LinkDescriptor
|
||||
import com.intellij.util.messages.Topic
|
||||
import com.intellij.vcs.log.CommitId
|
||||
import com.intellij.vcs.log.data.VcsLogData
|
||||
import com.intellij.vcs.log.visible.VisiblePack
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.util.*
|
||||
|
||||
@ApiStatus.Experimental
|
||||
interface CommitLinksProvider {
|
||||
/**
|
||||
* Return cached [LinkDescriptor] for the given [CommitId].
|
||||
*/
|
||||
fun getLinks(commitId: CommitId): List<LinkDescriptor>
|
||||
|
||||
/**
|
||||
* Asynchronously search in the given commits for links and cache it.
|
||||
*
|
||||
* E.g., in the Git VCS it could be "fixup!", "squash!" and "amend!" prefixes in the commit message subject.
|
||||
*/
|
||||
fun resolveLinks(logId: String, logData: VcsLogData, visiblePack: VisiblePack,
|
||||
startRow: Int, endRow: Int)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getServiceOrNull(project: Project) = project.serviceOrNull<CommitLinksProvider>()
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
fun interface CommitLinksResolveListener : EventListener {
|
||||
companion object {
|
||||
@JvmField
|
||||
@Topic.ProjectLevel
|
||||
val TOPIC = Topic(CommitLinksResolveListener::class.java, Topic.BroadcastDirection.NONE)
|
||||
}
|
||||
|
||||
fun onLinksResolved(logId: String)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.vcs.log.ui.table.links
|
||||
|
||||
import com.intellij.openapi.components.serviceOrNull
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.vcs.IssueNavigationConfiguration
|
||||
import com.intellij.openapi.vcs.IssueNavigationConfiguration.LinkMatch
|
||||
import com.intellij.openapi.vcs.LinkDescriptor
|
||||
import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkRenderer
|
||||
import com.intellij.ui.SimpleColoredComponent
|
||||
import com.intellij.ui.SimpleColoredComponent.BrowserLauncherTag
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.util.text.TextRangeUtil
|
||||
import com.intellij.vcs.log.CommitId
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
internal class VcsLinksRenderer(project: Project,
|
||||
private val coloredComponent: SimpleColoredComponent,
|
||||
private val commitPrefixLinkRenderer: CommitLinksProvider?) {
|
||||
|
||||
constructor(project: Project, coloredComponent: SimpleColoredComponent) :
|
||||
this(project, coloredComponent, project.serviceOrNull<CommitLinksProvider>())
|
||||
|
||||
private val issueNavigationConfiguration = IssueNavigationConfiguration.getInstance(project);
|
||||
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
fun appendTextWithLinks(textWithLinks: @NlsSafe String, baseStyle: SimpleTextAttributes, commitId: CommitId?) {
|
||||
val issuesLinks = issueNavigationConfiguration.findIssueLinks(textWithLinks)
|
||||
|
||||
val linksToCommits =
|
||||
if (commitId != null && commitPrefixLinkRenderer != null) {
|
||||
commitPrefixLinkRenderer.getLinks(commitId)
|
||||
}
|
||||
else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
if (issuesLinks.isEmpty() && linksToCommits.isEmpty()) {
|
||||
coloredComponent.append(textWithLinks, baseStyle)
|
||||
return
|
||||
}
|
||||
|
||||
val allMatched =
|
||||
(issuesLinks.filterNot { TextRangeUtil.intersectsOneOf(it.range, linksToCommits.map(LinkDescriptor::range)) } + linksToCommits)
|
||||
.sortedWith { l1, l2 -> TextRangeUtil.RANGE_COMPARATOR.compare(l1.range, l2.range) }
|
||||
|
||||
val linkStyle = IssueLinkRenderer.getLinkAttributes(baseStyle)
|
||||
|
||||
var processedOffset = 0
|
||||
for (link in allMatched) {
|
||||
val textRange = link.range
|
||||
|
||||
if (textRange.startOffset > processedOffset) {
|
||||
coloredComponent.append(textWithLinks.substring(processedOffset, textRange.startOffset), baseStyle)
|
||||
}
|
||||
|
||||
coloredComponent.append(textRange.substring(textWithLinks), linkStyle, link.asTag())
|
||||
|
||||
processedOffset = textRange.endOffset
|
||||
}
|
||||
|
||||
if (processedOffset < textWithLinks.length) {
|
||||
coloredComponent.append(textWithLinks.substring(processedOffset), baseStyle)
|
||||
}
|
||||
}
|
||||
|
||||
private fun LinkDescriptor?.asTag(): Any? {
|
||||
return when (this) {
|
||||
is NavigateToCommit -> this
|
||||
is LinkMatch -> BrowserLauncherTag(targetUrl)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun isEnabled() = Registry.`is`("vcs.log.render.commit.links", false)
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
class NavigateToCommit(override val range: TextRange, val target: String) : LinkDescriptor
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.vcs.log.data;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
@@ -6,6 +6,7 @@ import com.intellij.openapi.progress.EmptyProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.progress.impl.ProgressManagerImpl;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ExceptionUtil;
|
||||
@@ -31,11 +32,10 @@ public class VcsLogRefresherTest extends VcsPlatformTest {
|
||||
private static final int RECENT_COMMITS_COUNT = 2;
|
||||
private TestVcsLogProvider myLogProvider;
|
||||
private VcsLogData myLogData;
|
||||
private Map<VirtualFile, VcsLogProvider> myLogProviders;
|
||||
|
||||
private LogRefresherTestHelper myRefresherTestHelper;
|
||||
private DataWaiter myDataWaiter;
|
||||
private VcsLogRefresher myLoader;
|
||||
private final List<Future<?>> myStartedTasks = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
private List<String> myCommits;
|
||||
|
||||
@@ -44,24 +44,24 @@ public class VcsLogRefresherTest extends VcsPlatformTest {
|
||||
super.setUp();
|
||||
|
||||
myLogProvider = new TestVcsLogProvider();
|
||||
myLogProviders = Collections.singletonMap(getProjectRoot(), myLogProvider);
|
||||
Map<VirtualFile, VcsLogProvider> logProviders = Collections.singletonMap(getProjectRoot(), myLogProvider);
|
||||
|
||||
myCommits = Arrays.asList("3|-a2|-a1", "2|-a1|-a", "1|-a|-");
|
||||
myLogProvider.appendHistory(log(myCommits));
|
||||
myLogProvider.addRef(createBranchRef("master", "a2"));
|
||||
myLogData =
|
||||
new VcsLogData(myProject, logProviders, new LoggingErrorHandler(LOG), VcsLogSharedSettings.isIndexSwitchedOn(getProject()),
|
||||
myProject);
|
||||
myRefresherTestHelper = new LogRefresherTestHelper(myLogData, RECENT_COMMITS_COUNT);
|
||||
|
||||
myDataWaiter = new DataWaiter();
|
||||
myLoader = createLoader(myDataWaiter);
|
||||
myDataWaiter = myRefresherTestHelper.myDataWaiter;
|
||||
myLoader = myRefresherTestHelper.myLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() {
|
||||
try {
|
||||
assertNoMoreResultsArrive();
|
||||
myDataWaiter.tearDown();
|
||||
if (myDataWaiter.failed()) {
|
||||
fail("Only one refresh should have happened, an error happened instead: " + myDataWaiter.getExceptionText());
|
||||
}
|
||||
myRefresherTestHelper.tearDown();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
addSuppressedException(e);
|
||||
@@ -84,7 +84,7 @@ public class VcsLogRefresherTest extends VcsPlatformTest {
|
||||
myLogProvider.unblockFullLog();
|
||||
assertNotNull(result);
|
||||
assertDataPack(log(myCommits.subList(0, RECENT_COMMITS_COUNT)), result.getPermanentGraph().getAllCommits());
|
||||
waitForBackgroundTasksToComplete();
|
||||
myRefresherTestHelper.waitForBackgroundTasksToComplete();
|
||||
myDataWaiter.get();
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public class VcsLogRefresherTest extends VcsPlatformTest {
|
||||
myLogProvider.blockFullLog();
|
||||
myLoader.initialize();
|
||||
myDataWaiter.get();
|
||||
assertTimeout("Refresh waiter should have failed on the timeout");
|
||||
myRefresherTestHelper.assertTimeout("Refresh waiter should have failed on the timeout");
|
||||
myLogProvider.unblockFullLog();
|
||||
|
||||
DataPack result = myDataWaiter.get();
|
||||
@@ -110,7 +110,7 @@ public class VcsLogRefresherTest extends VcsPlatformTest {
|
||||
}
|
||||
|
||||
public void test_refresh_captures_new_commits() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
initAndWaitForFirstRefresh();
|
||||
myRefresherTestHelper.initAndWaitForFirstRefresh();
|
||||
|
||||
String newCommit = "4|-a3|-a2";
|
||||
myLogProvider.appendHistory(log(newCommit));
|
||||
@@ -124,7 +124,7 @@ public class VcsLogRefresherTest extends VcsPlatformTest {
|
||||
}
|
||||
|
||||
public void test_single_refresh_causes_single_data_read() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
initAndWaitForFirstRefresh();
|
||||
myRefresherTestHelper.initAndWaitForFirstRefresh();
|
||||
|
||||
myLogProvider.resetReadFirstBlockCounter();
|
||||
myLoader.refresh(Collections.singletonList(getProjectRoot()), false);
|
||||
@@ -133,7 +133,7 @@ public class VcsLogRefresherTest extends VcsPlatformTest {
|
||||
}
|
||||
|
||||
public void test_reinitialize_makes_refresh_cancelled() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
initAndWaitForFirstRefresh();
|
||||
myRefresherTestHelper.initAndWaitForFirstRefresh();
|
||||
|
||||
// initiate the refresh and make it hang
|
||||
myLogProvider.blockRefresh();
|
||||
@@ -146,68 +146,96 @@ public class VcsLogRefresherTest extends VcsPlatformTest {
|
||||
// we want to make sure only one data pack is reported
|
||||
myLogProvider.unblockRefresh();
|
||||
myDataWaiter.get();
|
||||
assertNoMoreResultsArrive();
|
||||
}
|
||||
|
||||
private void assertNoMoreResultsArrive() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
waitForBackgroundTasksToComplete();
|
||||
assertTrue(myDataWaiter.myQueue.isEmpty());
|
||||
}
|
||||
|
||||
private void waitForBackgroundTasksToComplete() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
for (Future<?> task : new ArrayList<>(myStartedTasks)) {
|
||||
task.get(1, TimeUnit.SECONDS);
|
||||
}
|
||||
myRefresherTestHelper.assertNoMoreResultsArrive();
|
||||
}
|
||||
|
||||
public void test_two_immediately_consecutive_refreshes_causes_only_one_data_pack_update()
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
initAndWaitForFirstRefresh();
|
||||
myRefresherTestHelper.initAndWaitForFirstRefresh();
|
||||
myLogProvider.blockRefresh();
|
||||
myLoader.refresh(Collections.singletonList(getProjectRoot()), false); // this refresh hangs in VcsLogProvider.readFirstBlock()
|
||||
myLoader.refresh(Collections.singletonList(getProjectRoot()), false); // this refresh is queued
|
||||
myLogProvider.unblockRefresh(); // this will make the first one complete, and then perform the second as well
|
||||
|
||||
myDataWaiter.get();
|
||||
assertTimeout("Second refresh shouldn't cause the data pack update"); // it may also fail in beforehand in set().
|
||||
myRefresherTestHelper.assertTimeout("Second refresh shouldn't cause the data pack update"); // it may also fail in beforehand in set().
|
||||
}
|
||||
|
||||
private void initAndWaitForFirstRefresh() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
// wait for the first block and the whole log to complete
|
||||
myLoader.initialize();
|
||||
public static class LogRefresherTestHelper {
|
||||
|
||||
DataPack firstDataPack = myDataWaiter.get();
|
||||
assertFalse(firstDataPack.isFull());
|
||||
private final @NotNull Project myProject;
|
||||
|
||||
DataPack fullDataPack = myDataWaiter.get();
|
||||
assertTrue(fullDataPack.isFull());
|
||||
assertNoMoreResultsArrive();
|
||||
}
|
||||
private final @NotNull DataWaiter myDataWaiter;
|
||||
private final @NotNull VcsLogRefresher myLoader;
|
||||
|
||||
private void assertTimeout(@NotNull String message) throws InterruptedException {
|
||||
assertNull(message, myDataWaiter.myQueue.poll(500, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
private final List<Future<?>> myStartedTasks = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
private VcsLogRefresherImpl createLoader(Consumer<? super DataPack> dataPackConsumer) {
|
||||
myLogData = new VcsLogData(myProject, myLogProviders, new LoggingErrorHandler(LOG), VcsLogSharedSettings.isIndexSwitchedOn(getProject()),
|
||||
myProject);
|
||||
VcsLogRefresherImpl refresher =
|
||||
new VcsLogRefresherImpl(myProject, myLogData.getStorage(), myLogProviders, myLogData.getUserRegistry(),
|
||||
myLogData.getModifiableIndex(),
|
||||
new VcsLogProgress(myLogData),
|
||||
myLogData.getTopCommitsCache(), dataPackConsumer, RECENT_COMMITS_COUNT
|
||||
) {
|
||||
@Override
|
||||
protected SingleTaskController.SingleTask startNewBackgroundTask(final @NotNull Task.Backgroundable refreshTask) {
|
||||
LOG.debug("Starting a background task...");
|
||||
Future<?> future = ((ProgressManagerImpl)ProgressManager.getInstance()).runProcessWithProgressAsynchronously(refreshTask);
|
||||
myStartedTasks.add(future);
|
||||
LOG.debug(myStartedTasks.size() + " started tasks");
|
||||
return new SingleTaskController.SingleTaskImpl(future, new EmptyProgressIndicator());
|
||||
}
|
||||
};
|
||||
Disposer.register(myLogData, refresher);
|
||||
return refresher;
|
||||
public LogRefresherTestHelper(@NotNull VcsLogData logData, int recentCommitsCount) {
|
||||
myProject = logData.getProject();
|
||||
myDataWaiter = new DataWaiter();
|
||||
myLoader = createLoader(logData, recentCommitsCount, myDataWaiter);
|
||||
}
|
||||
|
||||
public void initAndWaitForFirstRefresh() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
// wait for the first block and the whole log to complete
|
||||
myLoader.initialize();
|
||||
|
||||
DataPack firstDataPack = myDataWaiter.get();
|
||||
assertFalse(firstDataPack.isFull());
|
||||
|
||||
DataPack fullDataPack = myDataWaiter.get();
|
||||
assertTrue(fullDataPack.isFull());
|
||||
assertNoMoreResultsArrive();
|
||||
}
|
||||
|
||||
public void tearDown() throws ExecutionException, InterruptedException, TimeoutException {
|
||||
assertNoMoreResultsArrive();
|
||||
myDataWaiter.tearDown();
|
||||
if (myDataWaiter.failed()) {
|
||||
fail("Only one refresh should have happened, an error happened instead: " + myDataWaiter.getExceptionText());
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull DataPack getDataPack() {
|
||||
return myLoader.getCurrentDataPack();
|
||||
}
|
||||
|
||||
private void assertTimeout(@NotNull String message) throws InterruptedException {
|
||||
assertNull(message, myDataWaiter.myQueue.poll(500, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
private void assertNoMoreResultsArrive() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
waitForBackgroundTasksToComplete();
|
||||
assertTrue(myDataWaiter.myQueue.isEmpty());
|
||||
}
|
||||
|
||||
private void waitForBackgroundTasksToComplete() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
for (Future<?> task : new ArrayList<>(myStartedTasks)) {
|
||||
task.get(1, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private VcsLogRefresherImpl createLoader(@NotNull VcsLogData logData, int recentCommitsCount,
|
||||
@NotNull Consumer<? super DataPack> dataPackConsumer) {
|
||||
|
||||
VcsLogRefresherImpl refresher =
|
||||
new VcsLogRefresherImpl(myProject, logData.getStorage(), logData.getLogProviders(), logData.getUserRegistry(),
|
||||
logData.getModifiableIndex(),
|
||||
new VcsLogProgress(logData),
|
||||
logData.getTopCommitsCache(), dataPackConsumer, recentCommitsCount
|
||||
) {
|
||||
@Override
|
||||
protected SingleTaskController.SingleTask startNewBackgroundTask(final @NotNull Task.Backgroundable refreshTask) {
|
||||
LOG.debug("Starting a background task...");
|
||||
Future<?> future = ((ProgressManagerImpl)ProgressManager.getInstance()).runProcessWithProgressAsynchronously(refreshTask);
|
||||
myStartedTasks.add(future);
|
||||
LOG.debug(myStartedTasks.size() + " started tasks");
|
||||
return new SingleTaskController.SingleTaskImpl(future, new EmptyProgressIndicator());
|
||||
}
|
||||
};
|
||||
Disposer.register(logData, refresher);
|
||||
return refresher;
|
||||
}
|
||||
}
|
||||
|
||||
private void assertDataPack(@NotNull List<TimedVcsCommit> expectedLog, @NotNull List<? extends GraphCommit<Integer>> actualLog) {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.vcs.log.ui.table.links
|
||||
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.vcs.IssueNavigationConfiguration
|
||||
import com.intellij.openapi.vcs.IssueNavigationLink
|
||||
import com.intellij.ui.SimpleColoredComponent
|
||||
import com.intellij.ui.SimpleTextAttributes.REGULAR_ATTRIBUTES
|
||||
import com.intellij.vcs.log.CommitId
|
||||
import com.intellij.vcs.log.Hash
|
||||
import com.intellij.vcs.test.VcsPlatformTest
|
||||
import org.mockito.Mockito
|
||||
|
||||
class VcsLinksRendererTest : VcsPlatformTest() {
|
||||
private lateinit var linksRenderer: VcsLinksRenderer
|
||||
|
||||
fun `test single prefix match`() {
|
||||
val text = "fixup! some text"
|
||||
val linksProvider = Mockito.mock(CommitLinksProvider::class.java)
|
||||
val emptyCommitId = CommitId(EMPTY_HASH, projectRoot)
|
||||
|
||||
Mockito.`when`(linksProvider.getLinks(emptyCommitId))
|
||||
.thenReturn(listOf("fixup!".toLink()))
|
||||
val textComponent = SimpleColoredComponent()
|
||||
linksRenderer = VcsLinksRenderer(project, textComponent, linksProvider)
|
||||
|
||||
linksRenderer.appendTextWithLinks(text, REGULAR_ATTRIBUTES, emptyCommitId)
|
||||
|
||||
assertTrue(textComponent.fragmentCount == 2)
|
||||
assertEquals(text, textComponent.toString())
|
||||
}
|
||||
|
||||
fun `test multiple prefix match`() {
|
||||
val text = "fixup! fixup! squash! some text"
|
||||
val emptyCommitId = CommitId(EMPTY_HASH, projectRoot)
|
||||
val linksProvider = Mockito.mock(CommitLinksProvider::class.java)
|
||||
|
||||
Mockito.`when`(linksProvider.getLinks(emptyCommitId))
|
||||
.thenReturn(listOf("fixup!".toLink(), "fixup!".toLink(7), "squash!".toLink(14)))
|
||||
val textComponent = SimpleColoredComponent()
|
||||
linksRenderer = VcsLinksRenderer(project, textComponent, linksProvider)
|
||||
|
||||
linksRenderer.appendTextWithLinks(text, REGULAR_ATTRIBUTES, emptyCommitId)
|
||||
|
||||
assertTrue(textComponent.fragmentCount == 6)
|
||||
assertEquals(text, textComponent.toString())
|
||||
}
|
||||
|
||||
fun `test multiple prefix with issue links match`() {
|
||||
val text = "fixup! fixup! squash! some IDEA-1234 text IDEA-4567"
|
||||
val linksProvider = Mockito.mock(CommitLinksProvider::class.java)
|
||||
val emptyCommitId = CommitId(EMPTY_HASH, projectRoot)
|
||||
IssueNavigationConfiguration.getInstance(project).links = listOf(
|
||||
IssueNavigationLink("\\b[A-Z]+\\-\\d+\\b", "http://example.com/$0")
|
||||
)
|
||||
Mockito.`when`(linksProvider.getLinks(emptyCommitId))
|
||||
.thenReturn(listOf("fixup!".toLink(), "fixup!".toLink(7), "squash!".toLink(14)))
|
||||
val textComponent = SimpleColoredComponent()
|
||||
linksRenderer = VcsLinksRenderer(project, textComponent, linksProvider)
|
||||
|
||||
linksRenderer.appendTextWithLinks(text, REGULAR_ATTRIBUTES, emptyCommitId)
|
||||
|
||||
assertTrue(textComponent.fragmentCount == 9)
|
||||
assertEquals(text, textComponent.toString())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val EMPTY_HASH = object : Hash {
|
||||
override fun asString(): String = ""
|
||||
|
||||
override fun toShortString(): String = ""
|
||||
}
|
||||
|
||||
private fun String.toLink(offset: Int = 0) = NavigateToCommit(TextRange.from(offset, length), "")
|
||||
}
|
||||
}
|
||||
@@ -847,6 +847,8 @@
|
||||
<history.activityPresentationProvider implementation="git4idea.GitActivityPresentationProvider"/>
|
||||
|
||||
<searchScopesProvider implementation="git4idea.search.GitSearchScopeProvider"/>
|
||||
<projectService serviceInterface="com.intellij.vcs.log.ui.table.links.CommitLinksProvider"
|
||||
serviceImplementation="git4idea.log.GitCommitLinkProvider"/>
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="Git4Idea">
|
||||
|
||||
205
plugins/git4idea/src/git4idea/log/GitLinkToCommitResolver.kt
Normal file
205
plugins/git4idea/src/git4idea/log/GitLinkToCommitResolver.kt
Normal file
@@ -0,0 +1,205 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package git4idea.log
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.vcs.LinkDescriptor
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.intellij.util.ui.update.MergingUpdateQueue
|
||||
import com.intellij.util.ui.update.Update
|
||||
import com.intellij.vcs.log.CommitId
|
||||
import com.intellij.vcs.log.VcsCommitMetadata
|
||||
import com.intellij.vcs.log.data.VcsLogData
|
||||
import com.intellij.vcs.log.graph.impl.facade.VisibleGraphImpl
|
||||
import com.intellij.vcs.log.graph.utils.DfsWalk
|
||||
import com.intellij.vcs.log.graph.utils.LinearGraphUtils
|
||||
import com.intellij.vcs.log.graph.utils.isAncestor
|
||||
import com.intellij.vcs.log.ui.table.links.CommitLinksProvider
|
||||
import com.intellij.vcs.log.ui.table.links.CommitLinksResolveListener
|
||||
import com.intellij.vcs.log.ui.table.links.NavigateToCommit
|
||||
import com.intellij.vcs.log.visible.VisiblePack
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
internal class GitCommitLinkProvider(private val project: Project) : CommitLinksProvider {
|
||||
override fun getLinks(commitId: CommitId): List<LinkDescriptor> {
|
||||
return project.service<GitLinkToCommitResolver>().getLinks(commitId)
|
||||
}
|
||||
|
||||
override fun resolveLinks(logId: String, logData: VcsLogData, visiblePack: VisiblePack,
|
||||
startRow: Int, endRow: Int) {
|
||||
project.service<GitLinkToCommitResolver>().submitResolveLinks(logId, logData, visiblePack,
|
||||
startRow, endRow)
|
||||
}
|
||||
}
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
internal class GitLinkToCommitResolver(private val project: Project) {
|
||||
|
||||
companion object {
|
||||
private const val PREFIX_DELIMITER_LENGTH = 1
|
||||
private val prefixes = listOf("fixup!", "squash!", "amend!")
|
||||
private val regex = "^(${prefixes.joinToString("|")}) (.*)$".toRegex()
|
||||
|
||||
private const val CACHE_MAX_SIZE = 1_000L
|
||||
}
|
||||
|
||||
private val prefixesCache: Cache<CommitId, List<PrefixTarget>> =
|
||||
Caffeine.newBuilder()
|
||||
.maximumSize(CACHE_MAX_SIZE)
|
||||
.build()
|
||||
|
||||
private val resolveQueue = MergingUpdateQueue("resolve links queue", 100, true, null, project, null, false)
|
||||
private val updateQueue = MergingUpdateQueue("after resolve links ui update queue", 100, true, null, project, null, true)
|
||||
|
||||
internal fun getLinks(commitId: CommitId): List<LinkDescriptor> {
|
||||
return getCachedOrEmpty(commitId).map { NavigateToCommit(it.range, it.targetHash) }
|
||||
}
|
||||
|
||||
internal fun submitResolveLinks(
|
||||
logId: String,
|
||||
logData: VcsLogData,
|
||||
visiblePack: VisiblePack,
|
||||
startRow: Int,
|
||||
endRow: Int
|
||||
) {
|
||||
if (visiblePack.visibleGraph !is VisibleGraphImpl) return
|
||||
|
||||
val startFrom = max(0, startRow)
|
||||
val end = max(0, endRow)
|
||||
val rowRange = startFrom..end
|
||||
|
||||
val processingCount = min(abs(Registry.intValue("vcs.log.render.commit.links.process.chunk")), rowRange.count())
|
||||
if (processingCount < 2) return
|
||||
|
||||
val visibleGraph = visiblePack.visibleGraph
|
||||
|
||||
resolveQueue.queue(Update.create(logId + startFrom) {
|
||||
for (i in rowRange) {
|
||||
val commitId = visibleGraph.getRowInfo(i).commit
|
||||
val commit = logData.commitMetadataCache.getCachedData(commitId) ?: continue
|
||||
resolveLinks(logData, visiblePack, commit.getCommitId(), commit.subject, processingCount)
|
||||
}
|
||||
updateQueue.queue(Update.create(logId) {
|
||||
project.messageBus.syncPublisher(CommitLinksResolveListener.TOPIC).onLinksResolved(logId)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
internal fun resolveLinks(
|
||||
logData: VcsLogData,
|
||||
visiblePack: VisiblePack,
|
||||
commitId: CommitId, commitMessage: String,
|
||||
processingCount: Int
|
||||
) {
|
||||
if (visiblePack.visibleGraph !is VisibleGraphImpl) return
|
||||
|
||||
val cachedPrefixes = getCachedOrEmpty(commitId)
|
||||
if (cachedPrefixes.isNotEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
var match = regex.matchEntire(commitMessage) ?: return
|
||||
var prefix = match.groups[1]?.value.orEmpty()
|
||||
var rest = match.groups[2]?.value.orEmpty()
|
||||
if (prefix.isBlank() && rest.isBlank()) return
|
||||
|
||||
var prefixOffset = 0
|
||||
val existingPrefixes = cachedPrefixes.toMutableList()
|
||||
|
||||
while (prefix.isNotBlank() && rest.isNotBlank()) {
|
||||
|
||||
if (prefix.isNotBlank()) {
|
||||
val prefixRange = TextRange.from(prefixOffset, prefix.length)
|
||||
|
||||
val targetHash = resolveHash(logData, visiblePack, commitId, rest, processingCount)
|
||||
if (targetHash != null) {
|
||||
existingPrefixes.add(PrefixTarget(prefixRange, targetHash))
|
||||
}
|
||||
|
||||
prefixOffset += prefix.length + PREFIX_DELIMITER_LENGTH
|
||||
}
|
||||
|
||||
match = regex.matchEntire(rest) ?: break
|
||||
prefix = match.groups[1]?.value.orEmpty()
|
||||
rest = match.groups[2]?.value.orEmpty()
|
||||
}
|
||||
|
||||
if (existingPrefixes.isNotEmpty()) {
|
||||
prefixesCache.put(commitId, existingPrefixes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveHash(
|
||||
logData: VcsLogData,
|
||||
visiblePack: VisiblePack,
|
||||
commitId: CommitId, commitMessage: String,
|
||||
processingCount: Int
|
||||
): String? {
|
||||
val sourceCommitId = logData.getCommitIndex(commitId.hash, commitId.root)
|
||||
val visibleGraph = visiblePack.visibleGraph as VisibleGraphImpl
|
||||
val sourceCommitNodeIndex = visibleGraph.getVisibleRowIndex(sourceCommitId) ?: return null
|
||||
val liteLinearGraph = LinearGraphUtils.asLiteLinearGraph(visibleGraph.linearGraph)
|
||||
|
||||
var foundData: VcsCommitMetadata? = null
|
||||
iterateCommits(logData, visiblePack, sourceCommitNodeIndex, processingCount) { currentData ->
|
||||
val currentNodeId = visibleGraph.getVisibleRowIndex(currentData.getCommitIndex(logData))
|
||||
if (currentNodeId != null && currentData.subject == commitMessage
|
||||
&& liteLinearGraph.isAncestor(currentNodeId, sourceCommitNodeIndex)
|
||||
) {
|
||||
foundData = currentData
|
||||
}
|
||||
|
||||
foundData != null
|
||||
}
|
||||
|
||||
return foundData?.id?.toString()
|
||||
}
|
||||
|
||||
private fun iterateCommits(
|
||||
logData: VcsLogData, visiblePack: VisiblePack,
|
||||
startFromCommitIndex: Int,
|
||||
commitsCount: Int,
|
||||
consumer: (VcsCommitMetadata) -> Boolean
|
||||
) {
|
||||
val visibleGraph = visiblePack.visibleGraph as VisibleGraphImpl
|
||||
val linearGraph = visibleGraph.linearGraph
|
||||
|
||||
val walkDepth = min(commitsCount, visibleGraph.visibleCommitCount)
|
||||
var visitedNode = 0
|
||||
DfsWalk(listOf(startFromCommitIndex), linearGraph).walk(true) { currentNodeId ->
|
||||
|
||||
val currentCommitIndex = visibleGraph.getRowInfo(currentNodeId).commit
|
||||
val currentCommitData = logData.commitMetadataCache.getCachedData(currentCommitIndex)
|
||||
var consumed = false
|
||||
|
||||
visitedNode++
|
||||
if (visitedNode > walkDepth) return@walk false
|
||||
|
||||
if (currentCommitData != null) {
|
||||
if (currentCommitData.parents.size > 1) return@walk false
|
||||
|
||||
consumed = consumer(currentCommitData)
|
||||
}
|
||||
|
||||
!consumed
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCachedOrEmpty(commitId: CommitId): List<PrefixTarget> {
|
||||
return prefixesCache.getIfPresent(commitId) ?: emptyList()
|
||||
}
|
||||
|
||||
private fun VcsCommitMetadata.getCommitIndex(logData: VcsLogData): Int = logData.getCommitIndex(id, root)
|
||||
|
||||
private fun VcsCommitMetadata.getCommitId(): CommitId = CommitId(id, root)
|
||||
|
||||
private data class PrefixTarget(val range: TextRange, val targetHash: String)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package git4idea.log
|
||||
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.vcs.LinkDescriptor
|
||||
import com.intellij.vcs.log.CommitId
|
||||
import com.intellij.vcs.log.data.VcsLogData
|
||||
import com.intellij.vcs.log.data.VcsLogRefresherTest.LogRefresherTestHelper
|
||||
import com.intellij.vcs.log.graph.PermanentGraph
|
||||
import com.intellij.vcs.log.impl.HashImpl
|
||||
import com.intellij.vcs.log.impl.VcsProjectLog
|
||||
import com.intellij.vcs.log.ui.table.links.NavigateToCommit
|
||||
import com.intellij.vcs.log.visible.VisiblePack
|
||||
import com.intellij.vcs.log.visible.filters.VcsLogFilterObject
|
||||
import git4idea.test.GitSingleRepoTest
|
||||
import git4idea.test.commit
|
||||
|
||||
class GitLinkToCommitResolverTest : GitSingleRepoTest() {
|
||||
|
||||
private lateinit var logData: VcsLogData
|
||||
|
||||
private lateinit var logRefresherHelper: LogRefresherTestHelper
|
||||
private lateinit var visiblePack: VisiblePack
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
|
||||
if (VcsProjectLog.ensureLogCreated(project)) {
|
||||
val projectLog = VcsProjectLog.getInstance(project)
|
||||
logData = projectLog.dataManager!!
|
||||
logRefresherHelper = LogRefresherTestHelper(logData, VcsLogData.getRecentCommitsCount())
|
||||
}
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
try {
|
||||
logRefresherHelper.tearDown()
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
addSuppressedException(e)
|
||||
}
|
||||
finally {
|
||||
super.tearDown()
|
||||
}
|
||||
}
|
||||
|
||||
fun `test resolve single fixup`() {
|
||||
val fixupCommitMsg = "fixup! [subsystem] add file 1"
|
||||
val commitMsg = "[subsystem] add file 1"
|
||||
|
||||
file("1.txt").create("File 1 content").add()
|
||||
val commitHash = repo.commit(commitMsg)
|
||||
file("2.txt").create("File 2 content").add()
|
||||
val fixupCommitHash = HashImpl.build(repo.commit(fixupCommitMsg))
|
||||
|
||||
refreshVisibleGraph()
|
||||
|
||||
val resolver = project.service<GitLinkToCommitResolver>()
|
||||
resolver.resolveLinks(CommitId(fixupCommitHash, repo.root), fixupCommitMsg)
|
||||
val links = resolver.getLinks(CommitId(fixupCommitHash, repo.root))
|
||||
|
||||
assertTrue(links.size == 1)
|
||||
assertEquals(links[0].target, commitHash)
|
||||
assertEquals(links[0].range, TextRange.from(0, "fixup!".length))
|
||||
assertEquals(links[0].range.substring(fixupCommitMsg), "fixup!")
|
||||
}
|
||||
|
||||
fun `test resolve multiple prefixes`() {
|
||||
val squashCommitMsg = "fixup! squash! add file 1"
|
||||
val fixup2CommitMsg = "fixup! fixup! add file 1"
|
||||
val fixup1CommitMsg = "fixup! add file 1"
|
||||
val commitMsg = "add file 1"
|
||||
|
||||
file("1.txt").create("File 1 content").add()
|
||||
val commitHash = repo.commit(commitMsg)
|
||||
file("2.txt").create("File 2 content").add()
|
||||
val fixup1CommitHash = HashImpl.build(repo.commit(fixup1CommitMsg))
|
||||
file("3.txt").create("File 3 content").add()
|
||||
val fixup2CommitHash = HashImpl.build(repo.commit(fixup2CommitMsg))
|
||||
file("4.txt").create("File 4 content").add()
|
||||
val squashCommitHash = HashImpl.build(repo.commit(squashCommitMsg))
|
||||
|
||||
refreshVisibleGraph()
|
||||
|
||||
val resolver = project.service<GitLinkToCommitResolver>()
|
||||
resolver.resolveLinks(CommitId(fixup1CommitHash, repo.root), fixup1CommitMsg)
|
||||
resolver.resolveLinks(CommitId(fixup2CommitHash, repo.root), fixup2CommitMsg)
|
||||
resolver.resolveLinks(CommitId(squashCommitHash, repo.root), squashCommitMsg)
|
||||
|
||||
var links = resolver.getLinks(CommitId(fixup1CommitHash, repo.root))
|
||||
assertTrue(links.size == 1)
|
||||
assertEquals(links[0].target, commitHash)
|
||||
assertEquals(links[0].range, TextRange.from(0, "fixup!".length))
|
||||
assertEquals(links[0].range.substring(fixup1CommitMsg), "fixup!")
|
||||
|
||||
links = resolver.getLinks(CommitId(fixup2CommitHash, repo.root))
|
||||
assertTrue(links.size == 2)
|
||||
assertEquals(links[0].target, fixup1CommitHash.toString())
|
||||
assertEquals(links[0].range, TextRange.from(0, "fixup!".length))
|
||||
assertEquals(links[0].range.substring(fixup2CommitMsg), "fixup!")
|
||||
assertEquals(links[1].target, commitHash)
|
||||
assertEquals(links[1].range, TextRange.from(7, "fixup!".length))
|
||||
assertEquals(links[1].range.substring(fixup2CommitMsg), "fixup!")
|
||||
|
||||
links = resolver.getLinks(CommitId(squashCommitHash, repo.root))
|
||||
assertTrue(links.size == 1)
|
||||
assertEquals(links[0].target, commitHash)
|
||||
assertEquals(links[0].range, TextRange.from(7, "squash!".length))
|
||||
assertEquals(links[0].range.substring(squashCommitMsg), "squash!")
|
||||
}
|
||||
|
||||
private fun GitLinkToCommitResolver.resolveLinks(commitId: CommitId, commitMessage: @NlsSafe String) {
|
||||
resolveLinks(logData, visiblePack, commitId, commitMessage, Registry.intValue("vcs.log.render.commit.links.process.chunk"))
|
||||
}
|
||||
|
||||
private fun refreshVisibleGraph() {
|
||||
logRefresherHelper.initAndWaitForFirstRefresh()
|
||||
val dataPack = logRefresherHelper.dataPack
|
||||
|
||||
val visibleGraph = dataPack.permanentGraph.createVisibleGraph(PermanentGraph.Options.Default, null, null)
|
||||
visiblePack = VisiblePack(dataPack, visibleGraph, false, VcsLogFilterObject.collection())
|
||||
}
|
||||
|
||||
private val LinkDescriptor.target get() = (this as NavigateToCommit).target
|
||||
}
|
||||
Reference in New Issue
Block a user