Conversation
"Only Changes Since Common Ancestor" resolved its range by walking `git log` and unioning every commit's changes, so files reverted inside the range stayed listed and each path's base came from the oldest commit that touched it rather than the merge base. Resolve `git merge-base <selection> HEAD` instead and feed it through the same single-base diff the other scopes use, making a range scope exactly `git diff <selection>...HEAD` -- the diff a pull request shows. Range parsing lives in ScopeRefRange, free of platform types so it can be unit tested. It accepts both dot forms but requires HEAD on the right, reporting anything else as an invalid scope rather than silently reading it as "since the common ancestor with HEAD". Also corrects the checkbox label, stops appending "...HEAD" to refs that already carry a range, drops the now-unused GitCommit.getChanges() reflection bridge, and runs :backend:test in CI. Fixes #104 Co-authored-by: Ondrej Smola <ondrej.smola@gmail.com>
…ions The RPC transport applies credit-based backpressure, so a slow frontend or network legitimately stops the stream from being drained. The backend bridge broke under exactly that condition: listener callbacks pushed payloads into a callbackFlow via trySend, which silently discards events once the 64-element buffer fills, and DataUpdated events are state-bearing with no retransmission, so every discarded event was a file whose gutter never rendered. Restructure the producer around conflation by file: listeners only mark a file dirty and wake the emit loop, which reads the latest snapshot at send time and emits with a suspending call, so intermediate states collapse into the newest one, pending memory is bounded at one entry per file, and the final state always arrives. On the frontend, supervise the subscription. durable only retries RPC-level failures; two other terminations used to kill the gutter silently until IDE restart, namely the backend answering with an empty flow because it does not know the project yet, and any exception escaping event handling. Exceptions applying a single event are now logged and skipped, and a completed or failed stream is re-subscribed after a short delay.
…ates Every DataUpdated message carried the full base and HEAD contents of the file, JSON-encoded, although the ranges themselves are a handful of ints. The contents rarely change -- typing, local-change recomputation and settings republishes all produce new ranges against identical contents -- so on a slow link almost all of the transferred bytes were repeats. Track per subscription which contents each file was last sent with and omit them when unchanged; a contentsIncluded flag distinguishes "unchanged, reuse your cache" from a genuinely absent headContent, and the frontend falls back to the contents it already holds. A new subscription starts with no memory and therefore always sends contents on first contact, which keeps the durable re-subscribe after a connection drop correct, and the memory is cleared together with AllCleared since the frontend drops its cache at that point. Contents are still pushed rather than fetched on demand because the frontend needs baseContent synchronously for live range recomputation while typing. What remains per ordinary update is O(ranges) instead of O(file size).
The plugin wrote 67 always-on statements to idea.log, worst on the gutter data path, where a 200-file scope logged around 800 lines every time the scope changed. Demote all 11 INFO calls to DEBUG; demote 11 of the 12 LOG.error calls, since Logger.error feeds the platform's error-report mechanism and raised an "IDE internal error" for conditions the plugin already recovers from, the one kept being VcsTree's fallback failure, which does mean the tool window is broken with no recovery left; and demote 16 WARN calls that fire on normal outcomes such as teardown races and tabs the user cannot move. Debug calls whose argument construction is not free on hot paths are now guarded, and everything stays reachable through the existing single #gitscope switch. With the noise gone, add the debug coverage the refresh pipeline was missing, so a scope that stops picking up filesystem changes can be diagnosed from the log instead of guessed at: MyBulkFileListener now reports the VFS batch size and which project it refreshes, ViewService.collectChanges logs the two early exits that abandon a refresh without scheduling anything, ChangesService logs collections abandoned or interrupted by a newer generation, an empty repository list, cache hits, final counts and unresolvable scopes and now warns from onThrowable, and MyLineStatusTrackerImpl logs updates skipped, superseded or matching no open editor. Steady-state output is still zero lines; 31 WARN and 1 ERROR remain for genuine failures.
Three compounding races left the Git Scope window showing pre-git-operation state, with conflict files staying red after a resolved rebase as the reported symptom. The window is a few hundred milliseconds wide and needs a file to be opened inside it, which is why it reproduced on real branches but never in minimal tests: a collection during the conflict state caches red results, then finishing the rebase schedules a fresh collection, but opening any file bumped the apply generation, causing that fresh collection to abandon before its run() reached the cache clear, and scheduled a cache-permitted collection that served the conflict-era entries under the now-current generation. One fix per link in the chain. fileOpened no longer bumps the apply generation, since opening a file changes no scope input and the bump only discarded whichever fresh collection was in flight; the call remains as a cache-warmer. The cache is cleared when a fresh collection is scheduled rather than inside its run(), so pre-operation entries cannot outlive the operation even if that collection is abandoned. And the collection callback is now always invoked exactly once, with null when superseded or cancelled, so chained UI work such as the file-colors refresh after a tab switch is never silently lost. Refs #78
GitScopeFileStatusProvider answers from the current scope map, but the platform caches those answers until fileStatusesChanged() is called, which only tab switches, boot and the settings dialog did. A collection that changed the scope, such as a file leaving it after a rebase or a status changing after a commit, therefore updated the tool window but left Project-view and editor-tab colors showing the previous scope until the user switched tabs. Refresh the colors when an applied collection materially changed the scope map, compared as path -> FileStatus. The comparison guards the common no-change apply, because the refresh goes through FileStatusManager.fileStatusesChanged(), which can disturb line status trackers and is not free. Refs #78
MyBulkFileListener re-collected every open project on every VFS batch anywhere: build outputs, caches, files of unrelated projects. Beyond the wasted git work, each spurious collection cancels whichever collection is currently in flight, so busy phases such as a rebase writing dozens of files kept restarting the very collections that were about to deliver fresh state, widening the race windows behind issue #78. Skip projects where no event path lies under any of their git repository roots. Everything under a repository root still triggers, including .git internals, so no existing refresh source is lost; repositories not being registered yet also skips, since there is nothing to collect against until the VCS mapping listener fires. Refs #78
Rename Tab, Reset Tab Name and Move Tab Left/Right were all absent from the tool window's tab context menu over Remote Development. The cause was registration, not logic: the actions were declared in gitscope.backend.xml, so they existed only in the host's action registry, while in split mode the tab strip is rendered by the frontend and its context menu is built from the frontend registry. Register the four actions once in the frontend module for both modes -- monolith loads gitscope.frontend too, and UtilRpcApi resolves there as well -- so a single registration serves both modes with no second implementation to keep in step. This also removes three copies of a helper that read ContentTabLabel.myContent via getDeclaredField, which does not search superclasses and so already hid the actions in monolith; ToolWindowContextMenuActionBase replaces it. The tool window, its ContentManager and the scope models are backend state, so the actions decide enablement from what the tab strip shows and delegate over UtilRpcApi to a new TabActionService, which owns the rules and re-validates every request. Reset Tab Name additionally needs to know whether a tab carries a custom name, which is model state the frontend cannot see, so the backend publishes the custom-named tab indices as a StateFlow, republished after every rename, reset and move and once when a frontend subscribes. The frontend mirrors it in a service the action reads synchronously, since update() cannot suspend. Unknown state counts as enabled, so the worst case is an action that runs and finds nothing to reset, never one that is missing.
The tooltip on a renamed tab was missing right after boot in monolith, and always missing in split mode, for two independent reasons. At boot it is built from the tab's branch-based name, which resolves to an empty string before the VCS mapping is ready, and nothing retried afterwards, so a tab restored with a custom name kept no tooltip for the rest of the session. In split mode the tab strip is rendered against the frontend's Content objects while setDescription is called on the backend's copy, which never reaches the label. Both are fixed by making the original names part of the state the backend already pushes for tab actions: the flow now carries a map of tab index to branch-based name rather than a list of renamed indices, and the frontend applies it as the tooltip on its own contents. Reset Tab Name's enablement comes from the same map, since presence in it means the tab has a custom name. The map is republished after every rename, reset and move, when a frontend subscribes, after the tabs are restored, and after each change collection, the last being what finally supplies names that were unresolvable at boot. Tabs whose branch name is still unresolvable are left out rather than published with a blank tooltip.
The platform finishes a tab drag by re-adding the content at its new index, which reaches the plugin as a content removal indistinguishable from closing a tab. MyTabContentListener answered it by removing the model, so the dragged tab lost its scope, the collection order no longer matched the tab order, and save() persisted the result, which is why the tab was gone after restarting the IDE. Only monolith is affected, since the platform gates tab dragging on AppMode.isMonolith(). There is no "content moved" event to listen for, so repair rather than prevent. The removal still happens exactly as before, since that path also runs when tabs are torn down in bulk on project close, but the removed Content is remembered, and if the very same Content is added back, contentAdded recognises the move and rebuildCollectionFromTabOrder() derives the collection from the tabs themselves. Detection is deliberately event-driven rather than timed, because the platform can complete a drag several EDT ticks after the removal; the remembered contents are held weakly, so a Content that really was closed is collected without further bookkeeping. HEAD is moved back to the front and "+" back to the end before the collection is rebuilt, and the renamed-tab map is republished because the indices it addresses have shifted.
Next/Previous Change and Next/Previous Changed File visit the changed files in the order the Git Scope tree shows them, read from the tree itself so that whichever grouping is active -- module, repository or directory -- is followed. When the tool window has not built a tree yet, files are ordered hierarchically instead: into a subdirectory before the files beside it, names compared case-insensitively with digit runs read as numbers. Entries that cannot hold a caret are left out of the order rather than failing when opened: files deleted in the scope, directories recorded as changes such as submodules, and binary files. Each opened file is logged with its file type under #gitscope.
Next/Previous Change and Next/Previous Changed File asked for the opened editor to be focused. When navigation is driven from the tool window that hands the focus away, and Project View following the file leaves it there, so the next keystroke no longer reaches Git Scope. The editor is now focused only when navigation started from the editor; the caret position it would have reported is already tracked as the last navigated position. The changes tree keeps painting its selection as focused, so the file navigation moved to stays visible in the tool window while the editor holds the focus.
Adds the changelog section for the release. The common-ancestor fix moves from the 2026.2 section, which was already published without it.
ChangeListManager restores the changelist persisted in workspace.xml and then only updates incrementally, over whatever VcsDirtyScopeManager reports as dirty. An entry whose file no longer exists can never enter that dirty scope, so it survives every update, is re-persisted on close and returns on the next open. Restarting does not clear it; the scope kept showing files git reports as clean until an unrelated commit touched .git/index and forced a rescan. Mark everything dirty once per project on VcsMappingListener readiness, collect behind ChangeListManager.invokeAfterUpdate, drop changelist entries whose file no longer exists (DELETED exempt), and route untracked files through the same repository and staleness checks.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.