Reduce idle git status polling (#7069)

This commit is contained in:
Neil 2026-07-01 23:09:23 -07:00 committed by GitHub
parent 7c66f37632
commit 171d32d8f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 80 additions and 47 deletions

View File

@ -46,6 +46,7 @@ async function usePollingOnce(
sshStatus?: string
enabled?: boolean
expectStatusCall?: boolean
stateOverrides?: Partial<PollState>
} = {}
): Promise<{ state: PollState; gitStatus: ReturnType<typeof vi.fn> }> {
vi.resetModules()
@ -67,6 +68,7 @@ async function usePollingOnce(
rightSidebarTab: 'source-control',
openFiles: []
}
Object.assign(state, options.stateOverrides)
const mockedRepo = { ...repo, connectionId: options.connectionId ?? null }
const gitStatus = vi.fn().mockResolvedValue(status)
@ -226,6 +228,27 @@ describe('useGitStatusPolling', () => {
expect(gitStatus).not.toHaveBeenCalled()
expect(state.setGitStatus).not.toHaveBeenCalled()
expect(globalThis.setInterval).not.toHaveBeenCalled()
})
it('uses a slower git status cadence when only terminal branch detection needs polling', async () => {
const { gitStatus } = await usePollingOnce(
{
entries: [],
conflictOperation: 'unknown',
head: 'abc123',
branch: 'refs/heads/main'
},
{
stateOverrides: {
rightSidebarOpen: false,
openFiles: []
}
}
)
expect(gitStatus).toHaveBeenCalledTimes(1)
expect(globalThis.setInterval).toHaveBeenCalledWith(expect.any(Function), 30_000)
})
it('does not install the visible git status poll while disabled', async () => {

View File

@ -8,11 +8,18 @@ import { getRuntimeGitConflictOperation } from '@/runtime/runtime-git-client'
import { refreshGitStatusForWorktree } from './git-status-refresh'
import { type CoalescedPollRunner, createCoalescedPollRunner } from './coalesced-poll-runner'
import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval'
import { shouldPollActiveGitStatus } from '@/lib/passive-macos-app-data-access'
import {
hasInteractiveActiveGitStatusConsumer,
shouldPollActiveGitStatus
} from '@/lib/passive-macos-app-data-access'
import { getRightSidebarWorktreeRuntimeSettings } from './file-explorer-runtime-owner'
import { useGitStatusFileWatchRefresh } from './git-status-file-watch-refresh'
const POLL_INTERVAL_MS = 3000
const MIN_STATUS_REFRESH_INTERVAL_MS = 3000
const INTERACTIVE_STATUS_POLL_INTERVAL_MS = MIN_STATUS_REFRESH_INTERVAL_MS
// Why: file-watch refreshes cover content changes; terminal-only polling is
// just a fallback for branch switches made inside shells.
const TERMINAL_ONLY_STATUS_POLL_INTERVAL_MS = 30_000
export function useGitStatusPolling(options: { enabled?: boolean } = {}): void {
const enabled = options.enabled ?? true
@ -44,6 +51,29 @@ export function useGitStatusPolling(options: { enabled?: boolean } = {}): void {
!connectionId || sshConnectionStates.get(connectionId)?.status === 'connected',
[sshConnectionStates]
)
const activeGitStatusPollingArgs = {
activeWorktreeId,
worktreePath,
rightSidebarOpen,
rightSidebarTab,
rightSidebarExplorerView,
openFiles
}
const isActiveConnectionReady = isConnectionReady(activeConnectionId)
const shouldPollActiveWorktreeGitStatus =
enabled &&
!!activeWorktreeId &&
!!worktreePath &&
activeRepoSupportsGit &&
shouldPollActiveGitStatus(activeGitStatusPollingArgs) &&
isActiveConnectionReady &&
!gitStatusHugeByWorktree?.[activeWorktreeId]
const activeStatusPollIntervalMs = hasInteractiveActiveGitStatusConsumer(
activeGitStatusPollingArgs
)
? INTERACTIVE_STATUS_POLL_INTERVAL_MS
: TERMINAL_ONLY_STATUS_POLL_INTERVAL_MS
const activeStatusPollScope = shouldPollActiveWorktreeGitStatus ? activeWorktreeId : null
// Why: build a list of non-active worktrees that still have a known conflict
// operation (merge/rebase/cherry-pick). These need lightweight polling so
@ -68,34 +98,7 @@ export function useGitStatusPolling(options: { enabled?: boolean } = {}): void {
}, [allWorktrees, conflictOperationByWorktree, activeWorktreeId, repoMap])
const runFetchStatus = useCallback(async () => {
if (!enabled) {
return
}
if (!activeWorktreeId || !worktreePath) {
return
}
if (
!shouldPollActiveGitStatus({
activeWorktreeId,
worktreePath,
rightSidebarOpen,
rightSidebarTab,
rightSidebarExplorerView,
openFiles
}) ||
!activeRepoSupportsGit
) {
return
}
if (!isConnectionReady(activeConnectionId)) {
return
}
// Why: once a repo's status was truncated at the entry limit, re-running git
// status every 3s just re-does expensive work and re-truncates. Pause the
// automatic poll while huge (a manual refresh still goes through its own
// path); resolving the changes (e.g. .gitignoring the huge folder) clears
// the flag and polling resumes. Mirrors a "huge repo" disabling auto status.
if (gitStatusHugeByWorktree?.[activeWorktreeId]) {
if (!shouldPollActiveWorktreeGitStatus || !activeWorktreeId || !worktreePath) {
return
}
try {
@ -117,18 +120,10 @@ export function useGitStatusPolling(options: { enabled?: boolean } = {}): void {
// ignore
}
}, [
activeRepoSupportsGit,
activeConnectionId,
activePushTarget,
activeWorktreeId,
enabled,
fetchUpstreamStatus,
gitStatusHugeByWorktree,
isConnectionReady,
openFiles,
rightSidebarExplorerView,
rightSidebarOpen,
rightSidebarTab,
shouldPollActiveWorktreeGitStatus,
worktreePath,
setGitStatus,
setUpstreamStatus,
@ -147,7 +142,7 @@ export function useGitStatusPolling(options: { enabled?: boolean } = {}): void {
const statusPollRunnerRef = useRef<CoalescedPollRunner | null>(null)
useEffect(() => {
const runner = createCoalescedPollRunner(() => runFetchStatusRef.current(), {
minIntervalMs: POLL_INTERVAL_MS
minIntervalMs: MIN_STATUS_REFRESH_INTERVAL_MS
})
statusPollRunnerRef.current = runner
return () => {
@ -158,16 +153,19 @@ export function useGitStatusPolling(options: { enabled?: boolean } = {}): void {
const fetchStatus = useCallback(() => {
statusPollRunnerRef.current?.run()
}, [activeWorktreeId])
}, [])
useEffect(() => {
if (!enabled) {
if (!activeStatusPollScope) {
return
}
// Why: this root-level poll should pause while hidden, but visible
// unfocused windows still need fresh status for second-display workflows.
return installWindowVisibilityInterval({ run: fetchStatus, intervalMs: POLL_INTERVAL_MS })
}, [enabled, fetchStatus])
return installWindowVisibilityInterval({
run: fetchStatus,
intervalMs: activeStatusPollIntervalMs
})
}, [activeStatusPollIntervalMs, activeStatusPollScope, fetchStatus])
useGitStatusFileWatchRefresh({
activeConnectionId,
@ -225,7 +223,7 @@ export function useGitStatusPolling(options: { enabled?: boolean } = {}): void {
// visible unfocused windows, but do not poll disconnected hidden windows.
const stopVisiblePoll = installWindowVisibilityInterval({
run: () => pollRunner.run(),
intervalMs: POLL_INTERVAL_MS
intervalMs: MIN_STATUS_REFRESH_INTERVAL_MS
})
return () => {
pollRunner.dispose()

View File

@ -20,7 +20,7 @@ export function isMacAppDataPath(path: string | null | undefined, userAgent?: st
return MAC_APP_DATA_SEGMENT_RE.test(path.replace(/\\/g, '/'))
}
export function shouldPollActiveGitStatus(args: {
export type ActiveGitStatusPollingArgs = {
activeWorktreeId: string | null
worktreePath: string | null
rightSidebarOpen: boolean
@ -28,7 +28,9 @@ export function shouldPollActiveGitStatus(args: {
rightSidebarExplorerView?: RightSidebarExplorerView
openFiles?: OpenFile[]
userAgent?: string
}): boolean {
}
export function hasInteractiveActiveGitStatusConsumer(args: ActiveGitStatusPollingArgs): boolean {
if (!args.activeWorktreeId || !args.worktreePath) {
return false
}
@ -43,6 +45,16 @@ export function shouldPollActiveGitStatus(args: {
if ((args.openFiles ?? []).some((file) => file.worktreeId === args.activeWorktreeId)) {
return true
}
return false
}
export function shouldPollActiveGitStatus(args: ActiveGitStatusPollingArgs): boolean {
if (!args.activeWorktreeId || !args.worktreePath) {
return false
}
if (hasInteractiveActiveGitStatusConsumer(args)) {
return true
}
// Why: macOS app-container paths can trigger the "data from other apps"
// prompt. Keep terminal-only workspace switching from passively probing them.
return !isMacAppDataPath(args.worktreePath, args.userAgent)