Skip to content

Make Git mirror recovery safe - #11

Merged
programmersd21 merged 27 commits into
mainfrom
fix/git-mirror-cache-recovery
Oct 10, 2026
Merged

programmersd21 merged 27 commits into
mainfrom
fix/git-mirror-cache-recovery

Conversation

@programmersd21

@programmersd21 programmersd21 commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Recover incomplete mirrors in one install. Clone to staging, serialize per-mirror operations, and avoid following symlinks during cleanup. Add recovery, symlink-safety, and concurrent-install tests. GCC, Clang, macOS, Windows, and Rust checks pass.

@sourcery-ai

sourcery-ai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR makes failed Git mirror creation recoverable by deleting partial cache directories, keeps the standalone and Rust-embedded C implementations synchronized, and adds an integration test that verifies installation heals a mirror missing HEAD.

Sequence diagram for recovering an incomplete Git mirror cache

sequenceDiagram
    participant Flint
    participant pkg_ensure_mirror
    participant Git
    participant Cache

    Flint->>pkg_ensure_mirror: pkg_ensure_mirror(mirror, url)
    pkg_ensure_mirror->>Cache: Check mirror HEAD
    alt HEAD is missing
        pkg_ensure_mirror->>Git: pkg_git(git clone --mirror)
        alt Clone fails
            Git-->>pkg_ensure_mirror: non-zero status
            pkg_ensure_mirror->>Cache: pkg_remove_tree(mirror)
            pkg_ensure_mirror-->>Flint: false
        else Clone succeeds
            Git-->>pkg_ensure_mirror: success
            pkg_ensure_mirror-->>Flint: true
        end
    end
    Flint->>pkg_ensure_mirror: Retry installation
    pkg_ensure_mirror->>Git: pkg_git(git clone --mirror)
    Git-->>pkg_ensure_mirror: success
Loading

Flow diagram for healing a corrupted package mirror

flowchart TD
    A[flint pkg install] --> B{Mirror HEAD exists?}
    B -->|Yes| C[Use cached mirror]
    B -->|No| D[Attempt git clone --mirror]
    D -->|Failure| E["pkg_remove_tree(mirror)"]
    E --> F[Return false]
    F --> G[Retry installation]
    G --> D
    D -->|Success| H[Install package]
Loading

File-Level Changes

Change Details Files
Clean up failed mirror clones so subsequent package installs can recreate the cache.
  • Remove the mirror directory after a failed git clone --mirror.
  • Return the boolean failure value expected by pkg_ensure_mirror while preserving the clone error.
  • Apply the implementation identically to both C source copies.
src/pkg.c
rust/flintlang/c-src/src/pkg.c
Add an integration regression test for repairing a damaged Git mirror cache.
  • Locate an existing cached mirror and remove its HEAD.
  • Verify flint pkg install succeeds and the dependency remains importable.
tests/pkg_test.sh

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 37ec497e-62d2-4aef-b7b0-31f209d0f2ef

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@sourcery-ai sourcery-ai Bot 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.

Hey - I've found 5 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/pkg.c" line_range="869" />
<code_context>
+			 * Remove it so the next attempt can start cleanly rather
+			 * than failing forever on a non-empty cache directory.
+			 */
+			(void)pkg_remove_tree(mirror);
 			snprintf(error, error_size, "cannot clone '%s'", url);
-			return NULL;
</code_context>
<issue_to_address>
**Symlink targets lose data**

When the mirror path is a symlink to a non-empty directory and cloning fails, `pkg_remove_tree` calls `opendir` on the mirror path, follows the symlink, and recursively deletes the target’s contents; the later `rmdir` fails on the symlink only after the files are gone. The failed clone can therefore delete data outside the cache.

Check whether the mirror path is a symlink before recursing, and remove the link itself without traversing its target.

Also at `rust/flintlang/c-src/src/pkg.c:869`.
</issue_to_address>

### Comment 2
<location path="src/pkg.c" line_range="869" />
<code_context>
+			 * Remove it so the next attempt can start cleanly rather
+			 * than failing forever on a non-empty cache directory.
+			 */
+			(void)pkg_remove_tree(mirror);
 			snprintf(error, error_size, "cannot clone '%s'", url);
