This is an automated email from the ASF dual-hosted git repository. luigidemasi pushed a commit to branch test/133-local-git-fixtures in repository https://gitbox.apache.org/repos/asf/camel-kit.git
commit 2294f7effacb425322c9b77c604c473689e8ffa8 Author: Luigi De Masi <[email protected]> AuthorDate: Thu Oct 29 11:45:55 2026 +0100 fix: recover Git caches with invalid repository metadata Re-clone cached document repositories when opening their .git directory fails, with a regression for an interrupted clone that left an empty .git. Use local file transport for GitRepoFetcher fixtures, keep explicit signing overrides, and remove redundant peeled-tag filtering. Clarify that the shallow assertion checks history from HEAD. Follow-up to #133. Co-authored-by: OpenAI Codex <[email protected]> --- knowledge/CHANGELOG.md | 5 +++ .../kit/knowledge/indexer/GitRepoFetcher.java | 6 +-- .../kit/knowledge/indexer/GitRepoFetcherTest.java | 52 +++++++++++----------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/knowledge/CHANGELOG.md b/knowledge/CHANGELOG.md index e839ec8c..75507626 100644 --- a/knowledge/CHANGELOG.md +++ b/knowledge/CHANGELOG.md @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Recover cached document repositories with an invalid `.git` directory by re-cloning + them instead of failing every indexing attempt. + ### Monorepo migration - Move Kit and Knowledge into sibling `kit/` and `knowledge/` directories with a shared, diff --git a/knowledge/indexer/src/main/java/org/apache/camel/kit/knowledge/indexer/GitRepoFetcher.java b/knowledge/indexer/src/main/java/org/apache/camel/kit/knowledge/indexer/GitRepoFetcher.java index 1119b41d..16fb4b8e 100644 --- a/knowledge/indexer/src/main/java/org/apache/camel/kit/knowledge/indexer/GitRepoFetcher.java +++ b/knowledge/indexer/src/main/java/org/apache/camel/kit/knowledge/indexer/GitRepoFetcher.java @@ -38,6 +38,7 @@ public class GitRepoFetcher { * * <ul> * <li>If repo exists with .git → fetch + hard reset to latest (fast, only downloads delta)</li> + * <li>If opening or updating the existing repo fails → delete and re-clone</li> * <li>If repo exists without .git (corrupted) → delete and re-clone</li> * <li>If repo doesn't exist → fresh shallow clone</li> * </ul> @@ -92,7 +93,7 @@ public class GitRepoFetcher { * Lists all tags from a remote Git repository without cloning it. Uses JGit's {@code ls-remote --tags} equivalent. * * @param repoUrl the remote repository URL - * @return tag names without {@code refs/tags/} prefix, excluding dereferenced {@code ^{}} entries + * @return tag names without {@code refs/tags/} prefix */ public static List<String> listRemoteTags(String repoUrl) throws IOException { try { @@ -103,7 +104,6 @@ public class GitRepoFetcher { return refs.stream() .map(ref -> ref.getName().replaceFirst("^refs/tags/", "")) - .filter(name -> !name.endsWith("^{}")) .toList(); } catch (GitAPIException e) { throw new IOException("Failed to list tags from " + repoUrl, e); @@ -135,7 +135,7 @@ public class GitRepoFetcher { long elapsed = (System.currentTimeMillis() - start) / 1000; LOG.info(" Updated: {} ({}s)", localName, elapsed); - } catch (GitAPIException e) { + } catch (GitAPIException | IOException e) { // Update failed — fall back to fresh clone LOG.warn(" Update failed for {}: {} — re-cloning", localName, e.getMessage()); deleteDirectory(repoDir); diff --git a/knowledge/indexer/src/test/java/org/apache/camel/kit/knowledge/indexer/GitRepoFetcherTest.java b/knowledge/indexer/src/test/java/org/apache/camel/kit/knowledge/indexer/GitRepoFetcherTest.java index 9d8043d9..a6307446 100644 --- a/knowledge/indexer/src/test/java/org/apache/camel/kit/knowledge/indexer/GitRepoFetcherTest.java +++ b/knowledge/indexer/src/test/java/org/apache/camel/kit/knowledge/indexer/GitRepoFetcherTest.java @@ -1,7 +1,6 @@ package org.apache.camel.kit.knowledge.indexer; import java.io.IOException; -import java.net.InetSocketAddress; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; @@ -10,8 +9,6 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.transport.Daemon; -import org.eclipse.jgit.transport.resolver.FileResolver; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,7 +26,6 @@ class GitRepoFetcherTest { Path tempDir; private Git upstream; - private Daemon daemon; private String repoUrl; private GitRepoFetcher fetcher; private RevCommit latest; @@ -37,11 +33,6 @@ class GitRepoFetcherTest { @BeforeEach void createLocalRepository() throws Exception { upstream = Git.init().setDirectory(tempDir.resolve("upstream").toFile()).setInitialBranch("main").call(); - var config = upstream.getRepository().getConfig(); - config.setBoolean("commit", null, "gpgSign", false); - config.setBoolean("tag", null, "gpgSign", false); - config.setBoolean("tag", null, "forceSignAnnotated", false); - config.save(); writeUpstream("document.md", "first version"); writeUpstream("notes.txt", "not a Markdown document"); writeUpstream("nested/other.md", "nested document"); @@ -51,25 +42,14 @@ class GitRepoFetcherTest { latest = commit("second version"); tag("v2"); - // The local file transport cannot exercise the shallow-clone protocol. - daemon = new Daemon(new InetSocketAddress("127.0.0.1", 0)); - daemon.setRepositoryResolver(new FileResolver<>(tempDir.toFile(), true)); - daemon.setTimeout(10); - daemon.start(); - repoUrl = "git://127.0.0.1:" + daemon.getAddress().getPort() + "/upstream"; + repoUrl = tempDir.resolve("upstream").toUri().toString(); fetcher = new GitRepoFetcher(tempDir.resolve("cache")); } @AfterEach - void closeLocalRepository() throws Exception { - try { - if (daemon != null) { - daemon.stopAndWait(); - } - } finally { - if (upstream != null) { - upstream.close(); - } + void closeLocalRepository() { + if (upstream != null) { + upstream.close(); } } @@ -84,7 +64,7 @@ class GitRepoFetcherTest { assertTrue(clone.getRepository().getObjectDatabase().getShallowCommits().contains(latest.getId())); var history = clone.log().call().iterator(); assertEquals(latest.getId(), history.next().getId()); - assertFalse(history.hasNext(), "The clone must not contain the first commit's history"); + assertFalse(history.hasNext(), "History from HEAD must stop at the shallow boundary"); } } @@ -145,8 +125,9 @@ class GitRepoFetcherTest { Path repoDir = fetcher.fetchRepo(repoUrl, "v1", "documents", true); Path sentinel = repoDir.resolve(".git/retained-on-cache-hit"); Files.writeString(sentinel, "cached repository"); - daemon.stopAndWait(); - assertFalse(daemon.isRunning()); + upstream.close(); + upstream = null; + Files.move(tempDir.resolve("upstream"), tempDir.resolve("offline-upstream")); assertEquals(repoDir, fetcher.fetchRepo(repoUrl, "v1", "documents", true)); @@ -184,6 +165,23 @@ class GitRepoFetcherTest { assertFalse(Files.exists(partial)); } + @Test + void recoversCloneWithInvalidGitDirectory() throws Exception { + Path repoDir = tempDir.resolve("cache/documents"); + Files.createDirectories(repoDir.resolve(".git")); + Path partial = repoDir.resolve("partial.md"); + Files.writeString(partial, "interrupted clone"); + + assertEquals(repoDir, fetcher.fetchRepo(repoUrl, "main", "documents")); + + assertEquals("second version", Files.readString(repoDir.resolve("document.md"))); + assertFalse(Files.exists(partial)); + try (Git clone = Git.open(repoDir.toFile())) { + assertEquals(latest.getId(), clone.getRepository().resolve("HEAD")); + assertTrue(clone.status().call().isClean()); + } + } + @Test void reclonesFromSuppliedUrlAfterUpdateFails() throws Exception { Path repoDir = fetcher.fetchRepo(repoUrl, "main", "documents");
