Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## [2025.3.1]

### Fixes

- Fixed [runtime issues on 2025.3 line of IDEs](https://github.com/comod/git-scope-pro/issues/72)

### Added

- Added [support to reorder git scope tabs](https://github.com/comod/git-scope-pro/issues/73)

## [2025.3]

### Fixes
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
pluginGroup=org.woelkit.plugins
pluginName=Git Scope
pluginRepositoryUrl=https://github.com/comod/git-scope-pro
pluginVersion=2025.3
pluginVersion=2025.3.1
pluginSinceBuild=243

platformType=IU
#platformVersion=LATEST-EAP-SNAPSHOT
platformVersion=2025.2.5
platformVersion=2025.3

platformBundledPlugins=Git4Idea
gradleVersion=9.2.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ public void handleSwitchedToCommitDiff() {
/**
* Handle editor selection changed away from a commit diff editor.
* Call this when the user switches away from a commit panel diff tab.
*
* Note: This safely restores ALL tracked documents. Documents that still have
* commit diff editors open will be skipped by restoreCustomBaseForDocument().
*/
public void handleSwitchedAwayFromCommitDiff() {
restoreCustomBaseForAllDocuments();
Expand Down Expand Up @@ -271,6 +274,18 @@ private void scheduleActivationIfCommitDiffSelected() {
});
}

/**
* Activate HEAD base for all documents that have commit diff editors.
*
* Design note: This activates HEAD base for ALL documents with commit diffs, not just
* the currently visible one. This is intentional - when viewing commit diffs, we enter
* "commit review mode" where all files should show diffs against HEAD, not custom base.
* This provides consistent behavior and avoids confusion when switching between files.
*
* The restoration logic (via hasCommitDiffEditorsFor checks) ensures that when commit
* diffs are closed, only documents without any remaining commit diff editors get their
* custom base restored.
*/
private void activateHeadBaseForAllCommitDiffs() {
synchronized (this) {
for (Map.Entry<Document, Set<Editor>> entry : commitDiffEditors.entrySet()) {
Expand Down Expand Up @@ -335,6 +350,11 @@ private void restoreCustomBaseForDocument(@NotNull Document doc) {
return;
}

// Fix Hole #2: Don't restore if commit diff editors are still open for this document
if (hasCommitDiffEditorsFor(doc)) {
return;
}

String customContent = baseRevisionSwitcher.getCachedCustomBaseContent(doc);
if (customContent != null) {
baseRevisionSwitcher.markShowingHeadBase(doc, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,14 +208,6 @@ private boolean isDiffView(Editor editor) {
return editor.getEditorKind() == EditorKind.DIFF;
}

private void refreshEditor(Editor editor) {
// Don't remove all highlighters - this removes indent guides and other system decorations
// The platform manages system highlighters automatically through the daemon code analyzer
if (editor.getGutter() instanceof EditorGutterComponentEx gutter) {
gutter.revalidateMarkup();
}
}

public void update(Collection<Change> changes, @Nullable VirtualFile targetFile) {
if (changes == null || disposing.get()) {
return;
Expand All @@ -232,10 +224,8 @@ public void update(Collection<Change> changes, @Nullable VirtualFile targetFile)
Editor[] editors = EditorFactory.getInstance().getAllEditors();
for (Editor editor : editors) {
if (isDiffView(editor)) continue;
if (updateLineStatusByChangesForEditorSafe(editor, fileToRevisionMap))
{
refreshEditor(editor);
}
// Platform handles gutter repainting automatically - no need to force it
updateLineStatusByChangesForEditorSafe(editor, fileToRevisionMap);
}
});
});
Expand Down Expand Up @@ -364,10 +354,24 @@ private void updateTrackerBaseRevision(LineStatusTracker<?> tracker, String cont

if (setBaseRevisionMethod != null) {
setBaseRevisionMethod.setAccessible(true);
// Guard against concurrent disposal
if (disposing.get()) {
return;
}

ApplicationManager.getApplication().runWriteAction(() -> {
try {
// Double-check disposal state inside write action
if (disposing.get()) {
return;
}

// Use bulk update mode to batch changes and prevent flickering
Document document = tracker.getDocument();
if (document == null) {
return;
}

DocumentUtil.executeInBulk(document, () -> {
try {
setBaseRevisionMethod.invoke(tracker, content);
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/listener/MyGitRepositoryChangeListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package listener;

import com.intellij.openapi.project.Project;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryChangeListener;
import org.jetbrains.annotations.NotNull;
import service.ViewService;
import system.Defs;

/**
* Listens to Git repository changes (branches, tags, HEAD changes, remote updates).
* This complements MyChangeListListener which only triggers on working tree changes.
*
* This listener is essential for detecting:
* - New tags being created
* - New branches being created/deleted
* - Branch checkouts
* - Remote reference updates (fetch/pull)
*/
public class MyGitRepositoryChangeListener implements GitRepositoryChangeListener {
private static final com.intellij.openapi.diagnostic.Logger LOG = Defs.getLogger(MyGitRepositoryChangeListener.class);

private final ViewService viewService;

public MyGitRepositoryChangeListener(Project project) {
this.viewService = project.getService(ViewService.class);
}

@Override
public void repositoryChanged(@NotNull GitRepository repository) {
LOG.debug("repositoryChanged() called for repository: " + repository.getRoot().getName());

// TODO: collectChanges: repository changed (branches, tags, HEAD, remotes updated)
viewService.collectChanges(true);
}
}
7 changes: 6 additions & 1 deletion src/main/java/listener/MyTabContentListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ public void contentRemoved(@NotNull ContentManagerEvent event) {
if (Objects.equals(tabName, PLUS_TAB_LABEL)) {
return;
}
getViewService().removeTab(event.getIndex()); // Get service only when needed

// Don't remove the model if we're just reordering tabs
ViewService viewService = getViewService();
if (viewService != null && !viewService.isProcessingTabReorder()) {
viewService.removeTab(event.getIndex());
}
}
}
4 changes: 3 additions & 1 deletion src/main/java/listener/VcsContextMenuAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ private static String getHashesAsString(@NotNull List<? extends VcsRevisionNumbe
public void actionPerformed(@NotNull AnActionEvent e) {
List<VcsRevisionNumber> revisions = getRevisionNumbersFromContext(e);
revisions = ContainerUtil.reverse(revisions);
String rev = getHashesAsString(revisions);

// Use only the first commit hash as the tab name
String rev = revisions.isEmpty() ? "" : revisions.getFirst().asString();

Project project = e.getProject();
ViewService viewService = Objects.requireNonNull(project).getService(ViewService.class);
Expand Down
23 changes: 16 additions & 7 deletions src/main/java/service/ToolWindowService.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import system.Defs;
import toolwindow.TabRename;
import toolwindow.TabOperations;
import toolwindow.ToolWindowView;
import toolwindow.elements.VcsTree;

Expand All @@ -24,11 +24,11 @@
public final class ToolWindowService implements ToolWindowServiceInterface {
private final Project project;
private final Map<Content, ToolWindowView> contentToViewMap = new HashMap<>();
private final TabRename tabRename;
private final TabOperations tabOperations;

public ToolWindowService(Project project) {
this.project = project;
this.tabRename = new TabRename(project);
this.tabOperations = new TabOperations(project);
}

@Override
Expand Down Expand Up @@ -86,18 +86,18 @@ public void addListener() {
ContentManager contentManager = getContentManager();
contentManager.addContentManagerListener(new MyTabContentListener(project));

// Register the rename action in the tab context menu
tabRename.registerRenameTabAction();
// Register all tab actions (rename, reset, move) in the tab context menu
tabOperations.registerTabActions();
}

@Override
public void setupTabTooltip(MyModel model) {
tabRename.setupTabTooltip(model, contentToViewMap);
tabOperations.setupTabTooltip(model, contentToViewMap);
}

@Override
public void changeTabName(String title) {
tabRename.changeTabName(title, getContentManager());
tabOperations.changeTabName(title, getContentManager());
}

public void removeTab(int index) {
Expand Down Expand Up @@ -141,4 +141,13 @@ public void selectFile(VirtualFile file) {
vcsTree.selectFile(file);
}
}

@Override
public MyModel getModelForContent(Content content) {
ToolWindowView toolWindowView = contentToViewMap.get(content);
if (toolWindowView != null) {
return toolWindowView.getModel();
}
return null;
}
}
5 changes: 5 additions & 0 deletions src/main/java/service/ToolWindowServiceInterface.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package service;

import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import model.MyModel;
import toolwindow.elements.VcsTree;

Expand All @@ -26,4 +27,8 @@ public interface ToolWindowServiceInterface {
VcsTree getVcsTree();

void selectFile(VirtualFile file);

ToolWindow getToolWindow();

MyModel getModelForContent(com.intellij.ui.content.Content content);
}
Loading