diff options
author | Matthias Sohn <matthias.sohn@sap.com> | 2024-11-26 14:46:00 +0100 |
---|---|---|
committer | Matthias Sohn <matthias.sohn@sap.com> | 2024-11-26 14:46:00 +0100 |
commit | 856c1c37c0f2be4fc219547602a0cf69a8030cfd (patch) | |
tree | e60d8a1c2d2eb802d71a90b0456e333fdb0cb331 | |
parent | 437bb4841fa6a77dfee3684e0f52b7aa083edafe (diff) | |
parent | 683d444ca98fae7c0c6b1cb73f7278dd9114fcab (diff) | |
download | jgit-856c1c37c0f2be4fc219547602a0cf69a8030cfd.tar.gz jgit-856c1c37c0f2be4fc219547602a0cf69a8030cfd.zip |
Merge branch 'master' into stable-7.1
* master:
PackDirectory: Filter out tmp GC pack files
Add pack-refs command to the CLI
Test advertised capabilities with protocol V0 and allow*Sha1InWant
Align request policies with CGit
GitTimeParser: Fix multiple errorprone and style comments
PersonIdent: Preserve the timezone when copying with new time
PersonIdent: Revert @since of #getZoneId
tests/BasicTest: Use java.time constructors for PersonIdent
RawParseUtils test: Use java.time to create PersonIdents
Change default similarity score to 50(%) to match git's default
Pack.java: Recover more often in Pack.copyAsIs2()
Change-Id: I1386addb355ffba09dcbf1a6e8634d605c0d3b5c
24 files changed, 640 insertions, 260 deletions
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/PackRefsTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/PackRefsTest.java new file mode 100644 index 0000000000..b4d4ea9e56 --- /dev/null +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/PackRefsTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. + * and other copyright owners as documented in the project's IP log. + * + * 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.pgm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.lib.CLIRepositoryTestCase; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.junit.Before; +import org.junit.Test; + +public class PackRefsTest extends CLIRepositoryTestCase { + private Git git; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + git = new Git(db); + git.commit().setMessage("initial commit").call(); + } + + @Test + public void tagPacked() throws Exception { + git.tag().setName("test").call(); + git.packRefs().call(); + assertEquals(Ref.Storage.PACKED, + git.getRepository().exactRef("refs/tags/test").getStorage()); + } + + @Test + public void nonTagRefNotPackedWithoutAll() throws Exception { + git.branchCreate().setName("test").call(); + git.packRefs().call(); + assertEquals(Ref.Storage.LOOSE, + git.getRepository().exactRef("refs/heads/test").getStorage()); + } + + @Test + public void nonTagRefPackedWithAll() throws Exception { + git.branchCreate().setName("test").call(); + git.packRefs().setAll(true).call(); + assertEquals(Ref.Storage.PACKED, + git.getRepository().exactRef("refs/heads/test").getStorage()); + } + + @Test + public void refTableCompacted() throws Exception { + ((FileRepository) git.getRepository()).convertRefStorage( + ConfigConstants.CONFIG_REF_STORAGE_REFTABLE, false, false); + + git.commit().setMessage("test commit").call(); + File tableDir = new File(db.getDirectory(), Constants.REFTABLE); + File[] reftables = tableDir.listFiles(); + assertNotNull(reftables); + assertTrue(reftables.length > 2); + + git.packRefs().call(); + + reftables = tableDir.listFiles(); + assertNotNull(reftables); + assertEquals(2, reftables.length); + } +} diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin index 08d37278de..41b0091b77 100644 --- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin +++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin @@ -26,6 +26,7 @@ org.eclipse.jgit.pgm.LsTree org.eclipse.jgit.pgm.Merge org.eclipse.jgit.pgm.MergeBase org.eclipse.jgit.pgm.MergeTool +org.eclipse.jgit.pgm.PackRefs org.eclipse.jgit.pgm.Push org.eclipse.jgit.pgm.ReceivePack org.eclipse.jgit.pgm.Reflog diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties index 50ee809b98..d24b639a31 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties @@ -257,6 +257,7 @@ updating=Updating {0}..{1} usage_Abbrev=Instead of using the default number of hexadecimal digits (which will vary according to the number of objects in the repository with a default of 7) of the abbreviated object name, use <n> digits, or as many digits as needed to form a unique object name. An <n> of 0 will suppress long format, only showing the closest tag. usage_addRenormalize=Apply the "clean" process freshly to tracked files to forcibly add them again to the index. This implies -u. usage_Aggressive=This option will cause gc to more aggressively optimize the repository at the expense of taking much more time +usage_All=Pack all refs, except hidden refs, broken refs, and symbolic refs. usage_AlwaysFallback=Show uniquely abbreviated commit object as fallback usage_bareClone=Make a bare Git repository. That is, instead of creating [DIRECTORY] and placing the administrative files in [DIRECTORY]/.git, make the [DIRECTORY] itself the $GIT_DIR. usage_extraArgument=Pass an extra argument to a merge driver. Currently supported are "-X ours" and "-X theirs". @@ -300,6 +301,7 @@ usage_Match=Only consider tags matching the given glob(7) pattern or patterns, e usage_MergeBase=Find as good common ancestors as possible for a merge usage_MergesTwoDevelopmentHistories=Merges two development histories usage_PackKeptObjects=Include objects in packs locked by a ".keep" file when repacking +usage_PackRefs=Pack heads and tags for efficient repository access usage_PreserveOldPacks=Preserve old pack files by moving them into the preserved subdirectory instead of deleting them after repacking usage_PrunePreserved=Remove the preserved subdirectory containing previously preserved old pack files before repacking, and before preserving more old pack files usage_ReadDirCache= Read the DirCache 100 times diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/PackRefs.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/PackRefs.java new file mode 100644 index 0000000000..ee05f5ca0b --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/PackRefs.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. + * and other copyright owners as documented in the project's IP log. + * + * 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.pgm; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.TextProgressMonitor; +import org.kohsuke.args4j.Option; + +@Command(common = true, usage = "usage_PackRefs") +class PackRefs extends TextBuiltin { + @Option(name = "--all", usage = "usage_All") + private boolean all; + + @Override + protected void run() { + Git git = Git.wrap(db); + try { + git.packRefs().setProgressMonitor(new TextProgressMonitor(errw)) + .setAll(all).call(); + } catch (GitAPIException e) { + throw die(e.getMessage(), e); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java index c57295518d..f84be21e82 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java @@ -19,7 +19,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import java.io.File; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.BrokenBarrierException; @@ -31,6 +30,8 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.PackRefsCommand; +import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.junit.TestRepository.BranchBuilder; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; @@ -49,7 +50,7 @@ public class GcPackRefsTest extends GcTestCase { RevBlob a = tr.blob("a"); tr.lightweightTag("t", a); - gc.packRefs(); + packRefs(false); assertSame(repo.exactRef("refs/tags/t").getStorage(), Storage.PACKED); } @@ -60,7 +61,7 @@ public class GcPackRefsTest extends GcTestCase { String name = repo.findRef(ref).getName(); Path dir = repo.getCommonDirectory().toPath().resolve(name).getParent(); assertNotNull(dir); - gc.packRefs(); + packRefs(true); assertFalse(Files.exists(dir)); } @@ -75,9 +76,9 @@ public class GcPackRefsTest extends GcTestCase { Callable<Integer> packRefs = () -> { syncPoint.await(); try { - gc.packRefs(); + packRefs(false); return 0; - } catch (IOException e) { + } catch (GitAPIException e) { return 1; } }; @@ -102,7 +103,7 @@ public class GcPackRefsTest extends GcTestCase { "refs/tags/t1")); try { refLock.lock(); - gc.packRefs(); + packRefs(false); } finally { refLock.unlock(); } @@ -145,7 +146,7 @@ public class GcPackRefsTest extends GcTestCase { Future<Result> result2 = pool.submit(() -> { refUpdateLockedRef.await(); - gc.packRefs(); + packRefs(false); packRefsDone.await(); return null; }); @@ -173,19 +174,20 @@ public class GcPackRefsTest extends GcTestCase { assertEquals(repo.exactRef("HEAD").getTarget().getName(), "refs/heads/master"); assertNull(repo.exactRef("HEAD").getTarget().getObjectId()); - gc.packRefs(); + PackRefsCommand packRefsCommand = git.packRefs().setAll(true); + packRefsCommand.call(); assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); assertEquals(repo.exactRef("HEAD").getTarget().getName(), "refs/heads/master"); assertNull(repo.exactRef("HEAD").getTarget().getObjectId()); git.checkout().setName("refs/heads/side").call(); - gc.packRefs(); + packRefsCommand.call(); assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); // check for detached HEAD git.checkout().setName(first.getName()).call(); - gc.packRefs(); + packRefsCommand.call(); assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); } @@ -208,7 +210,7 @@ public class GcPackRefsTest extends GcTestCase { assertEquals(repo.exactRef("HEAD").getTarget().getName(), "refs/heads/master"); assertNull(repo.exactRef("HEAD").getTarget().getObjectId()); - gc.packRefs(); + packRefs(true); assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); assertEquals(repo.exactRef("HEAD").getTarget().getName(), "refs/heads/master"); @@ -216,9 +218,14 @@ public class GcPackRefsTest extends GcTestCase { // check for non-detached HEAD repo.updateRef(Constants.HEAD).link("refs/heads/side"); - gc.packRefs(); + packRefs(true); assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); assertEquals(repo.exactRef("HEAD").getTarget().getObjectId(), second.getId()); } + + private void packRefs(boolean all) throws GitAPIException { + new PackRefsCommand(repo).setAll(all).call(); + } + } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java index 49e8a7be66..e067beb317 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java @@ -28,6 +28,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.time.Instant; +import java.time.ZoneOffset; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; @@ -374,8 +375,10 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { public void test009_CreateCommitOldFormat() throws IOException { final ObjectId treeId = insertTree(new TreeFormatter()); final CommitBuilder c = new CommitBuilder(); - c.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); - c.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); + c.setAuthor(new PersonIdent(author, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4))); + c.setCommitter(new PersonIdent(committer, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4))); c.setMessage("A Commit\n"); c.setTreeId(treeId); assertEquals(treeId, c.getTreeId()); @@ -411,7 +414,8 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { final TagBuilder t = new TagBuilder(); t.setObjectId(emptyId, Constants.OBJ_BLOB); t.setTag("test020"); - t.setTagger(new PersonIdent(author, 1154236443000L, -4 * 60)); + t.setTagger(new PersonIdent(author, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4))); t.setMessage("test020 tagged\n"); ObjectId actid = insertTag(t); assertEquals("6759556b09fbb4fd8ae5e315134481cc25d46954", actid.name()); @@ -419,8 +423,9 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { RevTag mapTag = parseTag(actid); assertEquals(Constants.OBJ_BLOB, mapTag.getObject().getType()); assertEquals("test020 tagged\n", mapTag.getFullMessage()); - assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag - .getTaggerIdent()); + assertEquals(new PersonIdent(author, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)), + mapTag.getTaggerIdent()); assertEquals("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", mapTag .getObject().getId().name()); } @@ -434,7 +439,8 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { final TagBuilder t = new TagBuilder(); t.setObjectId(almostEmptyTreeId, Constants.OBJ_TREE); t.setTag("test021"); - t.setTagger(new PersonIdent(author, 1154236443000L, -4 * 60)); + t.setTagger(new PersonIdent(author, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4))); t.setMessage("test021 tagged\n"); ObjectId actid = insertTag(t); assertEquals("b0517bc8dbe2096b419d42424cd7030733f4abe5", actid.name()); @@ -442,8 +448,9 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { RevTag mapTag = parseTag(actid); assertEquals(Constants.OBJ_TREE, mapTag.getObject().getType()); assertEquals("test021 tagged\n", mapTag.getFullMessage()); - assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag - .getTaggerIdent()); + assertEquals(new PersonIdent(author, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)), + mapTag.getTaggerIdent()); assertEquals("417c01c8795a35b8e835113a85a5c0c1c77f67fb", mapTag .getObject().getId().name()); } @@ -455,17 +462,18 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); final CommitBuilder almostEmptyCommit = new CommitBuilder(); - almostEmptyCommit.setAuthor(new PersonIdent(author, 1154236443000L, - -2 * 60)); // not exactly the same - almostEmptyCommit.setCommitter(new PersonIdent(author, 1154236443000L, - -2 * 60)); + almostEmptyCommit.setAuthor(new PersonIdent(author, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-2))); + almostEmptyCommit.setCommitter(new PersonIdent(author, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-2))); almostEmptyCommit.setMessage("test022\n"); almostEmptyCommit.setTreeId(almostEmptyTreeId); ObjectId almostEmptyCommitId = insertCommit(almostEmptyCommit); final TagBuilder t = new TagBuilder(); t.setObjectId(almostEmptyCommitId, Constants.OBJ_COMMIT); t.setTag("test022"); - t.setTagger(new PersonIdent(author, 1154236443000L, -4 * 60)); + t.setTagger(new PersonIdent(author, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4))); t.setMessage("test022 tagged\n"); ObjectId actid = insertTag(t); assertEquals("0ce2ebdb36076ef0b38adbe077a07d43b43e3807", actid.name()); @@ -473,8 +481,9 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { RevTag mapTag = parseTag(actid); assertEquals(Constants.OBJ_COMMIT, mapTag.getObject().getType()); assertEquals("test022 tagged\n", mapTag.getFullMessage()); - assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag - .getTaggerIdent()); + assertEquals(new PersonIdent(author, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)), + mapTag.getTaggerIdent()); assertEquals("b5d3b45a96b340441f5abb9080411705c51cc86c", mapTag .getObject().getId().name()); } @@ -488,9 +497,9 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { CommitBuilder commit = new CommitBuilder(); commit.setTreeId(almostEmptyTreeId); commit.setAuthor(new PersonIdent("Joe H\u00e4cker", "joe@example.com", - 4294967295000L, 60)); + Instant.ofEpochMilli(4294967295000L), ZoneOffset.ofHours(1))); commit.setCommitter(new PersonIdent("Joe Hacker", "joe2@example.com", - 4294967295000L, 60)); + Instant.ofEpochMilli(4294967295000L), ZoneOffset.ofHours(1))); commit.setEncoding(UTF_8); commit.setMessage("\u00dcbergeeks"); ObjectId cid = insertCommit(commit); @@ -509,9 +518,9 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { CommitBuilder commit = new CommitBuilder(); commit.setTreeId(almostEmptyTreeId); commit.setAuthor(new PersonIdent("Joe H\u00e4cker", "joe@example.com", - 4294967295000L, 60)); + Instant.ofEpochMilli(4294967295000L), ZoneOffset.ofHours(1))); commit.setCommitter(new PersonIdent("Joe Hacker", "joe2@example.com", - 4294967295000L, 60)); + Instant.ofEpochMilli(4294967295000L), ZoneOffset.ofHours(1))); commit.setEncoding(ISO_8859_1); commit.setMessage("\u00dcbergeeks"); ObjectId cid = insertCommit(commit); @@ -544,8 +553,10 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { .fromString("00b1f73724f493096d1ffa0b0f1f1482dbb8c936"), treeId); final CommitBuilder c1 = new CommitBuilder(); - c1.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); - c1.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); + c1.setAuthor(new PersonIdent(author, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4))); + c1.setCommitter(new PersonIdent(committer, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4))); c1.setMessage("A Commit\n"); c1.setTreeId(treeId); assertEquals(treeId, c1.getTreeId()); @@ -555,8 +566,10 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { assertEquals(cmtid1, actid1); final CommitBuilder c2 = new CommitBuilder(); - c2.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); - c2.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); + c2.setAuthor(new PersonIdent(author, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4))); + c2.setCommitter(new PersonIdent(committer, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4))); c2.setMessage("A Commit 2\n"); c2.setTreeId(treeId); assertEquals(treeId, c2.getTreeId()); @@ -577,8 +590,10 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { assertEquals(actid1, rm2.getParent(0)); final CommitBuilder c3 = new CommitBuilder(); - c3.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); - c3.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); + c3.setAuthor(new PersonIdent(author, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4))); + c3.setCommitter(new PersonIdent(committer, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4))); c3.setMessage("A Commit 3\n"); c3.setTreeId(treeId); assertEquals(treeId, c3.getTreeId()); @@ -600,8 +615,10 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { assertEquals(actid2, rm3.getParent(1)); final CommitBuilder c4 = new CommitBuilder(); - c4.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60)); - c4.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60)); + c4.setAuthor(new PersonIdent(author, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4))); + c4.setCommitter(new PersonIdent(committer, + Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4))); c4.setMessage("A Commit 4\n"); c4.setTreeId(treeId); assertEquals(treeId, c3.getTreeId()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitTimeParserBadlyFormattedTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitTimeParserBadlyFormattedTest.java index e5f162d11a..a59d7bc7bb 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitTimeParserBadlyFormattedTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitTimeParserBadlyFormattedTest.java @@ -45,7 +45,7 @@ public class GitTimeParserBadlyFormattedTest { @DataPoints public static String[] getDataPoints() { - return new String[] { "", "1970", "3000.3000.3000", "3 yesterday ago", + return new String[] { "", ".", "...", "1970", "3000.3000.3000", "3 yesterday ago", "now yesterday ago", "yesterdays", "3.day. 2.week.ago", "day ago", "Gra Feb 21 15:35:00 2007 +0100", "Sun Feb 21 15:35:00 2007 +0100", diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_ParsePersonIdentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_ParsePersonIdentTest.java index 355bbbab16..e517889c83 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_ParsePersonIdentTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_ParsePersonIdentTest.java @@ -10,10 +10,13 @@ package org.eclipse.jgit.util; +import static java.time.Instant.EPOCH; +import static java.time.ZoneOffset.UTC; import static org.junit.Assert.assertEquals; -import java.util.Date; -import java.util.TimeZone; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; import org.eclipse.jgit.lib.PersonIdent; import org.junit.Test; @@ -22,8 +25,8 @@ public class RawParseUtils_ParsePersonIdentTest { @Test public void testParsePersonIdent_legalCases() { - final Date when = new Date(1234567890000L); - final TimeZone tz = TimeZone.getTimeZone("GMT-7"); + Instant when = Instant.ofEpochMilli(1234567890000L); + ZoneId tz = ZoneOffset.ofHours(-7); assertPersonIdent("Me <me@example.com> 1234567890 -0700", new PersonIdent("Me", "me@example.com", when, tz)); @@ -50,8 +53,8 @@ public class RawParseUtils_ParsePersonIdentTest { @Test public void testParsePersonIdent_fuzzyCases() { - final Date when = new Date(1234567890000L); - final TimeZone tz = TimeZone.getTimeZone("GMT-7"); + Instant when = Instant.ofEpochMilli(1234567890000L); + ZoneId tz = ZoneOffset.ofHours(-7); assertPersonIdent( "A U Thor <author@example.com>, C O. Miter <comiter@example.com> 1234567890 -0700", @@ -64,8 +67,8 @@ public class RawParseUtils_ParsePersonIdentTest { @Test public void testParsePersonIdent_incompleteCases() { - final Date when = new Date(1234567890000L); - final TimeZone tz = TimeZone.getTimeZone("GMT-7"); + Instant when = Instant.ofEpochMilli(1234567890000L); + ZoneId tz = ZoneOffset.ofHours(-7); assertPersonIdent("Me <> 1234567890 -0700", new PersonIdent("Me", "", when, tz)); @@ -76,26 +79,26 @@ public class RawParseUtils_ParsePersonIdentTest { assertPersonIdent(" <> 1234567890 -0700", new PersonIdent("", "", when, tz)); - assertPersonIdent("<>", new PersonIdent("", "", 0, 0)); + assertPersonIdent("<>", new PersonIdent("", "", EPOCH, UTC)); - assertPersonIdent(" <>", new PersonIdent("", "", 0, 0)); + assertPersonIdent(" <>", new PersonIdent("", "", EPOCH, UTC)); assertPersonIdent("<me@example.com>", new PersonIdent("", - "me@example.com", 0, 0)); + "me@example.com", EPOCH, UTC)); assertPersonIdent(" <me@example.com>", new PersonIdent("", - "me@example.com", 0, 0)); + "me@example.com", EPOCH, UTC)); - assertPersonIdent("Me <>", new PersonIdent("Me", "", 0, 0)); + assertPersonIdent("Me <>", new PersonIdent("Me", "", EPOCH, UTC)); assertPersonIdent("Me <me@example.com>", new PersonIdent("Me", - "me@example.com", 0, 0)); + "me@example.com", EPOCH, UTC)); assertPersonIdent("Me <me@example.com> 1234567890", new PersonIdent( - "Me", "me@example.com", 0, 0)); + "Me", "me@example.com", EPOCH, UTC)); assertPersonIdent("Me <me@example.com> 1234567890 ", new PersonIdent( - "Me", "me@example.com", 0, 0)); + "Me", "me@example.com", EPOCH, UTC)); } @Test diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters new file mode 100644 index 0000000000..9f8018d312 --- /dev/null +++ b/org.eclipse.jgit/.settings/.api_filters @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<component id="org.eclipse.jgit" version="2"> + <resource path="src/org/eclipse/jgit/diff/DiffDriver.java" type="org.eclipse.jgit.diff.DiffDriver"> + <filter id="1109393411"> + <message_arguments> + <message_argument value="6.10.1"/> + <message_argument value="org.eclipse.jgit.diff.DiffDriver"/> + </message_arguments> + </filter> + </resource> + <resource path="src/org/eclipse/jgit/diff/DiffFormatter.java" type="org.eclipse.jgit.diff.DiffFormatter"> + <filter id="1142947843"> + <message_arguments> + <message_argument value="6.10.1"/> + <message_argument value="format(EditList, RawText, RawText, DiffDriver)"/> + </message_arguments> + </filter> + <filter id="1142947843"> + <message_arguments> + <message_argument value="6.10.1"/> + <message_argument value="format(FileHeader, RawText, RawText, DiffDriver)"/> + </message_arguments> + </filter> + <filter id="1142947843"> + <message_arguments> + <message_argument value="6.10.1"/> + <message_argument value="writeHunkHeader(int, int, int, int, String)"/> + </message_arguments> + </filter> + </resource> + <resource path="src/org/eclipse/jgit/lib/Constants.java" type="org.eclipse.jgit.lib.Constants"> + <filter id="1142947843"> + <message_arguments> + <message_argument value="6.10.1"/> + <message_argument value="ATTR_BUILTIN_UNION_MERGE_DRIVER"/> + </message_arguments> + </filter> + </resource> + <resource path="src/org/eclipse/jgit/merge/ContentMergeStrategy.java" type="org.eclipse.jgit.merge.ContentMergeStrategy"> + <filter id="1176502275"> + <message_arguments> + <message_argument value="6.10.1"/> + <message_argument value="UNION"/> + </message_arguments> + </filter> + </resource> + <resource path="src/org/eclipse/jgit/merge/ResolveMerger.java" type="org.eclipse.jgit.merge.ResolveMerger"> + <filter id="336658481"> + <message_arguments> + <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/> + <message_argument value="attributesNodeProvider"/> + </message_arguments> + </filter> + <filter id="1142947843"> + <message_arguments> + <message_argument value="6.10.1"/> + <message_argument value="attributesNodeProvider"/> + </message_arguments> + </filter> + <filter id="1142947843"> + <message_arguments> + <message_argument value="6.10.1"/> + <message_argument value="setAttributesNodeProvider(AttributesNodeProvider)"/> + </message_arguments> + </filter> + </resource> +</component> diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 024ca77f21..acfe812a20 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -597,6 +597,8 @@ packInaccessible=Failed to access pack file {0}, caught {1} consecutive errors w packingCancelledDuringObjectsWriting=Packing cancelled during objects writing packObjectCountMismatch=Pack object count mismatch: pack {0} index {1}: {2} packRefs=Pack refs +packRefsFailed=Packing refs failed +packRefsSuccessful=Packed refs successfully packSizeNotSetYet=Pack size not yet set since it has not yet been received packTooLargeForIndexVersion1=Pack too large for index version 1 packWasDeleted=Pack file {0} was deleted, removing it from pack list diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java index 3dc53ec248..5bc035a46a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java @@ -714,6 +714,16 @@ public class Git implements AutoCloseable { } /** + * Return a command object to execute a {@code PackRefs} command + * + * @return a {@link org.eclipse.jgit.api.PackRefsCommand} + * @since 7.1 + */ + public PackRefsCommand packRefs() { + return new PackRefsCommand(repo); + } + + /** * Return a command object to find human-readable names of revisions. * * @return a {@link org.eclipse.jgit.api.NameRevCommand}. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PackRefsCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PackRefsCommand.java new file mode 100644 index 0000000000..29a69c5ac4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PackRefsCommand.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. + * and other copyright owners as documented in the project's IP log. + * + * 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.api; + +import java.io.IOException; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Repository; + +/** + * Optimize storage of references. + * + * @since 7.1 + */ +public class PackRefsCommand extends GitCommand<String> { + private ProgressMonitor monitor; + + private boolean all; + + /** + * Creates a new {@link PackRefsCommand} instance with default values. + * + * @param repo + * the repository this command will be used on + */ + public PackRefsCommand(Repository repo) { + super(repo); + this.monitor = NullProgressMonitor.INSTANCE; + } + + /** + * Set progress monitor + * + * @param monitor + * a progress monitor + * @return this instance + */ + public PackRefsCommand setProgressMonitor(ProgressMonitor monitor) { + this.monitor = monitor; + return this; + } + + /** + * Specify whether to pack all the references. + * + * @param all + * if <code>true</code> all the loose refs will be packed + * @return this instance + */ + public PackRefsCommand setAll(boolean all) { + this.all = all; + return this; + } + + /** + * Whether to pack all the references + * + * @return whether to pack all the references + */ + public boolean isAll() { + return all; + } + + @Override + public String call() throws GitAPIException { + checkCallable(); + try { + repo.getRefDatabase().packRefs(monitor, this); + return JGitText.get().packRefsSuccessful; + } catch (IOException e) { + throw new JGitInternalException(JGitText.get().packRefsFailed, e); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java index 5de7bac112..fb98df7c9e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java @@ -80,7 +80,7 @@ class SimilarityRenameDetector { private long[] matrix; /** Score a pair must exceed to be considered a rename. */ - private int renameScore = 60; + private int renameScore = 50; /** * File size threshold (in bytes) for detecting renames. Files larger diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 0980219e25..2d9d2c527c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -627,6 +627,8 @@ public class JGitText extends TranslationBundle { /***/ public String packingCancelledDuringObjectsWriting; /***/ public String packObjectCountMismatch; /***/ public String packRefs; + /***/ public String packRefsFailed; + /***/ public String packRefsSuccessful; /***/ public String packSizeNotSetYet; /***/ public String packTooLargeForIndexVersion1; /***/ public String packWasDeleted; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java index 80240e5062..25b7583b95 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java @@ -28,8 +28,10 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.api.PackRefsCommand; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.events.RefsChangedEvent; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.reftable.MergedReftable; import org.eclipse.jgit.internal.storage.reftable.ReftableBatchRefUpdate; import org.eclipse.jgit.internal.storage.reftable.ReftableDatabase; @@ -39,6 +41,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.RefRename; @@ -108,6 +111,22 @@ public class FileReftableDatabase extends RefDatabase { } /** + * {@inheritDoc} + * + * For Reftable, all the data is compacted into a single table. + */ + @Override + public void packRefs(ProgressMonitor pm, PackRefsCommand packRefs) + throws IOException { + pm.beginTask(JGitText.get().packRefs, 1); + try { + compactFully(); + } finally { + pm.endTask(); + } + } + + /** * Runs a full compaction for GC purposes. * @throws IOException on I/O errors */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java index 230dc6f45a..84c85659ff 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java @@ -33,6 +33,7 @@ import java.util.Set; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.attributes.AttributesNode; import org.eclipse.jgit.attributes.AttributesNodeProvider; @@ -599,7 +600,7 @@ public class FileRepository extends Repository { gc.setBackground(shouldAutoDetach()); try { gc.gc(); - } catch (ParseException | IOException e) { + } catch (ParseException | IOException | GitAPIException e) { throw new JGitInternalException(JGitText.get().gcFailed, e); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java index c9da4d8d67..7f3369364b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java @@ -63,6 +63,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.api.PackRefsCommand; +import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.CancelledException; import org.eclipse.jgit.errors.CorruptObjectException; @@ -233,9 +235,11 @@ public class GC { * @throws java.text.ParseException * If the configuration parameter "gc.pruneexpire" couldn't be * parsed + * @throws GitAPIException + * If packing refs failed */ public CompletableFuture<Collection<Pack>> gc() - throws IOException, ParseException { + throws IOException, ParseException, GitAPIException { if (!background) { return CompletableFuture.completedFuture(doGc()); } @@ -254,7 +258,7 @@ public class GC { gcLog.commit(); } return newPacks; - } catch (IOException | ParseException e) { + } catch (IOException | ParseException | GitAPIException e) { try { gcLog.write(e.getMessage()); StringWriter sw = new StringWriter(); @@ -277,7 +281,8 @@ public class GC { return (executor != null) ? executor : WorkQueue.getExecutor(); } - private Collection<Pack> doGc() throws IOException, ParseException { + private Collection<Pack> doGc() + throws IOException, ParseException, GitAPIException { if (automatic && !needGc()) { return Collections.emptyList(); } @@ -286,7 +291,8 @@ public class GC { return Collections.emptyList(); } pm.start(6 /* tasks */); - packRefs(); + new PackRefsCommand(repo).setProgressMonitor(pm).setAll(true) + .call(); // TODO: implement reflog_expire(pm, repo); Collection<Pack> newPacks = repack(); prune(Collections.emptySet()); @@ -780,43 +786,6 @@ public class GC { } /** - * Pack ref storage. For a RefDirectory database, this packs all - * non-symbolic, loose refs into packed-refs. For Reftable, all of the data - * is compacted into a single table. - * - * @throws java.io.IOException - * if an IO error occurred - */ - public void packRefs() throws IOException { - RefDatabase refDb = repo.getRefDatabase(); - if (refDb instanceof FileReftableDatabase) { - // TODO: abstract this more cleanly. - pm.beginTask(JGitText.get().packRefs, 1); - try { - ((FileReftableDatabase) refDb).compactFully(); - } finally { - pm.endTask(); - } - return; - } - - Collection<Ref> refs = refDb.getRefsByPrefix(Constants.R_REFS); - List<String> refsToBePacked = new ArrayList<>(refs.size()); - pm.beginTask(JGitText.get().packRefs, refs.size()); - try { - for (Ref ref : refs) { - checkCancelled(); - if (!ref.isSymbolic() && ref.getStorage().isLoose()) - refsToBePacked.add(ref.getName()); - pm.update(1); - } - ((RefDirectory) repo.getRefDatabase()).pack(refsToBePacked); - } finally { - pm.endTask(); - } - } - - /** * Packs all objects which reachable from any of the heads into one pack * file. Additionally all objects which are not reachable from any head but * which are reachable from any of the other refs (e.g. tags), special refs 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 be457644d9..3518342f97 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 @@ -416,185 +416,196 @@ public class Pack implements Iterable<PackIndex.MutableEntry> { final CRC32 crc2 = validate ? new CRC32() : null; final byte[] buf = out.getCopyBuffer(); + boolean isHeaderWritten = false; // Rip apart the header so we can discover the size. // - readFully(src.offset, buf, 0, 20, curs); - int c = buf[0] & 0xff; - final int typeCode = (c >> 4) & 7; - long inflatedLength = c & 15; - int shift = 4; - int headerCnt = 1; - while ((c & 0x80) != 0) { - c = buf[headerCnt++] & 0xff; - inflatedLength += ((long) (c & 0x7f)) << shift; - shift += 7; - } - - if (typeCode == Constants.OBJ_OFS_DELTA) { - do { + try { + readFully(src.offset, buf, 0, 20, curs); + + int c = buf[0] & 0xff; + final int typeCode = (c >> 4) & 7; + long inflatedLength = c & 15; + int shift = 4; + int headerCnt = 1; + while ((c & 0x80) != 0) { c = buf[headerCnt++] & 0xff; - } while ((c & 128) != 0); - if (validate) { - assert(crc1 != null && crc2 != null); - crc1.update(buf, 0, headerCnt); - crc2.update(buf, 0, headerCnt); + inflatedLength += ((long) (c & 0x7f)) << shift; + shift += 7; } - } else if (typeCode == Constants.OBJ_REF_DELTA) { - if (validate) { + + if (typeCode == Constants.OBJ_OFS_DELTA) { + do { + c = buf[headerCnt++] & 0xff; + } while ((c & 128) != 0); + if (validate) { + assert(crc1 != null && crc2 != null); + crc1.update(buf, 0, headerCnt); + crc2.update(buf, 0, headerCnt); + } + } else if (typeCode == Constants.OBJ_REF_DELTA) { + if (validate) { + assert(crc1 != null && crc2 != null); + crc1.update(buf, 0, headerCnt); + crc2.update(buf, 0, headerCnt); + } + + readFully(src.offset + headerCnt, buf, 0, 20, curs); + if (validate) { + assert(crc1 != null && crc2 != null); + crc1.update(buf, 0, 20); + crc2.update(buf, 0, 20); + } + headerCnt += 20; + } else if (validate) { assert(crc1 != null && crc2 != null); crc1.update(buf, 0, headerCnt); crc2.update(buf, 0, headerCnt); } - readFully(src.offset + headerCnt, buf, 0, 20, curs); - if (validate) { - assert(crc1 != null && crc2 != null); - crc1.update(buf, 0, 20); - crc2.update(buf, 0, 20); - } - headerCnt += 20; - } else if (validate) { - assert(crc1 != null && crc2 != null); - crc1.update(buf, 0, headerCnt); - crc2.update(buf, 0, headerCnt); - } + final long dataOffset = src.offset + headerCnt; + final long dataLength = src.length; + final long expectedCRC; + final ByteArrayWindow quickCopy; - final long dataOffset = src.offset + headerCnt; - final long dataLength = src.length; - final long expectedCRC; - final ByteArrayWindow quickCopy; - - // Verify the object isn't corrupt before sending. If it is, - // we report it missing instead. - // - try { - quickCopy = curs.quickCopy(this, dataOffset, dataLength); + // Verify the object isn't corrupt before sending. If it is, + // we report it missing instead. + // + try { + quickCopy = curs.quickCopy(this, dataOffset, dataLength); - if (validate && idx().hasCRC32Support()) { - assert(crc1 != null); - // Index has the CRC32 code cached, validate the object. - // - expectedCRC = idx().findCRC32(src); - if (quickCopy != null) { - quickCopy.crc32(crc1, dataOffset, (int) dataLength); - } else { - long pos = dataOffset; - long cnt = dataLength; - while (cnt > 0) { - final int n = (int) Math.min(cnt, buf.length); - readFully(pos, buf, 0, n, curs); - crc1.update(buf, 0, n); - pos += n; - cnt -= n; + if (validate && idx().hasCRC32Support()) { + assert(crc1 != null); + // Index has the CRC32 code cached, validate the object. + // + expectedCRC = idx().findCRC32(src); + if (quickCopy != null) { + quickCopy.crc32(crc1, dataOffset, (int) dataLength); + } else { + long pos = dataOffset; + long cnt = dataLength; + while (cnt > 0) { + final int n = (int) Math.min(cnt, buf.length); + readFully(pos, buf, 0, n, curs); + crc1.update(buf, 0, n); + pos += n; + cnt -= n; + } } + if (crc1.getValue() != expectedCRC) { + setCorrupt(src.offset); + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + Long.valueOf(src.offset), getPackFile())); + } + } else if (validate) { + // We don't have a CRC32 code in the index, so compute it + // now while inflating the raw data to get zlib to tell us + // whether or not the data is safe. + // + Inflater inf = curs.inflater(); + byte[] tmp = new byte[1024]; + if (quickCopy != null) { + quickCopy.check(inf, tmp, dataOffset, (int) dataLength); + } else { + assert(crc1 != null); + long pos = dataOffset; + long cnt = dataLength; + while (cnt > 0) { + final int n = (int) Math.min(cnt, buf.length); + readFully(pos, buf, 0, n, curs); + crc1.update(buf, 0, n); + inf.setInput(buf, 0, n); + while (inf.inflate(tmp, 0, tmp.length) > 0) + continue; + pos += n; + cnt -= n; + } + } + if (!inf.finished() || inf.getBytesRead() != dataLength) { + setCorrupt(src.offset); + throw new EOFException(MessageFormat.format( + JGitText.get().shortCompressedStreamAt, + Long.valueOf(src.offset))); + } + assert(crc1 != null); + expectedCRC = crc1.getValue(); + } else { + expectedCRC = -1; } - if (crc1.getValue() != expectedCRC) { - setCorrupt(src.offset); - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().objectAtHasBadZlibStream, - Long.valueOf(src.offset), getPackFile())); - } - } else if (validate) { - // We don't have a CRC32 code in the index, so compute it - // now while inflating the raw data to get zlib to tell us - // whether or not the data is safe. + } catch (DataFormatException dataFormat) { + setCorrupt(src.offset); + + CorruptObjectException corruptObject = new CorruptObjectException( + MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + Long.valueOf(src.offset), getPackFile()), + dataFormat); + + throw new StoredObjectRepresentationNotAvailableException( + corruptObject); + } + + if (quickCopy != null) { + // The entire object fits into a single byte array window slice, + // and we have it pinned. Write this out without copying. // - Inflater inf = curs.inflater(); - byte[] tmp = new byte[1024]; - if (quickCopy != null) { - quickCopy.check(inf, tmp, dataOffset, (int) dataLength); - } else { - assert(crc1 != null); + out.writeHeader(src, inflatedLength); + isHeaderWritten = true; + quickCopy.write(out, dataOffset, (int) dataLength); + + } else if (dataLength <= buf.length) { + // Tiny optimization: Lots of objects are very small deltas or + // deflated commits that are likely to fit in the copy buffer. + // + if (!validate) { long pos = dataOffset; long cnt = dataLength; while (cnt > 0) { final int n = (int) Math.min(cnt, buf.length); readFully(pos, buf, 0, n, curs); - crc1.update(buf, 0, n); - inf.setInput(buf, 0, n); - while (inf.inflate(tmp, 0, tmp.length) > 0) - continue; pos += n; cnt -= n; } } - if (!inf.finished() || inf.getBytesRead() != dataLength) { - setCorrupt(src.offset); - throw new EOFException(MessageFormat.format( - JGitText.get().shortCompressedStreamAt, - Long.valueOf(src.offset))); - } - assert(crc1 != null); - expectedCRC = crc1.getValue(); + out.writeHeader(src, inflatedLength); + isHeaderWritten = true; + out.write(buf, 0, (int) dataLength); } else { - expectedCRC = -1; - } - } catch (DataFormatException dataFormat) { - setCorrupt(src.offset); - - CorruptObjectException corruptObject = new CorruptObjectException( - MessageFormat.format( - JGitText.get().objectAtHasBadZlibStream, - Long.valueOf(src.offset), getPackFile()), - dataFormat); - - throw new StoredObjectRepresentationNotAvailableException( - corruptObject); - - } catch (IOException ioError) { - throw new StoredObjectRepresentationNotAvailableException(ioError); - } - - if (quickCopy != null) { - // The entire object fits into a single byte array window slice, - // and we have it pinned. Write this out without copying. - // - out.writeHeader(src, inflatedLength); - quickCopy.write(out, dataOffset, (int) dataLength); - - } else if (dataLength <= buf.length) { - // Tiny optimization: Lots of objects are very small deltas or - // deflated commits that are likely to fit in the copy buffer. - // - if (!validate) { + // Now we are committed to sending the object. As we spool it out, + // check its CRC32 code to make sure there wasn't corruption between + // the verification we did above, and us actually outputting it. + // long pos = dataOffset; long cnt = dataLength; while (cnt > 0) { final int n = (int) Math.min(cnt, buf.length); readFully(pos, buf, 0, n, curs); + if (validate) { + assert(crc2 != null); + crc2.update(buf, 0, n); + } + if (!isHeaderWritten) { + out.writeHeader(src, inflatedLength); + isHeaderWritten = true; + } + out.write(buf, 0, n); pos += n; cnt -= n; } - } - out.writeHeader(src, inflatedLength); - out.write(buf, 0, (int) dataLength); - } else { - // Now we are committed to sending the object. As we spool it out, - // check its CRC32 code to make sure there wasn't corruption between - // the verification we did above, and us actually outputting it. - // - out.writeHeader(src, inflatedLength); - long pos = dataOffset; - long cnt = dataLength; - while (cnt > 0) { - final int n = (int) Math.min(cnt, buf.length); - readFully(pos, buf, 0, n, curs); if (validate) { assert(crc2 != null); - crc2.update(buf, 0, n); + if (crc2.getValue() != expectedCRC) { + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + Long.valueOf(src.offset), getPackFile())); + } } - out.write(buf, 0, n); - pos += n; - cnt -= n; } - if (validate) { - assert(crc2 != null); - if (crc2.getValue() != expectedCRC) { - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().objectAtHasBadZlibStream, - Long.valueOf(src.offset), getPackFile())); - } + } catch (IOException ioError) { + if (!isHeaderWritten) { + throw new StoredObjectRepresentationNotAvailableException(ioError); } + throw ioError; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java index 8221cff442..51a8148838 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java @@ -545,7 +545,7 @@ class PackDirectory { for (String name : nameList) { try { PackFile pack = new PackFile(directory, name); - if (pack.getPackExt() != null) { + if (pack.getPackExt() != null && !pack.isTmpGCFile()) { Map<PackExt, PackFile> packByExt = packFilesByExtById .get(pack.getId()); if (packByExt == null) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java index 19979d0ed5..c9b05ad025 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java @@ -27,6 +27,7 @@ public class PackFile extends File { private static final long serialVersionUID = 1L; private static final String PREFIX = "pack-"; //$NON-NLS-1$ + private static final String TMP_GC_PREFIX = ".tmp-"; //$NON-NLS-1$ private final String base; // PREFIX + id i.e. // pack-0123456789012345678901234567890123456789 @@ -126,6 +127,13 @@ public class PackFile extends File { } /** + * @return whether the file is a temporary GC file + */ + public boolean isTmpGCFile() { + return id.startsWith(TMP_GC_PREFIX); + } + + /** * Create a new similar PackFile with the given extension instead. * * @param ext diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java index 604868133e..6aa1157e37 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java @@ -57,6 +57,7 @@ import java.util.stream.Stream; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.api.PackRefsCommand; import org.eclipse.jgit.errors.InvalidObjectIdException; import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.errors.MissingObjectException; @@ -69,6 +70,7 @@ import org.eclipse.jgit.lib.CoreConfig.TrustLooseRefStat; import org.eclipse.jgit.lib.CoreConfig.TrustPackedRefsStat; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefComparator; import org.eclipse.jgit.lib.RefDatabase; @@ -287,6 +289,33 @@ public class RefDirectory extends RefDatabase { clearReferences(); } + /** + * {@inheritDoc} + * + * For a RefDirectory database, by default this packs non-symbolic, loose + * tag refs into packed-refs. If {@code all} flag is set, this packs all the + * non-symbolic, loose refs. + */ + @Override + public void packRefs(ProgressMonitor pm, PackRefsCommand packRefs) + throws IOException { + String prefix = packRefs.isAll() ? R_REFS : R_TAGS; + Collection<Ref> refs = getRefsByPrefix(prefix); + List<String> refsToBePacked = new ArrayList<>(refs.size()); + pm.beginTask(JGitText.get().packRefs, refs.size()); + try { + for (Ref ref : refs) { + if (!ref.isSymbolic() && ref.getStorage().isLoose()) { + refsToBePacked.add(ref.getName()); + } + pm.update(1); + } + pack(refsToBePacked); + } finally { + pm.endTask(); + } + } + @Override public boolean isNameConflicting(String name) throws IOException { // Cannot be nested within an existing reference. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java index a59dde7083..5d3db9e6ee 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java @@ -240,7 +240,8 @@ public class PersonIdent implements Serializable { */ @Deprecated(since = "7.1") public PersonIdent(PersonIdent pi, Date aWhen) { - this(pi.getName(), pi.getEmailAddress(), aWhen.toInstant()); + this(pi.getName(), pi.getEmailAddress(), aWhen.toInstant(), + pi.tzOffset); } /** @@ -408,7 +409,7 @@ public class PersonIdent implements Serializable { * Get the time zone id * * @return the time zone id - * @since 7.1 + * @since 6.1 */ public ZoneId getZoneId() { return tzOffset; @@ -496,4 +497,3 @@ public class PersonIdent implements Serializable { return r.toString(); } } - diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java index 114246beb2..09cb5a83dd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java @@ -26,6 +26,7 @@ import java.util.stream.Stream; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.api.PackRefsCommand; /** * Abstraction of name to {@link org.eclipse.jgit.lib.ObjectId} mapping. @@ -593,4 +594,22 @@ public abstract class RefDatabase { } return null; } + + /** + * Optimize pack ref storage. + * + * @param pm + * a progress monitor + * + * @param packRefs + * {@link PackRefsCommand} to control ref packing behavior + * + * @throws java.io.IOException + * if an IO error occurred + * @since 7.1 + */ + public void packRefs(ProgressMonitor pm, PackRefsCommand packRefs) + throws IOException { + // nothing + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java index e238e3e92e..7d00fcd5ed 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java @@ -17,9 +17,10 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; -import java.util.HashMap; +import java.util.EnumMap; import java.util.Map; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.JGitText; /** @@ -35,7 +36,8 @@ import org.eclipse.jgit.internal.JGitText; */ public class GitTimeParser { - private static final Map<ParseableSimpleDateFormat, DateTimeFormatter> formatCache = new HashMap<>(); + private static final Map<ParseableSimpleDateFormat, DateTimeFormatter> formatCache = new EnumMap<>( + ParseableSimpleDateFormat.class); // An enum of all those formats which this parser can parse with the help of // a DateTimeFormatter. There are other formats (e.g. the relative formats @@ -59,6 +61,10 @@ public class GitTimeParser { } } + private GitTimeParser() { + // This class is not supposed to be instantiated + } + /** * Parses a string into a {@link java.time.LocalDateTime} using the default * locale. Since this parser also supports relative formats (e.g. @@ -95,16 +101,17 @@ public class GitTimeParser { static LocalDateTime parse(String dateStr, LocalDateTime now) throws ParseException { dateStr = dateStr.trim(); - LocalDateTime ret; - if ("never".equalsIgnoreCase(dateStr)) //$NON-NLS-1$ + if (dateStr.equalsIgnoreCase("never")) { //$NON-NLS-1$ return LocalDateTime.MAX; - ret = parse_relative(dateStr, now); - if (ret != null) + } + LocalDateTime ret = parseRelative(dateStr, now); + if (ret != null) { return ret; + } for (ParseableSimpleDateFormat f : ParseableSimpleDateFormat.values()) { try { - return parse_simple(dateStr, f); + return parseSimple(dateStr, f); } catch (DateTimeParseException e) { // simply proceed with the next parser } @@ -112,8 +119,9 @@ public class GitTimeParser { ParseableSimpleDateFormat[] values = ParseableSimpleDateFormat.values(); StringBuilder allFormats = new StringBuilder("\"") //$NON-NLS-1$ .append(values[0].formatStr); - for (int i = 1; i < values.length; i++) + for (int i = 1; i < values.length; i++) { allFormats.append("\", \"").append(values[i].formatStr); //$NON-NLS-1$ + } allFormats.append("\""); //$NON-NLS-1$ throw new ParseException( MessageFormat.format(JGitText.get().cannotParseDate, dateStr, @@ -122,10 +130,11 @@ public class GitTimeParser { } // tries to parse a string with the formats supported by DateTimeFormatter - private static LocalDateTime parse_simple(String dateStr, + private static LocalDateTime parseSimple(String dateStr, ParseableSimpleDateFormat f) throws DateTimeParseException { DateTimeFormatter dateFormat = formatCache.computeIfAbsent(f, - format -> DateTimeFormatter.ofPattern(f.formatStr) + format -> DateTimeFormatter + .ofPattern(f.formatStr) .withLocale(SystemReader.getInstance().getLocale())); TemporalAccessor parsed = dateFormat.parse(dateStr); return parsed.isSupported(ChronoField.HOUR_OF_DAY) @@ -135,25 +144,27 @@ public class GitTimeParser { // tries to parse a string with a relative time specification @SuppressWarnings("nls") - private static LocalDateTime parse_relative(String dateStr, + @Nullable + private static LocalDateTime parseRelative(String dateStr, LocalDateTime now) { // check for the static words "yesterday" or "now" - if ("now".equals(dateStr)) { + if (dateStr.equals("now")) { return now; } - if ("yesterday".equals(dateStr)) { + if (dateStr.equals("yesterday")) { return now.minusDays(1); } // parse constructs like "3 days ago", "5.week.2.day.ago" - String[] parts = dateStr.split("\\.| "); + String[] parts = dateStr.split("\\.| ", -1); int partsLength = parts.length; // check we have an odd number of parts (at least 3) and that the last // part is "ago" if (partsLength < 3 || (partsLength & 1) == 0 - || !"ago".equals(parts[parts.length - 1])) + || !parts[parts.length - 1].equals("ago")) { return null; + } int number; for (int i = 0; i < parts.length - 2; i += 2) { try { |