diff options
author | Dariusz Luksza <dariusz.luksza@gmail.com> | 2024-02-12 09:58:41 +0000 |
---|---|---|
committer | Dariusz Luksza <dariusz.luksza@gmail.com> | 2024-02-12 09:58:41 +0000 |
commit | 4f85ca0dc0ad12ee03d4015d1357962bbce1d157 (patch) | |
tree | 6cb2653217032cbfbc7dee1d4b306e74487105da | |
parent | 03d465c49c7957fc51fcc3e2f32021913b113784 (diff) | |
parent | 56d040b8c73c224aea19c6930bf0892b5b9aca1c (diff) | |
download | jgit-4f85ca0dc0ad12ee03d4015d1357962bbce1d157.tar.gz jgit-4f85ca0dc0ad12ee03d4015d1357962bbce1d157.zip |
Merge branch 'stable-6.7' into stable-6.8
* stable-6.7:
Improve handling of NFS stale handle errors
Fix handling of missing pack index file
Add tests for handling pack files removal during fetch
Change-Id: I409d1dc3354ad65c048d2c40f39071a1207246c6
3 files changed, 169 insertions, 5 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackHandleDeletedPackFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackHandleDeletedPackFileTest.java new file mode 100644 index 0000000000..417ce61df2 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackHandleDeletedPackFileTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2023, Dariusz Luksza <dariusz.luksza@gmail.com> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.transport; + +import static org.junit.Assert.fail; +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.internal.storage.file.GC; +import org.eclipse.jgit.internal.storage.file.Pack; +import org.eclipse.jgit.internal.storage.pack.PackExt; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.junit.TestRepository.CommitBuilder; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.UploadPack.RequestPolicy; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class UploadPackHandleDeletedPackFileTest + extends LocalDiskRepositoryTestCase { + + private FileRepository server; + + private TestRepository<FileRepository> remote; + + private Repository client; + + private RevCommit head; + + @Parameter + public boolean emptyCommit; + + @Parameters(name="empty commit: {0}") + public static Collection<Boolean[]> initTestData() { + return Arrays.asList( + new Boolean[][] { { Boolean.TRUE }, { Boolean.FALSE } }); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + server = createBareRepository(); + server.getConfig().setString("protocol", null, "version", "2"); + + remote = new TestRepository<>(server); + client = new InMemoryRepository(new DfsRepositoryDescription("client")); + + setupServerRepo(); + head = server.parseCommit(server.resolve(HEAD)); + } + + @Test + public void testV2PackFileRemovedDuringUploadPack() throws Exception { + doRemovePackFileDuringUploadPack(PackExt.PACK); + } + + @Test + public void testV2IdxFileRemovedDuringUploadPack() throws Exception { + doRemovePackFileDuringUploadPack(PackExt.INDEX); + } + + @Test + public void testV2BitmapFileRemovedDuringUploadPack() throws Exception { + doRemovePackFileDuringUploadPack(PackExt.BITMAP_INDEX); + } + + private void doRemovePackFileDuringUploadPack(PackExt packExt) + throws Exception { + Object ctx = new Object(); + TestProtocol testProtocol = new TestProtocol<>( + (Object req, Repository db) -> { + UploadPack up = new UploadPack(db); + up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT); + Collection<Pack> packs = server.getObjectDatabase() + .getPacks(); + assertEquals("single pack expected", 1, packs.size()); + Pack pack = packs.iterator().next(); + + try { + addNewCommit(); + + new GC(remote.getRepository()).gc(); + + pack.getPackFile().create(packExt).delete(); + } catch (Exception e) { + fail("GC or pack file removal failed"); + } + + return up; + }, null); + + URIish uri = testProtocol.register(ctx, server); + + try (Transport tn = testProtocol.open(uri, client, "server")) { + tn.fetch(NullProgressMonitor.INSTANCE, + Collections.singletonList(new RefSpec(head.name()))); + assertTrue(client.getObjectDatabase().has(head)); + } + } + + private void addNewCommit() throws Exception { + CommitBuilder commit = remote.commit().message("2"); + if (!emptyCommit) { + commit = commit.add("test2.txt", remote.blob("2")); + } + remote.update("master", commit.parent(head).create()); + } + + private void setupServerRepo() throws Exception { + RevCommit commit0 = remote.commit().message("0") + .add("test.txt", remote.blob("0")) + .create(); + remote.update("master", commit0); + + new GC(remote.getRepository()).gc(); // create pack files + + head = remote.commit().message("1").parent(commit0) + .add("test1.txt", remote.blob("1")) + .create(); + remote.update("master", head); + } +}
\ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java index 90f9811679..4f1d348844 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java @@ -64,6 +64,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.util.Hex; +import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.LongList; import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.RawParseUtils; @@ -682,7 +683,12 @@ public class Pack implements Iterable<PackIndex.MutableEntry> { // exceptions signaling permanent problems with a pack openFail(true, pe); throw pe; - } catch (IOException | RuntimeException ge) { + } catch (IOException ioe) { + // mark this packfile as invalid when NFS stale file handle error + // occur + openFail(FileUtils.isStaleFileHandleInCausalChain(ioe), ioe); + throw ioe; + } catch (RuntimeException ge) { // generic exceptions could be transient so we should not mark the // pack invalid to avoid false MissingObjectExceptions openFail(false, ge); @@ -1153,9 +1159,19 @@ public class Pack implements Iterable<PackIndex.MutableEntry> { return idx; } } catch (FileNotFoundException e) { - // Once upon a time this bitmap file existed. Now it - // has been removed. Most likely an external gc has - // removed this packfile and the bitmap + // Once upon a time the bitmap or index files existed. Now one + // of them has been removed. Most likely an external gc has + // removed index, packfile or the bitmap + bitmapIdxFile = null; + return null; + } catch (IOException e) { + if (!FileUtils.isStaleFileHandleInCausalChain(e)) { + throw e; + } + // Ignore NFS stale handle exception the same way as + // FileNotFoundException above. + bitmapIdxFile = null; + return null; } bitmapIdxFile = null; return null; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java index 99987cd635..c42d1c8866 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java @@ -64,7 +64,9 @@ public abstract class PackIndex public static PackIndex open(File idxFile) throws IOException { try (SilentFileInputStream fd = new SilentFileInputStream( idxFile)) { - return read(fd); + return read(fd); + } catch (FileNotFoundException e) { + throw e; } catch (IOException ioe) { throw new IOException( MessageFormat.format(JGitText.get().unreadablePackIndex, |