-			return NULL;
</code_context>
<issue_to_address>
**Concurrent mirrors are deleted**

When two Flint processes initialize the same mirror concurrently and one clone fails while the other process is creating or using it, the failing process calls `pkg_remove_tree` on the shared mirror and deletes the other process’s in-progress or completed clone. The other install can then fail, and the cache must be cloned again.

Serialize operations per mirror or clone into isolated staging directories and publish only after a successful clone.

Also at `rust/flintlang/c-src/src/pkg.c:869`.
</issue_to_address>

### Comment 3
<location path="src/pkg.c" line_range="869-871" />
<code_context>
+			 * Remove it so the next attempt can start cleanly rather
+			 * than failing forever on a non-empty cache directory.
+			 */
+			(void)pkg_remove_tree(mirror);
 			snprintf(error, error_size, "cannot clone '%s'", url);
-			return NULL;
+			return false;
 		}
 		return true;
</code_context>
<issue_to_address>
**Mirror repair needs another run**

When an existing mirror is missing `HEAD` but still contains other files, `git clone --mirror` rejects the non-empty mirror, then `pkg_ensure_mirror` removes it and returns `false` without retrying; `pkg_resolve_git` aborts the current install, so the user must run it again to restore the cache.

Retry the clone after removing the damaged mirror, or remove the damaged mirror before starting the clone.

Also at `rust/flintlang/c-src/src/pkg.c:869-871`, `tests/pkg_test.sh:162-166`.
</issue_to_address>

### Comment 4
<location path="src/pkg.c" line_range="869" />
<code_context>
+			 * Remove it so the next attempt can start cleanly rather
+			 * than failing forever on a non-empty cache directory.
+			 */
+			(void)pkg_remove_tree(mirror);
 			snprintf(error, error_size, "cannot clone '%s'", url);
-			return NULL;
</code_context>
<issue_to_address>
**Cleanup failures go unnoticed**

When removing the partial mirror fails, `pkg_ensure_mirror` discards the result of `pkg_remove_tree`, leaving the non-empty mirror in place; later clone attempts fail against it again, and callers receive no indication that cleanup failed.

Check the cleanup result, report its failure, and do not retry cloning into the uncleared mirror.

Also at `rust/flintlang/c-src/src/pkg.c:869`.
</issue_to_address>

### Comment 5
<location path="tests/pkg_test.sh" line_range="165-167" />
<code_context>
+mirror=$(find "$HOME/.flint/git" -mindepth 1 -maxdepth 1 -type d | head -n 1)
+expect "git mirror cache exists" "yes" "$([ -n "$mirror" ] && echo yes || echo no)"
+rm -f "$mirror/HEAD"
+out=$("$FLINT" pkg install 2>&1) && code=0 || code=$?
+expect "install recovers a mirror missing HEAD" "0" "$code"
+expect "recovered mirror still imports" "HEY zed" "$("$FLINT" use.fl 2>&1)"
+
 cat > "$WORK/gitsrc/main.fl" <<'EOF'
</code_context>
<issue_to_address>
**Recovery test skips the mirror**

When the installed module still matches the lockfile content hash, `pkg install` verifies the existing module and skips resolving the dependency, so it never checks the mirror after the test removes `HEAD`; the install and import assertions pass without exercising cache recovery.

Change the test setup so the install must resolve the Git dependency instead of reusing the already-installed module.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 5 findings to address first, and if the cleanup is wrong, a failed clone can recursively remove the mirror cache, including any repository state already present in that directory. The cache is bounded and can be recreated by retrying the clone, but reverting the change would not restore data already removed.

Blocking findings: src/pkg.c:869, src/pkg.c:869, src/pkg.c:871, src/pkg.c:869, tests/pkg_test.sh:167


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/pkg.c Outdated
Comment thread src/pkg.c Outdated
Comment thread src/pkg.c Outdated
Comment thread src/pkg.c Outdated
Comment thread tests/pkg_test.sh Outdated
@programmersd21 programmersd21 changed the title Recover cleanly from incomplete Git mirror caches Make Git mirror recovery safe Oct 10, 2026
@programmersd21
programmersd21 merged commit 726f124 into main Oct 10, 2026
7 checks passed
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