Skip to content

fix(environments): remove deleted branches from UI when synced from Git#4770

Merged
kodiakhq[bot] merged 14 commits into
v2from
markphelps/branch-delete-ui-bug
Sep 30, 2025
Merged

fix(environments): remove deleted branches from UI when synced from Git#4770
kodiakhq[bot] merged 14 commits into
v2from
markphelps/branch-delete-ui-bug

Conversation

@markphelps

@markphelps markphelps commented Sep 30, 2025

Copy link
Copy Markdown
Collaborator

Summary

Fixes a customer-reported bug where deleted Git branches persist in the Flipt Pro UI after being merged and deleted from the repository.

Root Cause

The bug had two components:

  1. Missing pruning on fetch: When fetching from remote, Git was not configured to prune (remove) deleted remote tracking branches. This meant that when a branch was deleted upstream, the local ref refs/remotes/origin/flipt/env/branch would persist indefinitely.

  2. No cleanup in RefreshEnvironment: The RefreshEnvironment method only added new branches and updated existing ones, but never removed branches that had been deleted from Git.

Solution

1. Enable Pruning on Fetch

Added Prune: true to FetchOptions in the Repository's Fetch method. This ensures that deleted remote branches are removed from local tracking refs, matching the behavior of git fetch --prune.

2. Branch Cleanup in RefreshEnvironment

Updated RefreshEnvironment to:

  • Track which branches currently exist in the Git repository during sync
  • Detect branches that have been deleted from Git but still exist in the cache
  • Remove deleted branches from both the environment's internal cache (e.branches) and the global EnvironmentStore
  • Return a RefreshResult struct containing both new and deleted branches for cleaner API

3. Fixed Data Race Conditions

  • Added proper mutex locking throughout RefreshEnvironment to protect concurrent access to e.branches and e.refs maps
  • Fixed race condition in Notify method which was accessing e.refs without holding the mutex

Behavior

Deleted branches will be automatically removed from the UI on the next Git sync cycle (default: 30 seconds). The fix is backwards compatible and follows existing patterns in the codebase.

@markphelps
markphelps requested a review from a team as a code owner September 30, 2025 14:33
@markphelps markphelps added the v2 Flipt v2 label Sep 30, 2025
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Sep 30, 2025
@dosubot

dosubot Bot commented Sep 30, 2025

Copy link
Copy Markdown

Related Documentation

Checked 3 published document(s). No updates required.

You have 3 draft document(s). Publish docs to keep them always up-to-date

How did I do? Any feedback?  Join Discord

@codecov

codecov Bot commented Sep 30, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.47059% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.46%. Comparing base (89bb754) to head (11677c6).
⚠️ Report is 1 commits behind head on v2.

Files with missing lines Patch % Lines
internal/storage/environments/git/store.go 83.51% 13 Missing and 2 partials ⚠️
internal/server/environments/storage.go 0.00% 5 Missing ⚠️
internal/storage/environments/environments.go 20.00% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##               v2    #4770      +/-   ##
==========================================
+ Coverage   58.36%   58.46%   +0.09%     
==========================================
  Files         134      134              
  Lines       16777    16852      +75     
