Skip to content

Guard git quick actions for diverged or stale branch status#47

Merged
juliusmarminge merged 2 commits intomainfrom
fix/too-aggresive-pull-action
Feb 15, 2026
Merged

Guard git quick actions for diverged or stale branch status#47
juliusmarminge merged 2 commits intomainfrom
fix/too-aggresive-pull-action

Conversation

@juliusmarminge
Copy link
Copy Markdown
Member

@juliusmarminge juliusmarminge commented Feb 15, 2026

Summary

  • Prevent quick pull/sync actions when the branch has diverged (ahead and behind), and show a disabled hint to rebase/merge first.
  • Add logic to detect when gitStatus.branch is out of sync with the current branch list and temporarily gate actions until queries refresh.
  • Route action/menu/dialog rendering through synchronized status data to avoid acting on stale branch context.
  • Update UI hints so “Behind upstream” messaging only appears for non-diverged behind state.
  • Add unit test coverage for diverged branch quick-action behavior.

Testing

  • apps/web/src/components/GitActionsControl.logic.test.ts: added test asserting diverged status resolves to disabled Sync branch hint.
  • Lint: Not run
  • Full test suite: Not run

Open with Devin

Summary by CodeRabbit

  • New Features

    • Detects when a local branch has diverged and shows a disabled "Sync branch" action with guidance to rebase/merge first
    • Shows a "Refreshing git status..." indicator while status resynchronizes
  • Bug Fixes

    • Runs upstream refresh in the background to keep checkout responsive and improves git status synchronization to avoid UI inconsistencies
  • Tests

    • Adds tests for divergence, background refresh timing, fetch recording during checkout, and async stability checks

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 15, 2026

Walkthrough

Adds branch divergence detection that disables the "Sync branch" quick action when a branch is both ahead and behind upstream. Introduces a null-safe git status proxy in the UI while resynchronizing and starts upstream refresh in the background on checkout; tests updated to handle asynchronous refresh/fetch behavior.

Changes

Cohort / File(s) Summary
Divergence Detection
apps/web/src/components/GitActionsControl.logic.ts, apps/web/src/components/GitActionsControl.logic.test.ts
Adds detection for diverged branches (aheadCount > 0 && behindCount > 0). Returns a disabled "Sync branch" quick action with hint to rebase/merge; adds a unit test for the diverged scenario.
Status Synchronization (UI)
apps/web/src/components/GitActionsControl.tsx
Introduces gitStatusForActions (null while out-of-sync), invalidates queries when gitStatus is out-of-sync, replaces direct gitStatus usage with gitStatusForActions, displays "Refreshing git status..." during resync, and guards UI logic for null status.
Checkout Background Refresh (server & tests)
apps/server/src/git.ts, apps/server/src/git.test.ts
Makes refreshCheckedOutBranchUpstream run asynchronously (background) after checkout instead of awaiting; updates tests to wait for asynchronous refresh/fetch activity, record fetch args, and add a slow-refresh scenario to validate background fetch behavior.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Web UI (GitActionsControl)
    participant API as Server API (checkout)
    participant Core as GitCoreService

    UI->>API: request checkoutBranch
    API->>Core: perform checkout
    Core-->>API: checkout result
    API-->>UI: return checkout result (immediate)
    API->>Core: start refreshCheckedOutBranchUpstream (async)
    Note right of Core: background fetch may be delayed/long-running
    Core-->>API: (background) fetch complete
    API->>UI: subsequent status invalidation/notification
    UI->>UI: set gitStatusForActions = null while resyncing
    UI-->>UI: show "Refreshing git status..."
    UI->>API: refetch git status
    API->>Core: query status
    Core-->>API: latest status
    API->>UI: deliver updated git status (gitStatusForActions populated)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Guard git quick actions for diverged or stale branch status' accurately summarizes the main changes in this PR.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/too-aggresive-pull-action

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/server/src/git.test.ts (1)

402-441: Test validates non-blocking checkout behavior well, but consider awaiting the released fetch.

The SlowRefreshGitCoreService pattern effectively proves checkout resolves before the background fetch completes. However, calling releaseFetch() at the end without awaiting the released promise may leave it dangling. While this doesn't affect test correctness, it could cause flaky warnings or resource cleanup issues in some test runners.

♻️ Optional: Await the background operation to ensure clean teardown
       const core = new SlowRefreshGitCoreService();
+      // Capture the background refresh promise before checkout triggers it
+      let backgroundRefreshPromise: Promise<void> | undefined;
+      const originalCheckout = core.checkoutBranch.bind(core);
+      core.checkoutBranch = async (input) => {
+        const result = originalCheckout(input);
+        // The background refresh is already started; we'll release and await it later
+        return result;
+      };
       await expect(core.checkoutBranch({ cwd: source.path, branch: featureBranch })).resolves.toBe(
         undefined,
       );
       await vi.waitFor(() => {
         expect(core.fetchStarted).toBe(true);
       });
       expect(await git(source.path, "branch --show-current")).toBe(featureBranch);
       core.releaseFetch();
+      // Allow the background promise to settle to avoid dangling promises
+      await vi.waitFor(() => Promise.resolve());
     });

Alternatively, simply adding a small await Promise.resolve() after releaseFetch() ensures the microtask queue flushes.


Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp bot commented Feb 15, 2026

Guard GitActionsControl quick actions for diverged or stale git status and run GitCoreService.checkoutBranch upstream refresh asynchronously in git.ts and GitActionsControl.tsx

Make GitCoreService.checkoutBranch fire refreshCheckedOutBranchUpstream in the background, gate quick actions when gitStatus is out of sync with the current branch, and return a disabled Sync action when both ahead and behind are positive. Tests add async waits and a diverged-branch case.

📍Where to Start

Start with GitCoreService.checkoutBranch in git.ts, then review resolveQuickAction in GitActionsControl.logic.ts and the GitActionsControl flow in GitActionsControl.tsx.


Macroscope summarized 724ecbe.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Feb 15, 2026

Greptile Summary

Improved git status synchronization and diverged branch handling by preventing quick actions when branches have both ahead and behind commits. The PR adds stale status detection that triggers automatic query refresh when gitStatus.branch differs from the current branch in branchList, and gates all action/menu/dialog rendering through gitStatusForActions to avoid acting on outdated context.

Key changes:

  • Added isDiverged check in resolveQuickAction to return disabled "Sync branch" hint when both aheadCount > 0 and behindCount > 0
  • Introduced isGitStatusOutOfSync detection in component to compare gitStatus.branch with current branch from branch list
  • Added useEffect hook that invalidates git queries when status is out of sync
  • Replaced all references from gitStatus to gitStatusForActions (which is null when out of sync) throughout action logic and UI
  • Updated "Behind upstream" warning to only show when aheadCount === 0 to avoid confusion with diverged state
  • Added "Refreshing git status..." message in dropdown menu when status is out of sync
  • Includes unit test coverage for diverged branch behavior

Critical Issue: The buildMenuItems function doesn't properly disable push/create PR for diverged branches - it only checks isBehind but doesn't account for simultaneous ahead+behind state.

Confidence Score: 3/5

  • This PR improves reliability but has a critical logic gap in menu item handling for diverged branches
  • buildMenuItems function doesn't properly handle diverged branch state - it only checks isBehind for disabling push/PR but will allow these actions when branch is both ahead AND behind, which contradicts the quick action logic that correctly disables actions for diverged branches
  • apps/web/src/components/GitActionsControl.logic.ts - the buildMenuItems function needs diverged branch handling similar to resolveQuickAction

Important Files Changed

Filename Overview
apps/web/src/components/GitActionsControl.logic.ts Added diverged branch detection logic to prevent quick actions when branch has both ahead and behind commits
apps/web/src/components/GitActionsControl.logic.test.ts Added test coverage for diverged branch state returning disabled sync hint
apps/web/src/components/GitActionsControl.tsx Added stale status detection, automatic query refresh, and conditional UI rendering based on synchronized git status

Flowchart

flowchart TD
    A[Git Status Query] --> B{isGitStatusOutOfSync?}
    B -->|Yes| C[Invalidate Git Queries]
    B -->|No| D[Use gitStatus]
    B -->|Yes| E[Set gitStatusForActions = null]
    
    D --> F{resolveQuickAction}
    E --> F
    
    F --> G{gitStatus null?}
    G -->|Yes| H[Show unavailable hint]
    G -->|No| I{isBusy?}
    
    I -->|Yes| J[Show busy hint]
    I -->|No| K{hasBranch?}
    
    K -->|No| L[Show branch hint]
    K -->|Yes| M{hasChanges?}
    
    M -->|Yes| N[Return commit action]
    M -->|No| O{isDiverged?}
    
    O -->|Yes| P[Show sync disabled hint]
    O -->|No| Q{isBehind?}
    
    Q -->|Yes| R[Return pull action]
    Q -->|No| S{isAhead?}
    
    S -->|Yes| T[Return push/PR action]
    S -->|No| U[Show up-to-date hint]
Loading

Last reviewed commit: 92b0dca

Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +550 to +554
{gitStatusForActions &&
gitStatusForActions.branch !== null &&
!gitStatusForActions.hasWorkingTreeChanges &&
gitStatusForActions.behindCount > 0 &&
gitStatusForActions.aheadCount === 0 && (
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a clearer message for diverged branches here instead of only showing "Behind upstream" for non-diverged cases. Users might be confused why the warning disappears when they make local commits on an already-behind branch.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Feb 15, 2026

Additional Comments (1)

apps/web/src/components/GitActionsControl.logic.ts
canPush logic doesn't account for diverged branches - it only checks isBehind but when aheadCount > 0 and behindCount > 0, push should also be disabled

  const canPush = !isBusy && hasBranch && !hasChanges && gitStatus.behindCount === 0 && gitStatus.aheadCount > 0;

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/components/GitActionsControl.tsx (1)

188-346: ⚠️ Potential issue | 🟠 Major

Block actions while git status is out-of-sync.
If gitStatusForActions is null (out-of-sync), an already-open dialog can still execute actions, which bypasses the new gating and can skip default-branch confirmation. Add a guard to prevent running actions until the status is synchronized.

🛡️ Suggested guard to enforce out-of-sync gating
     }) => {
+      if (isGitStatusOutOfSync || !gitStatusForActions) {
+        toastManager.add({
+          type: "info",
+          title: "Refreshing git status...",
+          description: "Please retry once the status is synchronized.",
+        });
+        return;
+      }
       const confirmed = await maybeConfirmPushToDefaultBranch(action);
       if (!confirmed) return;
       onConfirmed?.();
@@
     [
       api,
+      isGitStatusOutOfSync,
       gitStatusForActions?.branch,
       gitStatusForActions?.hasWorkingTreeChanges,
       gitStatusForActions?.openPr?.url,

juliusmarminge and others added 2 commits February 15, 2026 01:17
- Disable the quick sync action when local and upstream branches have diverged
- Treat mismatched branch/status data as out-of-sync and refresh before enabling actions
- Update GitActionsControl logic tests for diverged branch behavior

Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
@juliusmarminge juliusmarminge force-pushed the fix/too-aggresive-pull-action branch from 26c2e84 to 724ecbe Compare February 15, 2026 09:17
!requiresDefaultBranchConfirmation(action, isDefaultBranch) ||
!gitStatusForActions?.branch
) {
return true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low

components/GitActionsControl.tsx:202 When gitStatusForActions is null (during sync), maybeConfirmPushToDefaultBranch returns true immediately, bypassing the confirmation prompt. Consider returning false when gitStatusForActions?.branch is falsy to block the action until status is available.

Suggested change
return true;
return false;

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file apps/web/src/components/GitActionsControl.tsx around line 202:

When `gitStatusForActions` is `null` (during sync), `maybeConfirmPushToDefaultBranch` returns `true` immediately, bypassing the confirmation prompt. Consider returning `false` when `gitStatusForActions?.branch` is falsy to block the action until status is available.

@juliusmarminge juliusmarminge merged commit 98c4b7c into main Feb 15, 2026
3 checks passed
DavidIlie added a commit to DavidIlie/t3code that referenced this pull request Mar 13, 2026
Port upstream commits ac3f22c..268016d:
- Inline branch picker filtering and drop PR-only shortcut (pingdotgg#46)
- Fix PR thread upstream tracking for fork branches (pingdotgg#47)

Removes filterBranchPickerItems shared function in favor of inline filtering,
adds URL-derived fork identity resolution, ensures upstream tracking is
restored on local checkouts and reused worktrees, and simulates real
git checkout in fake GH CLI for integration tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant