Skip to content

feat!: add Git version control for editing projects - #2164

Open
yuto-trd wants to merge 49 commits into
mainfrom
yuto-trd/project-git-version-control
Open

feat!: add Git version control for editing projects#2164
yuto-trd wants to merge 49 commits into
mainfrom
yuto-trd/project-git-version-control

Conversation

@yuto-trd

@yuto-trd yuto-trd commented Jul 31, 2026

Copy link
Copy Markdown
Member

Description

  • Add opt-in Git-backed project history so explicit saves, Save All, and project close create restorable snapshots, while manual commits provide named milestones.
  • Provide an adaptive Version Control tab and title-bar branch controls for browsing changes and history, restoring safely, switching or creating branches, and pushing or fast-forward pulling a single remote.
  • Keep repositories safe with an exclusive project lifecycle, durable safety checkpoints, private worktree locks, exact-tip compare-and-swap transitions, and explicit ownership-loss/recovery-failure states.
  • Preserve inherited and custom SSH configuration while making only the default OpenSSH transport non-interactive, and coordinate app shutdown through one bounded asynchronous close pipeline.
  • Stabilize project files for meaningful diffs through ID-based element filenames, normalized paths and LF output, and byte-stable project version metadata.

Affected areas

  • Beutl.Engine (rendering / scene / animation / media)
  • Beutl.ProjectSystem (project / document persistence)
  • UI (Beutl.Editor, Beutl.Editor.Components, Beutl.Controls)
  • Beutl.Extensibility (extensions / plugins)
  • Beutl.NodeGraph
  • Beutl.FFmpegIpc / Beutl.FFmpegWorker
  • Beutl.Api
  • Build / CI / docs only
  • Core, configuration, and localization (Beutl.Core, Beutl.Configuration, Beutl.Language)

Breaking changes

  • Project.Serialize now preserves persisted appVersion and minAppVersion during a plain save instead of always stamping the running Beutl version. Migration code advances these fields only after rewriting persisted content.
  • Project JSON is normalized to LF line endings on every platform. Existing Windows project files will produce a one-time full-file newline diff on the next save.
  • IProjectVersionControlService is now query-only and no longer exposes mutation or disposal operations. Mutations are coordinated through IProjectVersionControlCoordinator; the backend and transaction surfaces are internal.
  • IProjectVersionControlInitializer.InitializeCurrentProjectAsync now accepts Func<CancellationToken, Task<GitIdentity?>>, and the exact operation token is forwarded to the callback.
  • CommitResult.Committed now carries a CommitRevision (Known or Unavailable) so a durable commit is not reported as failed when its SHA cannot be observed afterward.
  • SnapshotKind.Recovery and RemoteOpResult.RepositoryDirty distinguish compensating commits and actual cleanliness-precondition failures. VersionControlPolicyNotice is now an internal discriminated union.
  • MenuBarViewModel now requires a VersionControlCoordinator constructor argument.
  • CreateNewProjectViewModel now accepts the IProjectVersionControlInitializer abstraction for Git availability and project initialization.

Test plan

  • dotnet format Beutl.slnx --no-restore --verify-no-changes (3,269 files checked, no formatting changes)
  • .claude/scripts/check-gpl-mit-boundary-diff.sh --files <all changed files>
  • dotnet build Beutl.slnx -c Debug --no-restore (0 errors; 64 existing warnings)
  • dotnet test Beutl.slnx -m:1 -c Debug -f net10.0 --no-build --settings coverlet.runsettings --logger "console;verbosity=normal"
    • UnitTests: 5,170 passed, 7 skipped
    • HeadlessUITests: 238 passed
    • AgentToolkit: 527 passed
    • E2E: 80 passed
    • FFmpegIpc: 56 passed
    • MediaFoundation: 55 passed
    • AVFoundation: 12 passed
    • SourceGenerator: 11 passed
    • Graphics3D: 5 passed
    • FFmpegWorker: 1 passed
  • Focused Git transition, remote-operation, nested-repository, cancellation, shutdown, and adaptive UI regressions passed; the long adaptive history test also passed three consecutive focused runs.
  • Independent repository, public-API/design, and XAML compiled-binding audits reported no findings.
  • The network credential, Git LFS quota, and macOS discovery/notarization scenarios in the manual quickstart matrix were not run because they require a maintainer environment.

Fixed issues / References

  • Project #9: Git version control for editing projects

Summary by CodeRabbit

  • New Features
    • Added Git-based project version control with snapshots, commits, history, restore, branches, diffs, and remote synchronization.
    • Added a Version Control tab, branch selector, project commands, identity setup, and recovery workflows.
    • Added optional tracking during project creation and configurable Git, auto-commit, LFS, and media-warning settings.
  • Bug Fixes
    • Improved project migration, serialization compatibility, path handling, and shutdown reliability.
    • Added guidance when Git is unavailable or conflicts require attention.
  • Documentation
    • Added setup guidance, specifications, safety policies, and workflow documentation.
  • Localization
    • Added English and Japanese version-control messages.

yuto-trd added 30 commits July 28, 2026 14:18
**Feature Branch**: `speckit/005-project-git-versioning`
Session 2026-07-28: creation default on, save/close-only snapshots, fresh history on Save As, auto LFS when detected, guidance-only when Git is absent.
**Branch**: `speckit/005-project-git-versioning` | **Date**: 2026-07-28 | **Spec**: [spec.md](./spec.md)

Also adds research.md, data-model.md, quickstart.md, and contracts/ (version-control-service, git-cli-invocation, coordinator-lifecycle).
**Input**: Design documents from `docs/specs/005-project-git-versioning/`
Preserve the loaded appVersion/minAppVersion on plain resave (a newer Beutl no longer dirties every project it opens; migrations advance the version via Project.MarkAsMigrated), and pin JSON serialization to LF on all platforms so cross-OS diffs stop churning.

BREAKING CHANGE: Project.Serialize now writes the persisted appVersion/minAppVersion instead of unconditionally stamping the running app version, and project JSON now always uses LF line endings (Windows projects produce a one-time full-file diff on next save). Affected: Beutl.Core.
Scene Include/Exclude entries were written with OS-native separators, so a Windows-saved exclude list silently failed to match on macOS/Linux. Normalize to '/' on write and on read, and compare with normalized relatives.
Replace RandomFileNameGenerator with ElementFileNaming ({Id:N}.belm, -{index} on collision, matching the AgentToolkit convention) at all GUI element-creation sites, so element file names are meaningful and reproducible. Existing files are never renamed.
VersionControlConfig (enable-for-new-projects, auto-commit toggles, git path override, LFS policy, large-media threshold) wired into GlobalConfiguration, plus the English/Japanese UI strings for the upcoming version control surface.
GitInstallationLocator (ordered probe with macOS CLT-stub check, 2.23 floor, config override, LFS probe), GitCliRunner (no shell, GIT_TERMINAL_PROMPT=0/GIT_OPTIONAL_LOCKS=0/LC_ALL=C, timeout, cancellation kills the process tree, stale-lock event), RepositoryWatcher (500ms debounce, .git/.beutl/*.tmp exclusion), domain models, and the GitCliVersionControlService core (serialized operations, porcelain-v2 status parsing incl. conflicts, watcher-driven StatusChanged) with a real-git test fixture. Also registers the 005 active-feature context (CLAUDE.md, .specify/feature.json) and marks T001-T011 done.
… service

InitializeAsync (idempotent git init -b main, generated .gitignore/.gitattributes with optional LFS tracking, local lfs install, initial commit with the Beutl-Snapshot: init trailer), CommitAllAsync (clean-tree skip, pathspec-scoped add -A, kind trailer, SkippedNoIdentity for unattended auto commits), and repo-local identity get/set. Repository transitions in place after init and the watcher is created lazily.
VersionControlCoordinator owns the per-project service lifecycle and hooks explicit Save/Save All and the new async ProjectService.Closing event; the create-project dialog gains a git-tracking checkbox (default from VersionControlConfig), the File menu/palette gain Enable Version Control backed by the compiled-binding identity dialog, and EditViewModel exposes IProjectVersionControlService to tool tabs. Includes a headless E2E test proving one snapshot per dirty save and none when clean.
… service

Paged history with Beutl-Snapshot trailer parsing, per-commit changed files, capped unified diffs, pathspec-scoped restore (git restore --source + git clean -fd, ignored files preserved), branch create/switch, and the IVersionControlRestoreCoordinator seam for UI-driven restore.
VersionControlTabExtension (DockAnchor.Right) with status header, incrementally loaded commit list with localized kind badges, changed-files pane and colored diff view (compiled bindings); the coordinator implements the confirm -> safety snapshot -> close -> restore -> commit -> reopen cycle with an export-running guard and failure recovery, plus Restore to new branch. Headless E2E covers exact restore, pre-restore reachability, export refusal, and branch restore.
Repository discovery via git rev-parse --show-toplevel with a consent-required exception before using an enclosing repository; every status/add/log/restore/clean call stays pathspec-scoped to the project directory so foreign and sibling files are never staged, restored, or cleaned. Adds conflicted-state lockout (mutating members throw VersionControlConflictedException while reads keep working), a conflict-marker scanner for project files, and consent-gated stale index.lock recovery in GitCliRunner.
…s in the shell

The Version Control tab collapses to per-OS install guidance when git is missing or too old and menu commands disable; the coordinator refreshes availability, runs the enclosing-repository consent flow, offers one-click stale-lock removal, and warns before opening projects containing conflict markers. Headless conflict scenarios included.
…service

GetBranchesAsync/CreateBranchAsync via for-each-ref, single-origin remote management, cancellable PushAsync with stderr progress, ff-only PullFastForwardAsync with RemoteOpResult mapping (Success/AuthFailed/Diverged/Offline/Failed), the renamed IProjectVersionControlCoordinator seam covering the full UI surface, and the one-time LFS quota notice / non-blocking large-media warning policies. Real-git tests run against a local bare remote.
…ell UI

Commit message box + Commit Version command (identity flow, NoChanges feedback), branch dropdown with the dirty-prompt safety-snapshot close/switch/reopen cycle and failure recovery, remote URL/Push/Pull with progress + cancel and localized divergence/auth/offline dialogs; pull runs through the coordinator cycle. No merge, rebase, reset, or force operation is exposed anywhere.
…teness checks

1000-write burst produces a bounded number of git status calls (measured 1, bound 25) with no watcher self-feedback; a single property edit commits exactly its element file (SC-002); 500-element snapshot and 200-commit history land far inside the SC-003 budgets (505 ms / 43 ms). Adds the missing version-control ContextCommandDefinitions and de-duplicates palette registrations.
…nd record the T039 gate results

The palette-enumeration assertion predated the T037 ContextCommandDefinition registration; the palette now reaches the command via FindContextCommand.
Onboarding: in-tab Enable Version Control call-to-action for untracked projects, a Download Git link in the unavailable state, and localized empty-state hints for history/changes/diff. History: relative timestamps with absolute tooltips, per-kind badge colors, and a restore action bar for the selected version. Mistake-proofing: branch switching now requires an explicit Switch button (selection reverts on cancel/failure), remote controls live in a collapsed Expander, and ahead/behind show as compact arrow badges.
Below 600px the tab switches to a full-width drill-down: the history list fills the panel and selecting a version navigates to a detail view (back row with short SHA, restore action bar, changed files, full-width diff) that preserves the selection on return. At 600px and above the two-column layout with the splitter remains. The history, changes, and detail-header panes are extracted into shared compiled-binding sub-views used by both roots, and the threshold lives in an independently tested helper.
…textual SplitButton

The primary action follows the workspace state — Commit when changes exist, Pull (down-count) when behind, Push (up-count) when ahead, a disabled Up to date when clean and synced, Publish branch… (set remote + push) when clean with no remote, and Cancel while a remote operation runs. The chevron flyout always lists Commit / Push / Pull / Set remote… / Cancel with proper enablement, and the inline remote URL row plus the Remote Expander are gone — Set remote… opens a prefilled dialog via a testable ViewModel callback. Shared across the narrow and wide layouts.
…n Control tab

The coordinator publishes a provisional untracked service before async repository discovery finishes, and a tab restored from the saved dock layout kept that first instance forever, showing 'not tracked' until reopened. EditorService now exposes the service as a read-only reactive seam (published internally by the coordinator so consumers cannot cast it writable), EditViewModel serves both the current value and the observable, and the tab ViewModel rebinds with full state rebuild and old-service detachment on every service transition — which also fixes the same staleness when switching projects with the tab open.
The Version Control tab header loses its branch ComboBox, Switch, and New Branch controls; the window title bar (shared MainView chrome on all three platforms) gains a compact branch widget - glyph + current branch + ahead/behind arrows - visible only while a tracked project is open with git available. Clicking it opens a flyout listing local branches (current one checked; choosing another runs the existing confirm + safety-snapshot switch cycle) plus New branch. The widget stays interactive inside the drag area via AppWindow.AllowInteractionInTitleBar.
Replaces the stock-button list with a JetBrains-style popup: a Current branch header with icon and ahead/behind chips, a live case-insensitive branch filter that resets on open (with an empty-result hint), a Branches section of flat hover-highlight rows with a Fluent checkmark and bold emphasis on the current branch inside a capped-height scroll area, and a matching New branch footer row with an Add icon. All colors come from theme resources so light and dark both render correctly.
…ching styling

The widget moves from the right-side title-bar cluster into the breadcrumb row (project > file, then branch) and adopts the breadcrumb's LiteButtonStyle plus its compact flyout metrics (4px presenter padding, 240px minimum width, 32px transparent action rows) so the two read as siblings.
The filter TextBox, its ViewModel state, the two resource strings, and the filter tests are removed; the flyout lists branches directly.
…ranch popups

The scene-selection flyout's presenter and row ControlThemes move into Beutl.Controls/Styles.axaml as CompactFlyoutPresenterTheme / CompactFlyoutItemButtonTheme, and both popups reference them, so the branch flyout's whitespace is identical by construction: 16px text inset, 32px rows, 4px rhythm, same separators. Current-branch checkmark and the New-branch icon become trailing so labels align with scene rows.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ff40b911ca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Beutl/ViewModels/MenuBarViewModel.cs Outdated
Comment thread src/Beutl.Core/ProjectItem.cs Outdated
Comment thread src/Beutl/ViewModels/MenuBarViewModel.Files.cs Outdated
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs Outdated
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs
Comment thread src/Beutl.Editor/VersionControl/GitCliRunner.cs
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs
Make initialization identity atomic, serialize Git configuration reactivation with close and public operations, harden repository hygiene and Git input validation, and preserve extension-provided project migration reporting. Avoid snapshots after partial Save All failures and add deterministic lifecycle and safety regressions.

BREAKING CHANGE: Beutl.Editor's InitOptions is now internal because initialization is an internal backend concern; external callers must use IProjectVersionControlCoordinator.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
tests/Beutl.UnitTests/Editor/VersionControl/GitCliVersionControlServiceTests.cs (1)

457-496: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the test to match the exercised API and assert the recorded LFS calls.

The test name states EnsureRepositoryHygieneAsync, but the body calls service.InitializeAsync. The test also builds a RecordingLfsRunner and never asserts LfsInstallCalls. Rename the test to describe initialization, or call EnsureRepositoryHygieneAsync. Add an assertion on runner.LfsInstallCalls, or drop the recording runner.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tests/Beutl.UnitTests/Editor/VersionControl/GitCliVersionControlServiceTests.cs`
around lines 457 - 496, Rename
EnsureRepositoryHygieneAsync_keeps_user_Lfs_overrides_after_the_managed_block to
describe the exercised InitializeAsync behavior, and add an assertion verifying
the expected runner.LfsInstallCalls value. Keep the existing attribute-file and
Git check-attr assertions unchanged.
tests/Beutl.HeadlessUITests/VersionControlRestoreTests.cs (3)

2941-2948: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Prefer an internal test hook over reflection on _configurationActivationQuiesced.

The test replaces a private field of VersionControlCoordinator through reflection. This couples the test to a private implementation detail and can leave the coordinator in a state the production code never produces. The Assert.That(readinessField, Is.Not.Null) check catches a rename, but it does not protect against a change in how the field is used. Expose an internal seam on the coordinator for the quiescence signal, and use that seam here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Beutl.HeadlessUITests/VersionControlRestoreTests.cs` around lines 2941
- 2948, Replace the reflection-based assignment of
_configurationActivationQuiesced in the affected test with an internal test hook
on VersionControlCoordinator that exposes the quiescence signal for controlled
setup. Update the test to assign synchronousReadiness through that hook,
preserving the existing synchronization behavior without accessing private
fields.

2685-2693: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Collect factory-created backends in a thread-safe collection.

createdBackends is a List<PullCycleTestBackend>. The service factory runs on coordinator activation threads, and the test body reads createdBackends.Count and enumerates the list from the test thread. List<T> is not safe for concurrent add and read, so this can produce a torn count or a stale view and make the assertions flaky. Use a ConcurrentBag<PullCycleTestBackend> or guard the list with a lock. The same pattern appears at Line 2777 (publishedServices), Line 3103, and Line 3470.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Beutl.HeadlessUITests/VersionControlRestoreTests.cs` around lines 2685
- 2693, Replace the thread-unsafe List collections used for createdBackends and
the corresponding publishedServices, createdBackends, and publishedServices
instances at the other occurrences with a thread-safe collection such as
ConcurrentBag<T>. Update any affected initialization or enumeration code while
preserving the existing factory additions and test assertions.

5254-5254: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard InitializationOptions against concurrent access.

InitializeAsync adds to InitializationOptions, and tests read the list by index from the test thread. List<T> does not support concurrent add and read. Use a lock or a concurrent collection so the identity-retry assertions stay deterministic.

Also applies to: 5397-5397

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Beutl.HeadlessUITests/VersionControlRestoreTests.cs` at line 5254,
Protect the shared InitializationOptions collection used by InitializeAsync and
the test assertions from concurrent access. Update the InitializationOptions
declaration and every add/index-read in the affected test flows to use
consistent synchronization or a suitable concurrent collection, while preserving
deterministic identity-retry assertions at both referenced locations.
tests/Beutl.UnitTests/Editor/VersionControl/NestedRepositoryTests.cs (1)

568-579: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move the symbolic-link test helpers into the shared base fixture. Three fixtures that all derive from RealGitTestRepository now declare identical platform-aware symlink helpers, so the same Assert.Ignore policy is duplicated in three places and can drift.

  • tests/Beutl.UnitTests/Editor/VersionControl/NestedRepositoryTests.cs#L568-L579: remove CreateFileSymbolicLinkOrIgnore and the neighbouring CreateDirectorySymbolicLinkOrIgnore, and call the base-class helpers instead.
  • tests/Beutl.UnitTests/Editor/VersionControl/GitCliVersionControlServiceTests.cs#L2219-L2230: remove CreateFileSymbolicLinkOrIgnore and call the base-class helper.
  • tests/Beutl.UnitTests/Editor/VersionControl/GitCliRunnerTests.cs#L643-L656: remove CreateDirectorySymbolicLinkOrIgnore and call the base-class helper. Add both helpers as protected static members of RealGitTestRepository.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Beutl.UnitTests/Editor/VersionControl/NestedRepositoryTests.cs` around
lines 568 - 579, Centralize the platform-aware symlink helpers in
RealGitTestRepository as protected static members, preserving their existing
Assert.Ignore behavior. In
tests/Beutl.UnitTests/Editor/VersionControl/NestedRepositoryTests.cs:568-579,
remove both local helpers and use the base helpers; in
tests/Beutl.UnitTests/Editor/VersionControl/GitCliVersionControlServiceTests.cs:2219-2230,
remove CreateFileSymbolicLinkOrIgnore and call the base helper; in
tests/Beutl.UnitTests/Editor/VersionControl/GitCliRunnerTests.cs:643-656, remove
CreateDirectorySymbolicLinkOrIgnore and call the base helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs`:
- Around line 3589-3594: Update CommitAllCoreAsync and the related flow around
FindIgnoredExistingRequiredProjectPathAsync so the pre-commit ignore probe uses
only the bounded required hygiene paths and representative media paths produced
by GetRequiredProjectRelativePaths, rather than enumerating every file beneath
resources. Remove or bypass the full-tree EnumerateRequiredProjectFiles walk,
and ensure inaccessible directories cannot cause the save to fail; alternatively
cache its result and invalidate it through RepositoryWatcher.

---

Nitpick comments:
In `@tests/Beutl.HeadlessUITests/VersionControlRestoreTests.cs`:
- Around line 2941-2948: Replace the reflection-based assignment of
_configurationActivationQuiesced in the affected test with an internal test hook
on VersionControlCoordinator that exposes the quiescence signal for controlled
setup. Update the test to assign synchronousReadiness through that hook,
preserving the existing synchronization behavior without accessing private
fields.
- Around line 2685-2693: Replace the thread-unsafe List collections used for
createdBackends and the corresponding publishedServices, createdBackends, and
publishedServices instances at the other occurrences with a thread-safe
collection such as ConcurrentBag<T>. Update any affected initialization or
enumeration code while preserving the existing factory additions and test
assertions.
- Line 5254: Protect the shared InitializationOptions collection used by
InitializeAsync and the test assertions from concurrent access. Update the
InitializationOptions declaration and every add/index-read in the affected test
flows to use consistent synchronization or a suitable concurrent collection,
while preserving deterministic identity-retry assertions at both referenced
locations.

In
`@tests/Beutl.UnitTests/Editor/VersionControl/GitCliVersionControlServiceTests.cs`:
- Around line 457-496: Rename
EnsureRepositoryHygieneAsync_keeps_user_Lfs_overrides_after_the_managed_block to
describe the exercised InitializeAsync behavior, and add an assertion verifying
the expected runner.LfsInstallCalls value. Keep the existing attribute-file and
Git check-attr assertions unchanged.

In `@tests/Beutl.UnitTests/Editor/VersionControl/NestedRepositoryTests.cs`:
- Around line 568-579: Centralize the platform-aware symlink helpers in
RealGitTestRepository as protected static members, preserving their existing
Assert.Ignore behavior. In
tests/Beutl.UnitTests/Editor/VersionControl/NestedRepositoryTests.cs:568-579,
remove both local helpers and use the base helpers; in
tests/Beutl.UnitTests/Editor/VersionControl/GitCliVersionControlServiceTests.cs:2219-2230,
remove CreateFileSymbolicLinkOrIgnore and call the base helper; in
tests/Beutl.UnitTests/Editor/VersionControl/GitCliRunnerTests.cs:643-656, remove
CreateDirectorySymbolicLinkOrIgnore and call the base helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 461b4ac6-9c25-4b18-8afb-68e22877b0a5

📥 Commits

Reviewing files that changed from the base of the PR and between ff40b91 and 6b67633.

📒 Files selected for processing (17)
  • docs/specs/005-project-git-versioning/research.md
  • src/Beutl.Core/Project.cs
  • src/Beutl.Core/ProjectItem.cs
  • src/Beutl.Editor/VersionControl/GitCliRunner.cs
  • src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs
  • src/Beutl.Editor/VersionControl/VersionControlModels.cs
  • src/Beutl.ProjectSystem/ProjectSystem/Scene.cs
  • src/Beutl/Services/VersionControlCoordinator.cs
  • src/Beutl/ViewModels/MenuBarViewModel.Files.cs
  • tests/Beutl.E2ETests/Scenarios/ProjectPersistenceTests.cs
  • tests/Beutl.HeadlessUITests/ShutdownPipelineTests.cs
  • tests/Beutl.HeadlessUITests/VersionControlRestoreTests.cs
  • tests/Beutl.HeadlessUITests/VersionControlSaveTests.cs
  • tests/Beutl.HeadlessUITests/VersionControlTabViewTests.cs
  • tests/Beutl.UnitTests/Editor/VersionControl/GitCliRunnerTests.cs
  • tests/Beutl.UnitTests/Editor/VersionControl/GitCliVersionControlServiceTests.cs
  • tests/Beutl.UnitTests/Editor/VersionControl/NestedRepositoryTests.cs
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/Beutl.Core/ProjectItem.cs
  • src/Beutl.ProjectSystem/ProjectSystem/Scene.cs
  • docs/specs/005-project-git-versioning/research.md
  • tests/Beutl.HeadlessUITests/VersionControlTabViewTests.cs
  • src/Beutl.Core/Project.cs
  • tests/Beutl.HeadlessUITests/ShutdownPipelineTests.cs
  • src/Beutl/ViewModels/MenuBarViewModel.Files.cs
  • src/Beutl.Editor/VersionControl/GitCliRunner.cs
  • src/Beutl.Editor/VersionControl/VersionControlModels.cs
  • src/Beutl/Services/VersionControlCoordinator.cs

Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b67633e6b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Beutl.Editor/VersionControl/RepositoryWatcher.cs Outdated
Comment thread src/Beutl.Editor/VersionControl/GitInstallationLocator.cs Outdated
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d58e2ff669

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ce5fb2f10

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Beutl/ViewModels/Dialogs/CreateNewProjectViewModel.cs Outdated
Comment thread src/Beutl/ViewModels/MenuBarViewModel.cs Outdated
Comment thread src/Beutl.Editor/VersionControl/GitCliRunner.cs
Comment thread src/Beutl/Services/VersionControlCoordinator.cs Outdated
Comment thread src/Beutl/Services/VersionControlCoordinator.cs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1dcdd3353c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Beutl/Services/VersionControlCoordinator.cs
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs Outdated
Comment thread src/Beutl.Editor/VersionControl/GitCliRunner.cs
Make pull recovery durable across restarts, verify marker and transition ownership at every apply boundary, and keep project open/close serialization transactional. Harden Git process, branch, environment, LFS, path, and UI integration contracts with regression coverage.

BREAKING CHANGE: Beutl.Editor IProjectVersionControlCoordinator implementations must now provide pending-recovery notifications, listing/actions, and typed ProjectRecoveryResult dispositions. Beutl CreateNewProjectViewModel now requires ProjectService, IProjectVersionControlInitializer, and an identity callback, while MenuBarViewModel requires IProjectVersionControlSession.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c664b1e9a2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Beutl.Core/Project.cs Outdated
Comment thread src/Beutl/Services/VersionControlCoordinator.cs
Comment thread src/Beutl/Services/VersionControlCoordinator.cs Outdated
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs Outdated
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f964e76dd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Beutl/ViewModels/MenuBarViewModel.cs
Comment thread src/Beutl.Editor/ProjectPackageService.cs Outdated
Comment thread src/Beutl/Services/VersionControlCoordinator.cs
Comment thread src/Beutl/Services/VersionControlCoordinator.cs
Comment thread src/Beutl.Editor/VersionControl/ProjectConflictMarkerScanner.cs Outdated
Comment thread src/Beutl.Editor/VersionControl/RepositoryWatcher.cs Outdated
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

No TODO comments were found.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Code Coverage

Package Line Rate Branch Rate Complexity Health
Beutl 31% 22% 12726
Beutl.AgentToolkit 90% 74% 7523
Beutl.Api 31% 19% 1422
Beutl.Configuration 65% 41% 447
Beutl.Controls 35% 14% 5557
Beutl.Core 69% 60% 3122
Beutl.Editor 85% 78% 4946
Beutl.Editor.Components 28% 18% 10608
Beutl.Embedding.MediaFoundation 6% 8% 1374
Beutl.Engine 69% 58% 19233
Beutl.Engine.SourceGenerators 59% 44% 540
Beutl.ExceptionHandler 0% 0% 45
Beutl.Extensibility 71% 76% 167
Beutl.Extensions.AVFoundation 5% 2% 202
Beutl.Extensions.FFmpeg 28% 26% 738
Beutl.Extensions.FFmpeg.Core 50% 30% 323
Beutl.FFmpegIpc 27% 35% 858
Beutl.FFmpegWorker 4% 4% 898
Beutl.Language 49% 50% 1623
Beutl.NodeGraph 26% 17% 2477
Beutl.PackageTools.UI 0% 0% 676
Beutl.ProjectSystem 76% 62% 1269
Beutl.Testing.Headless 88% 92% 15
Beutl.Threading 100% 90% 137
Beutl.Utilities 94% 87% 358
Beutl.WaitingDialog 0% 0% 36
Iciclecreek.Avalonia.Terminal 35% 23% 1164
XTerm.NET 15% 7% 2009
Summary 50% (106554 / 215162) 40% (24919 / 62746) 80493

Minimum allowed line rate is 0%

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d932f8e472

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs Outdated
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs
Comment thread src/Beutl/Services/VersionControlCoordinator.cs
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs Outdated
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs Outdated
Validate branch names at each point of use, refresh repository metadata watchers, keep portable packages free of reserved metadata and links, and bound conflict-marker scans. Serialize menu command initialization and retain output-operation leases for the full lifetime of render jobs.

BREAKING CHANGE: In Beutl.AgentToolkit, RenderTools now requires an IOutputOperationLeaseProvider. RenderJobManager.Enqueue now requires a caller-owned output lease; ownership transfers to the job after a successful enqueue.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b9bb2670f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Beutl.AgentToolkit/Tools/RenderTools.cs
Comment thread src/Beutl.Editor/ProjectPackageService.cs
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs
Restore initialization state when initial snapshots fail, detect external repository operations before mutations, and classify local-ahead pulls correctly.

Reject imported packages containing portable .git metadata aliases and harden repository discovery parsing.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6fc3daedcb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs Outdated
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs Outdated
Comment thread src/Beutl/ViewModels/MainViewModel.cs
Comment thread src/Beutl.Editor/VersionControl/GitCliRunner.cs
Preserve referenced symbolic-link resources in project packages, retain paged history across unchanged status refreshes, and align Git LFS policy with every advertised media extension.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84531ec3d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Beutl/ViewModels/MenuBarViewModel.cs
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs Outdated
Comment thread src/Beutl/Services/VersionControlCoordinator.cs Outdated
Comment thread src/Beutl.Editor/VersionControl/GitCliVersionControlService.cs Outdated
Reject external Git operations across pull transitions and rollback, preserve literal Unix paths, atomically update local identity, reject CLI-special branch names, and keep BatchMode for default OpenSSH variants.
Cancel and await every accepted render job before project close, including lease release when cancellation callbacks fail.

BREAKING CHANGE: In Beutl.AgentToolkit, RenderJobManager no longer implements IDisposable. Use DisposeAsync or await using so disposal cancels and drains all accepted render jobs before releasing their output-operation leases.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd9f3167d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Jobs are serialized (single-flight) because all stills share the one RenderThread and each export
// builds its own graphics context; concurrent background renders would race those resources.
public sealed class RenderJobManager : IDisposable
public sealed class RenderJobManager : IAsyncDisposable

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add the required breaking-change footer

Fresh evidence beyond the existing threads is that the exact reviewed commit df9e12713581080ea7865cc7a4a3f64b74d94e1d has a feat!: subject and a ## Breaking changes body section, but no literal BREAKING CHANGE: footer, even though this line changes the public RenderJobManager contract from IDisposable to IAsyncDisposable and the commit changes other public constructors. Add the required footer with the migration and affected projects so Conventional Commits release tooling does not omit it.

AGENTS.md reference: AGENTS.md:L62-L62

Useful? React with 👍 / 👎.

Comment on lines +323 to +324
if (ShouldSkipEntry(file, isDirectory: false))
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Materialize linked project files before reopening staging

Fresh evidence beyond the earlier linked-media-asset thread is that this skips structural project files too: when the opened .bep, a referenced .scene, or a .belm is a symlink (or lies beneath a linked directory), the staging copy omits it before ExportAsync reopens the temporary project at line 80. The later ExternalResourceCollector relocation cannot repair that ordering, so export either fails outright or reloads the staging project without the missing item; retain arbitrary-link exclusion while materializing referenced project files before the temporary restore.

Useful? React with 👍 / 👎.

Comment on lines +116 to +118
await _versionControlInitializer.InitializeCurrentProjectAsync(
_requestIdentityAsync,
CancellationToken.None);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle version-control initialization failures during creation

When tracking is selected and initialization throws—for example because an initial commit hook rejects the commit, a repository lock appears, or a Git command fails—this callback lets the exception escape after the project dialog has already closed and the project was successfully created. CreateProject only handles its own persistence errors, while the menu's enable-version-control path explicitly catches and passes initialization failures to ex.Handle(); handle this call similarly so the user receives an error instead of a faulted or unhandled create command.

Useful? React with 👍 / 👎.

Comment on lines +163 to +164
int bufferedLength = overlapLength + read;
if (buffer.AsSpan(0, bufferedLength).IndexOf(s_conflictMarkerBytes) >= 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.

P2 Badge Match conflict markers only at valid marker lines

Fresh evidence beyond the earlier linked-file scanner thread is that this byte search accepts <<<<<<< anywhere in a serialized project file, without requiring a line boundary or the matching separator/end markers. A legitimate text property such as "Text": "<<<<<<< ours" therefore triggers the conflict-warning notification every time the project opens even though the JSON and Git state are valid; restrict detection to actual conflict-marker lines and preferably require the corresponding ======= and >>>>>>> markers.

Useful? React with 👍 / 👎.

Comment on lines +688 to +691
return await _versionControlCoordinator.RestoreToNewBranchAsync(
commit.Sha,
branchName.Trim(),
CancellationToken.None);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Cancel stale restore prompts when the repository changes

When the user starts “restore to new branch” for project A and the published service is replaced while the branch-name prompt is open, this continuation never checks _serviceRevision, _service, or _disposed and invokes the global coordinator with A's commit against the now-current project B. If both projects are in the same enclosing repository, A's SHA is valid there, so accepting the subsequent confirmation can restore B's files from the wrong commit; bind the prompt to the originating service generation and cancel or reject it after a rebind.

Useful? React with 👍 / 👎.

Reapply LFS hygiene when the setting changes, including across internal project transitions, and preserve custom pre-push hooks by falling back to the non-LFS policy when local Git LFS installation fails.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3e2d3000bd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +476 to +477
string? remoteUrl = await RequestRemoteUrlAsync(
HasRemote.Value ? RemoteUrl.Value : null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Bind remote prompts to the originating repository

When project A is replaced while the remote-URL dialog is open, this continuation does not check _serviceRevision, _service, or _disposed, and SetRemoteAsync therefore applies A's submitted URL to the coordinator's now-current project B. The Publish Branch path then proceeds to PushAsync, potentially pushing B to a remote selected for A; capture the originating service generation and abort after the prompt if the binding changed.

Useful? React with 👍 / 👎.

Comment on lines +223 to +225
string? branchName = await RequestNewBranchNameAsync();
if (_disposed
|| lifetimeToken.IsCancellationRequested

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cancel branch creation after a project rebind

When the user opens the new-branch prompt for project A and project B becomes current before the prompt returns, rebinding cancels only _serviceBindingCancellation; this continuation checks the view-model lifetime token instead and invokes the global coordinator for B. Accepting the prompt can consequently create a branch, safety-commit dirty files, and clear undo state in the wrong project, so validate the captured service revision after awaiting the prompt.

Useful? React with 👍 / 👎.

public MenuBarViewModel(
ProjectService projectService,
EditorService editorService,
IProjectVersionControlSession versionControlSession)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add the required breaking-change footer

Fresh evidence relative to the earlier threads is that the exact reviewed commit 5f8a9af54859319d96b134f5c102b0327dcd664c is a real commit with a feat!: subject but no literal BREAKING CHANGE: footer, although this line makes the public MenuBarViewModel constructor incompatible. The ## Breaking changes body section is not a Conventional Commits footer; add the required footer describing migration and affected projects so release tooling exposes the change.

AGENTS.md reference: AGENTS.md:L62-L62

Useful? React with 👍 / 👎.

Comment on lines +234 to +238
private static readonly string[] s_lfsAttributeLines =
s_supportedMediaExtensions
.Select(static extension =>
$"resources/**/*{CreateCaseInsensitiveGlob(extension)} "
+ "filter=lfs diff=lfs merge=lfs -text")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cover media outside the resources directory

When a project references an in-project media file under the root or a directory such as assets/clip.mp4, this generated policy does not match it because every LFS pattern is restricted to resources/**. GetLargeMediaPath repeats the same restriction, so even with the media extension supported the file receives neither LFS filtering nor the configured large-media warning and is committed directly into history; apply both policies to supported media anywhere inside the project scope.

Useful? React with 👍 / 👎.

Comment on lines +719 to +723
List<string> lockPaths =
[
Path.Combine(gitDirectory, "index.lock"),
Path.Combine(gitDirectory, "HEAD.lock"),
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recover stale remote-tracking ref locks

After an interrupted fetch leaves a stale lock such as .git/refs/remotes/origin/main.lock, pull continues to fail but this recovery scan can only return index.lock, HEAD.lock, or the checked-out local-branch lock. A Git 2.43 reproduction reports cannot lock ref 'refs/remotes/origin/main' followed by the standard stale-process guidance, which makes RepositoryLockFailed fire while GetRecoverableRepositoryLock still returns null; safely recognize the lock path reported for remote-tracking refs so the existing recovery UI can remove it.

Useful? React with 👍 / 👎.

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.

2 participants