==========================================
+ Hits         9792     9852      +60     
- Misses       6300     6314      +14     
- Partials      685      686       +1     
Flag Coverage Δ
integrationtests 33.90% <36.27%> (+<0.01%) ⬆️
unittests 49.03% <74.50%> (+0.13%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread internal/storage/environments/git/store.go Outdated
Comment thread internal/storage/environments/git/store.go Outdated
@markphelps
markphelps force-pushed the markphelps/branch-delete-ui-bug branch from b390adc to a0ccadf Compare September 30, 2025 18:15
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Sep 30, 2025
markphelps and others added 5 commits September 30, 2025 14:16
When a branch is merged and deleted in Git, the Flipt UI was continuing
to show the branch because RefreshEnvironment only added new branches but
never removed deleted ones from the internal cache.

This fix updates RefreshEnvironment to:
1. Track which branches still exist in the Git repository
2. Remove branches from the environment's internal cache when they no longer exist
3. Return the list of deleted branch keys so the EnvironmentStore can remove them
4. Add a Remove method to EnvironmentStore for proper cleanup

Changes:
- Modified RefreshEnvironment signature to return deleted branch keys
- Added Remove method to EnvironmentStore
- Updated subscriber in environments.go to handle deleted branches
- Added comprehensive test for branch deletion sync

Fixes the customer-reported issue where deleted branches persist in the UI.

Signed-off-by: Mark Phelps <[email protected]>
…returns

Improved the RefreshEnvironment API to return a single RefreshResult
struct instead of multiple return values (newBranches, deletedBranchKeys, error).

This makes the API cleaner and more maintainable:
- Easier to extend with additional fields in the future
- More explicit about what data is being returned
- Better aligns with Go best practices for multiple return values

Changes:
- Added RefreshResult struct with NewBranches and DeletedBranchKeys fields
- Updated RefreshEnvironment signature to return (*RefreshResult, error)
- Updated all callers to use the struct fields
- Updated tests to access result.NewBranches and result.DeletedBranchKeys

Signed-off-by: Mark Phelps <[email protected]>
Co-authored-by: Roman Dmytrenko <[email protected]>
Signed-off-by: Mark Phelps <[email protected]>
Fixed compilation error where map lookup was not properly checking
key existence. Changed from direct boolean check to proper two-value
lookup pattern.

Signed-off-by: Mark Phelps <[email protected]>
Co-authored-by: Roman Dmytrenko <[email protected]>
Signed-off-by: Mark Phelps <[email protected]>
@markphelps
markphelps force-pushed the markphelps/branch-delete-ui-bug branch from a0ccadf to 584f7bc Compare September 30, 2025 18:17
Signed-off-by: Mark Phelps <[email protected]>
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Sep 30, 2025
@markphelps
markphelps requested a review from erka September 30, 2025 18:58
@markphelps markphelps added automerge Used by Kodiak bot to automerge PRs and removed automerge Used by Kodiak bot to automerge PRs labels Sep 30, 2025
RefreshEnvironment was modifying shared state (e.branches, e.refs) without
proper mutex protection, creating a race condition. Other methods like
Branch(), DeleteBranch(), and EvaluationStore() correctly use the mutex.

Changes:
- Added RLock/RUnlock when reading e.branches and e.refs
- Added Lock/Unlock when modifying e.branches and e.refs
- Used fine-grained locking to avoid deadlocks with updateSnapshot()
- Maintained consistency with existing locking patterns in the codebase

This prevents race conditions when RefreshEnvironment is called concurrently
with other operations that access the branches map or refs map.

Signed-off-by: Mark Phelps <[email protected]>
Remove dagger.io/dagger/dag import that keeps appearing due to
variable named 'dag' in the code. The import is unused and causes
build issues.

Signed-off-by: Mark Phelps <[email protected]>
Signed-off-by: Mark Phelps <[email protected]>
The Notify method was accessing e.refs map without holding the mutex,
creating a race condition with RefreshEnvironment. Added fine-grained
RLock/Lock usage to protect reads and writes to e.refs.

This follows the same locking pattern used in RefreshEnvironment.

Signed-off-by: Mark Phelps <[email protected]>
@erka

erka commented Sep 30, 2025

Copy link
Copy Markdown
Contributor

something is not right for my gitea setup. I still see the deleted branch as environment. When snapshot is updated it still holds the ref to the deleted branch.

Added detailed debug logging to help diagnose branch deletion issues:
- Log environment key when removing from cache
- Log key when removing from EnvironmentStore

This will help identify any key mismatch issues between the branch
environment cache and the global EnvironmentStore.

Signed-off-by: Mark Phelps <[email protected]>
Added comprehensive debug logging for Git branch synchronization:
- Log when scanning for branches in Git
- Log each branch found with both the environment name and Git branch path
- Log cache vs Git branch counts before deletion check

This will help diagnose branch deletion issues by showing exactly what
branches Git sees vs what's cached, making it easier to identify
ref pattern mismatches or sync issues.

Signed-off-by: Mark Phelps <[email protected]>
@markphelps

Copy link
Copy Markdown
Collaborator Author

something is not right for my gitea setup. I still see the deleted branch as environment. When snapshot is updated it still holds the ref to the deleted branch.

yup also reproduced. working on a fix

Added Prune: true to FetchOptions to ensure deleted remote branches
are removed from local tracking refs. Without this, when a branch is
deleted upstream, the local ref refs/remotes/origin/branch-name persists,
causing the branch to still appear as existing in Git.

This is the root cause of the bug where deleted Git branches continue
to show in the Flipt UI. The branch detection logic scans refs/remotes/origin/*
refs, and without pruning, deleted upstream branches remain in the
local repository indefinitely.

With pruning enabled, git fetch will remove stale tracking branches,
allowing RefreshEnvironment to correctly detect and remove deleted branches.

Signed-off-by: Mark Phelps <[email protected]>
Removed verbose debug logs that were added for investigation:
- "scanning for branch environments in Git"
- "found branch in Git" per-branch logs
- "checking for deleted branches" with counts
- "removing environment from store"

Kept the "removing deleted branch from cache" log as it provides
useful visibility when branches are actually deleted.

Signed-off-by: Mark Phelps <[email protected]>
@markphelps

markphelps commented Sep 30, 2025

Copy link
Copy Markdown
Collaborator Author

@erka fixed. Was missing the prune option on fetch

Pre-Delete

Screenshot 2025-09-30 at 3 57 40 PM Screenshot 2025-09-30 at 3 57 47 PM

Post Delete (on GitHub)

Screenshot 2025-09-30 at 4 01 09 PM Screenshot 2025-09-30 at 3 58 33 PM

Note: This required a UI refresh, we should make the UI auto refresh on changes like this if possible, but we can do that in another PR

@erka erka left a comment

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.

nice

@markphelps markphelps added the automerge Used by Kodiak bot to automerge PRs label Sep 30, 2025
@kodiakhq
kodiakhq Bot merged commit b928b68 into v2 Sep 30, 2025
41 of 42 checks passed
@kodiakhq
kodiakhq Bot deleted the markphelps/branch-delete-ui-bug branch September 30, 2025 20:11
@github-project-automation github-project-automation Bot moved this to Done in Flipt V2 Sep 30, 2025
@dosubot

dosubot Bot commented Sep 30, 2025

Copy link
Copy Markdown

Documentation Updates

Checked 3 published document(s). No updates required.

You have 3 draft document(s). Publish docs to keep them always up-to-date

How did I do? Any feedback?  Join Discord

markphelps added a commit that referenced this pull request Sep 30, 2025
The environment picker was showing stale data for deleted/merged branches
and closed pull requests until the user performed a hard refresh. This
occurred because RTK Query's default caching behavior doesn't automatically
refetch data when the browser tab regains focus or when network reconnects.

Changes:
- Added refetchOnFocus and refetchOnReconnect to environmentsApi config
- Added refetchOnMountOrArgChange to useListEnvironmentsQuery in Layout
- Added refetchOnMountOrArgChange to useListBranchEnvironmentsQuery in BranchActionsDropdown

Now when users switch back to the Flipt tab or reconnect to the network,
the environment list will automatically refetch, removing any deleted
branches or closed PRs from the picker without requiring a manual refresh.

Fixes #4770

Signed-off-by: Mark Phelps <[email protected]>
markphelps added a commit that referenced this pull request Oct 1, 2025
The environment picker was showing stale data for deleted/merged branches
and closed pull requests until the user performed a hard refresh. This
occurred because:
1. RTK Query's default caching doesn't automatically refetch on focus/reconnect
2. The backend polls upstream every 30s, but the UI wasn't polling for updates

Changes:
- Added refetchOnFocus and refetchOnReconnect to environmentsApi config
- Added refetchOnMountOrArgChange to useListEnvironmentsQuery in Layout
- Added 30s pollingInterval to useListEnvironmentsQuery to match backend fetch interval
- Added refetchOnMountOrArgChange to useListBranchEnvironmentsQuery in BranchActionsDropdown
- Added conditional 30s polling to namespace query when environment switcher is open

Now the UI automatically refetches environments when:
- The browser tab regains focus (refetchOnFocus)
- Network reconnects (refetchOnReconnect)
- Every 30 seconds via polling (matching backend Git fetch interval)

This ensures deleted branches and closed PRs are removed from the environment
picker within 30 seconds without requiring a manual page refresh.

Fixes #4770

Signed-off-by: Mark Phelps <[email protected]>
ptejasvini pushed a commit to ptejasvini/flipt that referenced this pull request Oct 3, 2025
…it (flipt-io#4770)

* fix(environments): remove deleted branches from UI when synced from Git

When a branch is merged and deleted in Git, the Flipt UI was continuing
to show the branch because RefreshEnvironment only added new branches but
never removed deleted ones from the internal cache.

This fix updates RefreshEnvironment to:
1. Track which branches still exist in the Git repository
2. Remove branches from the environment's internal cache when they no longer exist
3. Return the list of deleted branch keys so the EnvironmentStore can remove them
4. Add a Remove method to EnvironmentStore for proper cleanup

Changes:
- Modified RefreshEnvironment signature to return deleted branch keys
- Added Remove method to EnvironmentStore
- Updated subscriber in environments.go to handle deleted branches
- Added comprehensive test for branch deletion sync

Fixes the customer-reported issue where deleted branches persist in the UI.

Signed-off-by: Mark Phelps <[email protected]>

* refactor(environments): use RefreshResult struct instead of multiple returns

Improved the RefreshEnvironment API to return a single RefreshResult
struct instead of multiple return values (newBranches, deletedBranchKeys, error).

This makes the API cleaner and more maintainable:
- Easier to extend with additional fields in the future
- More explicit about what data is being returned
- Better aligns with Go best practices for multiple return values

Changes:
- Added RefreshResult struct with NewBranches and DeletedBranchKeys fields
- Updated RefreshEnvironment signature to return (*RefreshResult, error)
- Updated all callers to use the struct fields
- Updated tests to access result.NewBranches and result.DeletedBranchKeys

Signed-off-by: Mark Phelps <[email protected]>

* chore: Apply suggestions from code review

Co-authored-by: Roman Dmytrenko <[email protected]>
Signed-off-by: Mark Phelps <[email protected]>

* fix: correct map lookup syntax in branch deletion check

Fixed compilation error where map lookup was not properly checking
key existence. Changed from direct boolean check to proper two-value
lookup pattern.

Signed-off-by: Mark Phelps <[email protected]>

* chore: add DCO sign-off for code review changes

Co-authored-by: Roman Dmytrenko <[email protected]>
Signed-off-by: Mark Phelps <[email protected]>

* chore: fix build/main import

Signed-off-by: Mark Phelps <[email protected]>

* fix(environments): add proper mutex locking to RefreshEnvironment

RefreshEnvironment was modifying shared state (e.branches, e.refs) without
proper mutex protection, creating a race condition. Other methods like
Branch(), DeleteBranch(), and EvaluationStore() correctly use the mutex.

Changes:
- Added RLock/RUnlock when reading e.branches and e.refs
- Added Lock/Unlock when modifying e.branches and e.refs
- Used fine-grained locking to avoid deadlocks with updateSnapshot()
- Maintained consistency with existing locking patterns in the codebase

This prevents race conditions when RefreshEnvironment is called concurrently
with other operations that access the branches map or refs map.

Signed-off-by: Mark Phelps <[email protected]>

* chore: fix build/main import

Remove dagger.io/dagger/dag import that keeps appearing due to
variable named 'dag' in the code. The import is unused and causes
build issues.

Signed-off-by: Mark Phelps <[email protected]>

* chore: mod tidy

Signed-off-by: Mark Phelps <[email protected]>

* fix(environments): add mutex locking to Notify method

The Notify method was accessing e.refs map without holding the mutex,
creating a race condition with RefreshEnvironment. Added fine-grained
RLock/Lock usage to protect reads and writes to e.refs.

This follows the same locking pattern used in RefreshEnvironment.

Signed-off-by: Mark Phelps <[email protected]>

* chore(environments): add debug logging for branch deletion

Added detailed debug logging to help diagnose branch deletion issues:
- Log environment key when removing from cache
- Log key when removing from EnvironmentStore

This will help identify any key mismatch issues between the branch
environment cache and the global EnvironmentStore.

Signed-off-by: Mark Phelps <[email protected]>

* chore(environments): enhance branch sync debug logging

Added comprehensive debug logging for Git branch synchronization:
- Log when scanning for branches in Git
- Log each branch found with both the environment name and Git branch path
- Log cache vs Git branch counts before deletion check

This will help diagnose branch deletion issues by showing exactly what
branches Git sees vs what's cached, making it easier to identify
ref pattern mismatches or sync issues.

Signed-off-by: Mark Phelps <[email protected]>

* fix(git): enable pruning when fetching to remove deleted remote branches

Added Prune: true to FetchOptions to ensure deleted remote branches
are removed from local tracking refs. Without this, when a branch is
deleted upstream, the local ref refs/remotes/origin/branch-name persists,
causing the branch to still appear as existing in Git.

This is the root cause of the bug where deleted Git branches continue
to show in the Flipt UI. The branch detection logic scans refs/remotes/origin/*
refs, and without pruning, deleted upstream branches remain in the
local repository indefinitely.

With pruning enabled, git fetch will remove stale tracking branches,
allowing RefreshEnvironment to correctly detect and remove deleted branches.

Signed-off-by: Mark Phelps <[email protected]>

* chore(environments): remove excessive debug logging

Removed verbose debug logs that were added for investigation:
- "scanning for branch environments in Git"
- "found branch in Git" per-branch logs
- "checking for deleted branches" with counts
- "removing environment from store"

Kept the "removing deleted branch from cache" log as it provides
useful visibility when branches are actually deleted.

Signed-off-by: Mark Phelps <[email protected]>

---------

Signed-off-by: Mark Phelps <[email protected]>
Co-authored-by: Roman Dmytrenko <[email protected]>
Signed-off-by: tejasvini1 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge Used by Kodiak bot to automerge PRs size:L This PR changes 100-499 lines, ignoring generated files. v2 Flipt v2

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants