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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## [2026.1.2]

### Fixes

- Fixed [multiple "Collecting Git Scope" tasks slow down PHPStorm](https://github.com/comod/git-scope-pro/issues/92)
- Fixed one instance of illegal method call on EDT

## [2026.1.1]

### Fixes
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pluginGroup=org.woelkit.plugins
pluginName=Git Scope
pluginRepositoryUrl=https://github.com/comod/git-scope-pro
pluginVersion=2026.1.1
pluginVersion=2026.1.2
pluginSinceBuild=243

platformType=IU
Expand Down
21 changes: 18 additions & 3 deletions src/main/java/implementation/compare/ChangesService.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

public class ChangesService extends GitCompareWithRefAction implements Disposable {
Expand Down Expand Up @@ -58,6 +60,8 @@ public record ChangesResult(Collection<Change> mergedChanges, Collection<Change>
private final GitService git;
private Task.Backgroundable task;
private final AtomicBoolean disposing = new AtomicBoolean(false);
private final AtomicReference<ProgressIndicator> currentIndicator = new AtomicReference<>();
private final AtomicLong collectionGeneration = new AtomicLong(0);

public ChangesService(Project project) {
this.project = project;
Expand Down Expand Up @@ -85,15 +89,18 @@ public void collectChangesWithCallback(TargetBranchMap targetBranchByRepo, Consu
// Capture the current project reference to ensure consistency
final Project currentProject = this.project;
final GitService currentGitService = this.git;
final long gen = collectionGeneration.incrementAndGet();

task = new Task.Backgroundable(currentProject, "Collecting " + Defs.APPLICATION_NAME, true) {

private ChangesResult result;

@Override
public void run(@NotNull ProgressIndicator indicator) {
// Early exit if disposing
if (disposing.get() || indicator.isCanceled()) {
currentIndicator.set(indicator);
try {
// Early exit if disposing or superseded by a newer collection request
if (disposing.get() || indicator.isCanceled() || collectionGeneration.get() != gen) {
return;
}

Expand All @@ -110,6 +117,7 @@ public void run(@NotNull ProgressIndicator indicator) {
}

repositories.forEach(repo -> {
if (indicator.isCanceled() || collectionGeneration.get() != gen) return;
try {
String branchToCompare = getBranchToCompare(targetBranchByRepo, repo);

Expand Down Expand Up @@ -177,14 +185,17 @@ public void run(@NotNull ProgressIndicator indicator) {
} else {
result = new ChangesResult(_changes, _scopeChanges, _localChanges);
}
} finally {
currentIndicator.compareAndSet(indicator, null);
}
}

@Override
public void onSuccess() {
// Ensure result is accessed only on the UI thread to update the UI component
ApplicationManager.getApplication().invokeLater(() -> {
// Double-check the project is still valid
if (!currentProject.isDisposed() && callBack != null) {
if (!currentProject.isDisposed() && callBack != null && this.result != null) {
callBack.accept(this.result);
}
}, ModalityState.defaultModalityState(), __ -> disposing.get());
Expand All @@ -199,6 +210,10 @@ public void onThrowable(@NotNull Throwable error) {
}, ModalityState.defaultModalityState(), __ -> disposing.get());
}
};
ProgressIndicator prev = currentIndicator.getAndSet(null);
if (prev != null) {
prev.cancel();
}
task.queue();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.concurrency.SequentialTaskExecutor;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.messages.MessageBusConnection;
import implementation.gutter.Range;
Expand All @@ -32,7 +33,9 @@
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

/**
* Manages custom line status markers for scope changes in editor gutters.
Expand All @@ -44,6 +47,9 @@ public class MyLineStatusTrackerImpl implements Disposable {
private final Project project;
private MessageBusConnection messageBusConnection;
private final AtomicBoolean disposing = new AtomicBoolean(false);
private final ExecutorService updateExecutor =
SequentialTaskExecutor.createSequentialApplicationPoolExecutor("ScopeGutterUpdate");
private final AtomicLong updateGeneration = new AtomicLong(0);

// Lightweight disposable token to check disposal state without capturing 'this'
private static class DisposalToken {
Expand All @@ -70,6 +76,7 @@ private static final class RendererInfo {
@Override
public void dispose() {
disposalToken.disposed = true;
updateExecutor.shutdownNow();
releaseAll();
}

Expand Down Expand Up @@ -118,9 +125,11 @@ public void update(Map<String, Change> scopeChangesMap, Map<String, Change> loca
}

final DisposalToken token = this.disposalToken;
// Process editors in parallel on background threads
ApplicationManager.getApplication().executeOnPooledThread(() -> {
if (token.disposed) return;
final long gen = updateGeneration.incrementAndGet();

updateExecutor.execute(() -> {
// Drop stale update if a newer one has been submitted while we were queued
if (token.disposed || updateGeneration.get() != gen) return;

Editor[] editors = EditorFactory.getInstance().getAllEditors();

Expand Down
12 changes: 11 additions & 1 deletion src/main/java/service/ViewService.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,17 @@ private static class DisposalToken {

public ViewService(Project project) {
this.project = project;
EventQueue.invokeLater(this::initDependencies);
ApplicationManager.getApplication().executeOnPooledThread(() -> {
// Force lazy service initialization on BGT where blocking ops are allowed,
// preventing EDT warnings from config loading (e.g. Maven path macro resolution)
project.getService(ChangesService.class);
project.getService(ToolWindowServiceInterface.class);
project.getService(StatusBarService.class);
project.getService(GitService.class);
project.getService(TargetBranchService.class);
project.getService(State.class);
EventQueue.invokeLater(ViewService.this::initDependencies);
});
}

private final @NotNull ExecutorService changesExecutor =
Expand Down