diff options
13 files changed, 768 insertions, 192 deletions
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index 66cf739ef1..c6cdfafe84 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -1020,7 +1020,8 @@ public class TestRepository<R extends Repository> implements AutoCloseable { private static void prunePacked(ObjectDirectory odb) throws IOException { for (Pack p : odb.getPacks()) { for (MutableEntry e : p) - FileUtils.delete(odb.fileFor(e.toObjectId())); + FileUtils.delete(odb.fileFor(e.toObjectId()), + FileUtils.SKIP_MISSING); } } diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.c b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.c new file mode 100644 index 0000000000..3661160921 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.c @@ -0,0 +1,43 @@ +#include <stdio.h> +#include <string.h> +#include <ctype.h> + +void getGreeting(char *result, const char *name) { + sprintf(result, "Hello, %s!", name); +} + +void getFarewell(char *result, const char *name) { + sprintf(result, "Goodbye, %s. Have a great day!", name); +} + +void toLower(char *str) { + for (int i = 0; str[i]; i++) { + str[i] = tolower(str[i]); + } +} + +void getPersonalizedGreeting(char *result, const char *name, const char *timeOfDay) { + char timeOfDayLower[50]; + strcpy(timeOfDayLower, timeOfDay); + toLower(timeOfDayLower); + if (strcmp(timeOfDayLower, "morning") == 0) { + sprintf(result, "Good morning, %s", name); + } else if (strcmp(timeOfDayLower, "afternoon") == 0) { + sprintf(result, "Good afternoon, %s", name); + } else if (strcmp(timeOfDayLower, "evening") == 0) { + sprintf(result, "Good evening, %s", name); + } else { + sprintf(result, "Good day, %s", name); + } +} + +int main() { + char result[100]; + getGreeting(result, "foo"); + printf("%s\\n", result); + getFarewell(result, "bar"); + printf("%s\\n", result); + getPersonalizedGreeting(result, "baz", "morning"); + printf("%s\\n", result); + return 0; +} diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.javasource b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.javasource new file mode 100644 index 0000000000..9659685c63 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.javasource @@ -0,0 +1,37 @@ +public class Greeting { + public String getGreeting(String name) { + String msg = "Hello, " + name + "!"; + return msg; + } + + public String getFarewell(String name) { + String msg = "Goodbye, " + name + ". Have a great day!"; + return msg; + } + + public String getPersonalizedGreeting(String name, String timeOfDay) { + String msg; + switch (timeOfDay.toLowerCase()) { + case "morning": + msg = "Good morning, " + name; + break; + case "afternoon": + msg = "Good afternoon, " + name; + break; + case "evening": + msg = "Good evening, " + name; + break; + default: + msg = "Good day, " + name; + break; + } + return msg; + } + + public static void main(String[] args) { + Greeting greeting = new Greeting(); + System.out.println(greeting.getGreeting("foo")); + System.out.println(greeting.getFarewell("bar")); + System.out.println(greeting.getPersonalizedGreeting("baz", "morning")); + } +} diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.py b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.py new file mode 100644 index 0000000000..9eda6cd8fe --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.py @@ -0,0 +1,26 @@ +class Greeting: + def get_greeting(self, name): + greeting_message = f"Hello, {name}!" + return greeting_message + + def get_farewell(self, name): + farewell_message = f"Goodbye, {name}. Have a great day!" + return farewell_message + + def get_personalized_greeting(self, name, time_of_day): + time_of_day = time_of_day.lower() + if time_of_day == "morning": + personalized_message = f"Good morning, {name}" + elif time_of_day == "afternoon": + personalized_message = f"Good afternoon, {name}" + elif time_of_day == "evening": + personalized_message = f"Good evening, {name}" + else: + personalized_message = f"Good day, {name}" + return personalized_message + +if __name__ == "__main__": + greeting = Greeting() + print(greeting.get_greeting("foo")) + print(greeting.get_farewell("bar")) + print(greeting.get_personalized_greeting("baz", "morning")) diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.rs b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.rs new file mode 100644 index 0000000000..a3aa5cbe7c --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.rs @@ -0,0 +1,27 @@ +struct Greeting; + +impl Greeting { + fn get_greeting(&self, name: &str) -> String { + format!("Hello, {}!", name) + } + + fn get_farewell(&self, name: &str) -> String { + format!("Goodbye, {}. Have a great day!", name) + } + + fn get_personalized_greeting(&self, name: &str, time_of_day: &str) -> String { + match time_of_day.to_lowercase().as_str() { + "morning" => format!("Good morning, {}", name), + "afternoon" => format!("Good afternoon, {}", name), + "evening" => format!("Good evening, {}", name), + _ => format!("Good day, {}", name), + } + } +} + +fn main() { + let greeting = Greeting; + println!("{}", greeting.get_greeting("foo")); + println!("{}", greeting.get_farewell("bar")); + println!("{}", greeting.get_personalized_greeting("baz", "morning")); +} diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/sample.dtsi b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/sample.dtsi new file mode 100644 index 0000000000..6aa4ecdd4c --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/sample.dtsi @@ -0,0 +1,25 @@ +/dts-v1/; + +/ { + model = "Example Board"; + compatible = "example,board"; + cpus { + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a9"; + reg = <0>; + }; + }; + + memory { + device_type = "memory"; + reg = <0x80000000 0x20000000>; + }; + + uart0: uart@101f1000 { + compatible = "ns16550a"; + reg = <0x101f1000 0x1000>; + interrupts = <5>; + clock-frequency = <24000000>; + }; +}; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterBuiltInDriverTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterBuiltInDriverTest.java new file mode 100644 index 0000000000..1352871983 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterBuiltInDriverTest.java @@ -0,0 +1,173 @@ +/* + * 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.diff; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.stream.Collectors; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.junit.Test; + +public class DiffFormatterBuiltInDriverTest extends RepositoryTestCase { + @Test + public void testCppDriver() throws Exception { + String fileName = "greeting.c"; + String body = Files.readString( + Path.of(JGitTestUtil.getTestResourceFile(fileName) + .getAbsolutePath())); + RevCommit c1; + RevCommit c2; + try (Git git = new Git(db)) { + createCommit(git, ".gitattributes", "*.c diff=cpp"); + c1 = createCommit(git, fileName, body); + c2 = createCommit(git, fileName, + body.replace("Good day", "Greetings") + .replace("baz", "qux")); + } + try (ByteArrayOutputStream os = new ByteArrayOutputStream(); + DiffFormatter diffFormatter = new DiffFormatter(os)) { + String actual = getHunkHeaders(c1, c2, os, diffFormatter); + String expected = + "@@ -27,7 +27,7 @@ void getPersonalizedGreeting(char *result, const char *name, const char *timeOfD\n" + + "@@ -37,7 +37,7 @@ int main() {"; + assertEquals(expected, actual); + } + } + + @Test + public void testDtsDriver() throws Exception { + String fileName = "sample.dtsi"; + String body = Files.readString( + Path.of(JGitTestUtil.getTestResourceFile(fileName) + .getAbsolutePath())); + RevCommit c1; + RevCommit c2; + try (Git git = new Git(db)) { + createCommit(git, ".gitattributes", "*.dtsi diff=dts"); + c1 = createCommit(git, fileName, body); + c2 = createCommit(git, fileName, + body.replace("clock-frequency = <24000000>", + "clock-frequency = <48000000>")); + } + try (ByteArrayOutputStream os = new ByteArrayOutputStream(); + DiffFormatter diffFormatter = new DiffFormatter(os)) { + String actual = getHunkHeaders(c1, c2, os, diffFormatter); + String expected = "@@ -20,6 +20,6 @@ uart0: uart@101f1000 {"; + assertEquals(expected, actual); + } + } + + @Test + public void testJavaDriver() throws Exception { + String resourceName = "greeting.javasource"; + String body = Files.readString( + Path.of(JGitTestUtil.getTestResourceFile(resourceName) + .getAbsolutePath())); + RevCommit c1; + RevCommit c2; + try (Git git = new Git(db)) { + createCommit(git, ".gitattributes", "*.java diff=java"); + String fileName = "Greeting.java"; + c1 = createCommit(git, fileName, body); + c2 = createCommit(git, fileName, + body.replace("Good day", "Greetings") + .replace("baz", "qux")); + } + try (ByteArrayOutputStream os = new ByteArrayOutputStream(); + DiffFormatter diffFormatter = new DiffFormatter(os)) { + String actual = getHunkHeaders(c1, c2, os, diffFormatter); + String expected = + "@@ -22,7 +22,7 @@ public String getPersonalizedGreeting(String name, String timeOfDay) {\n" + + "@@ -32,6 +32,6 @@ public static void main(String[] args) {"; + assertEquals(expected, actual); + } + } + + @Test + public void testPythonDriver() throws Exception { + String fileName = "greeting.py"; + String body = Files.readString( + Path.of(JGitTestUtil.getTestResourceFile(fileName) + .getAbsolutePath())); + RevCommit c1; + RevCommit c2; + try (Git git = new Git(db)) { + createCommit(git, ".gitattributes", "*.py diff=python"); + c1 = createCommit(git, fileName, body); + c2 = createCommit(git, fileName, + body.replace("Good day", "Greetings")); + } + try (ByteArrayOutputStream os = new ByteArrayOutputStream(); + DiffFormatter diffFormatter = new DiffFormatter(os)) { + String actual = getHunkHeaders(c1, c2, os, diffFormatter); + String expected = "@@ -16,7 +16,7 @@ def get_personalized_greeting(self, name, time_of_day):"; + assertEquals(expected, actual); + } + } + + @Test + public void testRustDriver() throws Exception { + String fileName = "greeting.rs"; + String body = Files.readString( + Path.of(JGitTestUtil.getTestResourceFile(fileName) + .getAbsolutePath())); + RevCommit c1; + RevCommit c2; + try (Git git = new Git(db)) { + createCommit(git, ".gitattributes", "*.rs diff=rust"); + c1 = createCommit(git, fileName, body); + c2 = createCommit(git, fileName, + body.replace("Good day", "Greetings") + .replace("baz", "qux")); + } + try (ByteArrayOutputStream os = new ByteArrayOutputStream(); + DiffFormatter diffFormatter = new DiffFormatter(os)) { + String actual = getHunkHeaders(c1, c2, os, diffFormatter); + String expected = + "@@ -14,7 +14,7 @@ fn get_personalized_greeting(&self, name: &str, time_of_day: &str) -> String {\n" + + "@@ -23,5 +23,5 @@ fn main() {"; + assertEquals(expected, actual); + } + } + + private String getHunkHeaders(RevCommit c1, RevCommit c2, + ByteArrayOutputStream os, DiffFormatter diffFormatter) + throws IOException { + diffFormatter.setRepository(db); + diffFormatter.format(new CanonicalTreeParser(null, db.newObjectReader(), + c1.getTree()), + new CanonicalTreeParser(null, db.newObjectReader(), + c2.getTree())); + diffFormatter.flush(); + return Arrays.stream(os.toString(StandardCharsets.UTF_8).split("\n")) + .filter(line -> line.startsWith("@@")) + .collect(Collectors.joining("\n")); + } + + private RevCommit createCommit(Git git, String fileName, String body) + throws IOException, GitAPIException { + writeTrashFile(fileName, body); + git.add().addFilepattern(".").call(); + return git.commit().setMessage("message").call(); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcNumberOfPackFilesAfterBitmapStatisticsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcNumberOfPackFilesAfterBitmapStatisticsTest.java deleted file mode 100644 index e5a391f2e3..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcNumberOfPackFilesAfterBitmapStatisticsTest.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (c) 2024 Jacek Centkowski <geminica.programs@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.internal.storage.file; - -import static org.junit.Assert.assertEquals; - -import java.io.BufferedOutputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.file.Files; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.StreamSupport; - -import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; -import org.eclipse.jgit.internal.storage.pack.PackExt; -import org.eclipse.jgit.internal.storage.pack.PackWriter; -import org.eclipse.jgit.junit.TestRepository; -import org.eclipse.jgit.lib.NullProgressMonitor; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PersonIdent; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.util.FileUtils; -import org.junit.Test; - -public class GcNumberOfPackFilesAfterBitmapStatisticsTest extends GcTestCase { - @Test - public void testShouldReportZeroObjectsForInitializedRepo() - throws IOException { - assertEquals(0L, gc.getStatistics().numberOfPackFilesAfterBitmap); - } - - @Test - public void testShouldReportAllPackFilesWhenNoGcWasPerformed() - throws Exception { - packAndPrune(); - long result = gc.getStatistics().numberOfPackFilesAfterBitmap; - - assertEquals(repo.getObjectDatabase().getPacks().size(), result); - } - - @Test - public void testShouldReportNoObjectsDirectlyAfterGc() throws Exception { - // given - addCommit(null); - gc.gc().get(); - assertEquals(1L, repositoryBitmapFiles()); - assertEquals(0L, gc.getStatistics().numberOfPackFilesAfterBitmap); - } - - @Test - public void testShouldReportNewObjectsAfterGcWhenRepositoryProgresses() - throws Exception { - // commit & gc - RevCommit parent = addCommit(null); - gc.gc().get(); - assertEquals(1L, repositoryBitmapFiles()); - - // progress & pack - addCommit(parent); - packAndPrune(); - - assertEquals(1L, gc.getStatistics().numberOfPackFilesAfterBitmap); - } - - @Test - public void testShouldReportNewObjectsFromTheLatestBitmapWhenRepositoryProgresses() - throws Exception { - // commit & gc - RevCommit parent = addCommit(null); - gc.gc().get(); - assertEquals(1L, repositoryBitmapFiles()); - - // progress & gc - parent = addCommit(parent); - gc.gc().get(); - assertEquals(2L, repositoryBitmapFiles()); - - // progress & pack - addCommit(parent); - packAndPrune(); - - assertEquals(1L, gc.getStatistics().numberOfPackFilesAfterBitmap); - } - - private void packAndPrune() throws Exception { - try (SkipNonExistingFilesTestRepository testRepo = new SkipNonExistingFilesTestRepository( - repo)) { - testRepo.packAndPrune(); - } - } - - private RevCommit addCommit(RevCommit parent) throws Exception { - return tr.branch("master").commit() - .author(new PersonIdent("repo-metrics", "repo@metrics.com")) - .parent(parent).create(); - } - - private long repositoryBitmapFiles() throws IOException { - return StreamSupport - .stream(Files - .newDirectoryStream(repo.getObjectDatabase() - .getPackDirectory().toPath(), "pack-*.bitmap") - .spliterator(), false) - .count(); - } - - /** - * The TestRepository has a {@link TestRepository#packAndPrune()} function - * but it fails in the last step after GC was performed as it doesn't - * SKIP_MISSING files. In order to circumvent it was copied and improved - * here. - */ - private static class SkipNonExistingFilesTestRepository - extends TestRepository<FileRepository> { - private final FileRepository repo; - - private SkipNonExistingFilesTestRepository(FileRepository db) throws IOException { - super(db); - repo = db; - } - - @Override - public void packAndPrune() throws Exception { - ObjectDirectory odb = repo.getObjectDatabase(); - NullProgressMonitor m = NullProgressMonitor.INSTANCE; - - final PackFile pack, idx; - try (PackWriter pw = new PackWriter(repo)) { - Set<ObjectId> all = new HashSet<>(); - for (Ref r : repo.getRefDatabase().getRefs()) - all.add(r.getObjectId()); - pw.preparePack(m, all, PackWriter.NONE); - - pack = new PackFile(odb.getPackDirectory(), pw.computeName(), - PackExt.PACK); - try (OutputStream out = new BufferedOutputStream( - new FileOutputStream(pack))) { - pw.writePack(m, m, out); - } - pack.setReadOnly(); - - idx = pack.create(PackExt.INDEX); - try (OutputStream out = new BufferedOutputStream( - new FileOutputStream(idx))) { - pw.writeIndex(out); - } - idx.setReadOnly(); - } - - odb.openPack(pack); - updateServerInfo(); - - // alternative packAndPrune implementation that skips missing files - // after GC. - for (Pack p : odb.getPacks()) { - for (MutableEntry e : p) - FileUtils.delete(odb.fileFor(e.toObjectId()), - FileUtils.SKIP_MISSING); - } - } - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcSinceBitmapStatisticsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcSinceBitmapStatisticsTest.java new file mode 100644 index 0000000000..3cd766c4e9 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcSinceBitmapStatisticsTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2024 Jacek Centkowski <geminica.programs@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.internal.storage.file; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.Collection; +import java.util.stream.StreamSupport; + +import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Test; + +public class GcSinceBitmapStatisticsTest extends GcTestCase { + @Test + public void testShouldReportZeroPacksAndObjectsForInitializedRepo() + throws IOException { + RepoStatistics s = gc.getStatistics(); + assertEquals(0L, s.numberOfPackFilesSinceBitmap); + assertEquals(0L, s.numberOfObjectsSinceBitmap); + } + + @Test + public void testShouldReportAllPackFilesWhenNoGcWasPerformed() + throws Exception { + tr.packAndPrune(); + long result = gc.getStatistics().numberOfPackFilesSinceBitmap; + + assertEquals(repo.getObjectDatabase().getPacks().size(), result); + } + + @Test + public void testShouldReportAllObjectsWhenNoGcWasPerformed() + throws Exception { + tr.packAndPrune(); + + assertEquals( + getNumberOfObjectsInPacks(repo.getObjectDatabase().getPacks()), + gc.getStatistics().numberOfObjectsSinceBitmap); + } + + @Test + public void testShouldReportNoPacksDirectlyAfterGc() throws Exception { + // given + addCommit(null); + gc.gc().get(); + assertEquals(1L, repositoryBitmapFiles()); + assertEquals(0L, gc.getStatistics().numberOfPackFilesSinceBitmap); + } + + @Test + public void testShouldReportNoObjectsDirectlyAfterGc() throws Exception { + // given + addCommit(null); + assertEquals(2L, gc.getStatistics().numberOfObjectsSinceBitmap); + + gc.gc().get(); + assertEquals(0L, gc.getStatistics().numberOfObjectsSinceBitmap); + } + + @Test + public void testShouldReportNewPacksSinceGcWhenRepositoryProgresses() + throws Exception { + // commit & gc + RevCommit parent = addCommit(null); + gc.gc().get(); + assertEquals(1L, repositoryBitmapFiles()); + + // progress & pack + addCommit(parent); + tr.packAndPrune(); + + assertEquals(1L, gc.getStatistics().numberOfPackFilesSinceBitmap); + } + + @Test + public void testShouldReportNewObjectsSinceGcWhenRepositoryProgresses() + throws Exception { + // commit & gc + RevCommit parent = addCommit(null); + gc.gc().get(); + assertEquals(0L, gc.getStatistics().numberOfObjectsSinceBitmap); + + // progress & pack + addCommit(parent); + assertEquals(1L, gc.getStatistics().numberOfObjectsSinceBitmap); + + tr.packAndPrune(); + assertEquals(3L, gc.getStatistics().numberOfObjectsSinceBitmap); + } + + @Test + public void testShouldReportNewPacksFromTheLatestBitmapWhenRepositoryProgresses() + throws Exception { + // commit & gc + RevCommit parent = addCommit(null); + gc.gc().get(); + assertEquals(1L, repositoryBitmapFiles()); + + // progress & gc + parent = addCommit(parent); + gc.gc().get(); + assertEquals(2L, repositoryBitmapFiles()); + + // progress & pack + addCommit(parent); + tr.packAndPrune(); + + assertEquals(1L, gc.getStatistics().numberOfPackFilesSinceBitmap); + } + + @Test + public void testShouldReportNewObjectsFromTheLatestBitmapWhenRepositoryProgresses() + throws Exception { + // commit & gc + RevCommit parent = addCommit(null); + gc.gc().get(); + + // progress & gc + parent = addCommit(parent); + gc.gc().get(); + assertEquals(0L, gc.getStatistics().numberOfObjectsSinceBitmap); + + // progress & pack + addCommit(parent); + assertEquals(1L, gc.getStatistics().numberOfObjectsSinceBitmap); + + tr.packAndPrune(); + assertEquals(4L, gc.getStatistics().numberOfObjectsSinceBitmap); + } + + private RevCommit addCommit(RevCommit parent) throws Exception { + return tr.branch("master").commit() + .author(new PersonIdent("repo-metrics", "repo@metrics.com")) + .parent(parent).create(); + } + + private long repositoryBitmapFiles() throws IOException { + return StreamSupport + .stream(Files + .newDirectoryStream(repo.getObjectDatabase() + .getPackDirectory().toPath(), "pack-*.bitmap") + .spliterator(), false) + .count(); + } + + private long getNumberOfObjectsInPacks(Collection<Pack> packs) { + return packs.stream().mapToLong(pack -> { + try { + return pack.getObjectCount(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).sum(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffDriver.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffDriver.java new file mode 100644 index 0000000000..b74444400e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffDriver.java @@ -0,0 +1,116 @@ +/* + * 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.diff; + +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Built-in drivers for various languages, sorted by name. These drivers will be + * used to determine function names for a hunk. + * <p> + * When writing or updating patterns, assume the contents are syntactically + * correct. Patterns can be simple and need not cover all syntactical corner + * cases, as long as they are sufficiently permissive. + * + * @since 6.10.1 + */ +@SuppressWarnings({"ImmutableEnumChecker", "nls"}) +public enum DiffDriver { + /** + * Built-in diff driver for <a href= + * "https://learn.microsoft.com/en-us/cpp/cpp/cpp-language-reference">c++</a> + */ + cpp(List.of( + /* Jump targets or access declarations */ + "^[ \\t]*[A-Za-z_][A-Za-z_0-9]*:\\s*($|/[/*])"), List.of( + /* functions/methods, variables, and compounds at top level */ + "^((::\\s*)?[A-Za-z_].*)$")), + /** + * Built-in diff driver for <a href= + * "https://devicetree-specification.readthedocs.io/en/stable/source-language.html">device + * tree files</a> + */ + dts(List.of(";", "="), List.of( + /* lines beginning with a word optionally preceded by '&' or the root */ + "^[ \\t]*((/[ \\t]*\\{|&?[a-zA-Z_]).*)")), + /** + * Built-in diff driver for <a href= + * "https://docs.oracle.com/javase/specs/jls/se21/html/index.html">java</a> + */ + java(List.of( + "^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)"), + List.of( + /* Class, enum, interface, and record declarations */ + "^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t]+.*)$", + /* Method definitions; note that constructor signatures are not */ + /* matched because they are indistinguishable from method calls. */ + "^[ \\t]*(([A-Za-z_<>&\\]\\[][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_]" + + "[A-Za-z_0-9]*[ \\t]*\\([^;]*)$")), + /** + * Built-in diff driver for + * <a href="https://docs.python.org/3/reference/index.html">python</a> + */ + python(List.of("^[ \\t]*((class|(async[ \\t]+)?def)[ \\t].*)$")), + /** + * Built-in diff driver for + * <a href="https://doc.rust-lang.org/reference/introduction.html">rust</a> + */ + rust(List.of("^[\\t ]*((pub(\\([^\\)]+\\))?[\\t ]+)?" + + "((async|const|unsafe|extern([\\t ]+\"[^\"]+\"))[\\t ]+)?" + + "(struct|enum|union|mod|trait|fn|impl|macro_rules!)[< \\t]+[^;]*)$")); + + private final List<Pattern> negatePatterns; + + private final List<Pattern> matchPatterns; + + DiffDriver(List<String> negate, List<String> match, int flags) { + if (negate != null) { + this.negatePatterns = negate.stream() + .map(r -> Pattern.compile(r, flags)) + .collect(Collectors.toList()); + } else { + this.negatePatterns = null; + } + this.matchPatterns = match.stream().map(r -> Pattern.compile(r, flags)) + .collect(Collectors.toList()); + } + + DiffDriver(List<String> match) { + this(null, match, 0); + } + + DiffDriver(List<String> negate, List<String> match) { + this(negate, match, 0); + } + + /** + * Returns the list of patterns used to exclude certain lines from being + * considered as function names. + * + * @return the list of negate patterns + */ + public List<Pattern> getNegatePatterns() { + return negatePatterns; + } + + /** + * Returns the list of patterns used to match lines for potential function + * names. + * + * @return the list of match patterns + */ + public List<Pattern> getMatchPatterns() { + return matchPatterns; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java index 2f472b5c0a..fa446e18cd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java @@ -30,7 +30,9 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.regex.Pattern; import org.eclipse.jgit.api.errors.CanceledException; +import org.eclipse.jgit.attributes.Attribute; import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm; import org.eclipse.jgit.diff.DiffEntry.ChangeType; import org.eclipse.jgit.dircache.DirCacheIterator; @@ -703,7 +705,7 @@ public class DiffFormatter implements AutoCloseable { */ public void format(DiffEntry ent) throws IOException { FormatResult res = createFormatResult(ent); - format(res.header, res.a, res.b); + format(res.header, res.a, res.b, getDiffDriver(ent)); } private static byte[] writeGitLinkText(AbbreviatedObjectId id) { @@ -749,11 +751,14 @@ public class DiffFormatter implements AutoCloseable { * text source for the post-image version of the content. This * must match the content of * {@link org.eclipse.jgit.patch.FileHeader#getNewId()}. + * @param diffDriver + * the diff driver used to obtain function names in hunk headers * @throws java.io.IOException - * writing to the supplied stream failed. + * writing to the supplied stream failed. + * @since 6.10.1 */ - public void format(FileHeader head, RawText a, RawText b) - throws IOException { + public void format(FileHeader head, RawText a, RawText b, + DiffDriver diffDriver) throws IOException { // Reuse the existing FileHeader as-is by blindly copying its // header lines, but avoiding its hunks. Instead we recreate // the hunks from the text instances we have been supplied. @@ -763,8 +768,49 @@ public class DiffFormatter implements AutoCloseable { if (!head.getHunks().isEmpty()) end = head.getHunks().get(0).getStartOffset(); out.write(head.getBuffer(), start, end - start); - if (head.getPatchType() == PatchType.UNIFIED) - format(head.toEditList(), a, b); + if (head.getPatchType() == PatchType.UNIFIED) { + format(head.toEditList(), a, b, diffDriver); + } + } + + /** + * Format a patch script, reusing a previously parsed FileHeader. + * <p> + * This formatter is primarily useful for editing an existing patch script + * to increase or reduce the number of lines of context within the script. + * All header lines are reused as-is from the supplied FileHeader. + * + * @param head + * existing file header containing the header lines to copy. + * @param a + * text source for the pre-image version of the content. This must match + * the content of {@link org.eclipse.jgit.patch.FileHeader#getOldId()}. + * @param b + * text source for the post-image version of the content. This must match + * the content of {@link org.eclipse.jgit.patch.FileHeader#getNewId()}. + * @throws java.io.IOException + * writing to the supplied stream failed. + */ + public void format(FileHeader head, RawText a, RawText b) + throws IOException { + format(head, a, b, null); + } + + /** + * Formats a list of edits in unified diff format + * + * @param edits + * some differences which have been calculated between A and B + * @param a + * the text A which was compared + * @param b + * the text B which was compared + * @throws java.io.IOException + * if an IO error occurred + */ + public void format(EditList edits, RawText a, RawText b) + throws IOException { + format(edits, a, b, null); } /** @@ -776,11 +822,14 @@ public class DiffFormatter implements AutoCloseable { * the text A which was compared * @param b * the text B which was compared + * @param diffDriver + * the diff driver used to obtain function names in hunk headers * @throws java.io.IOException * if an IO error occurred + * @since 6.10.1 */ - public void format(EditList edits, RawText a, RawText b) - throws IOException { + public void format(EditList edits, RawText a, RawText b, + DiffDriver diffDriver) throws IOException { for (int curIdx = 0; curIdx < edits.size();) { Edit curEdit = edits.get(curIdx); final int endIdx = findCombinedEnd(edits, curIdx); @@ -791,7 +840,8 @@ public class DiffFormatter implements AutoCloseable { final int aEnd = (int) Math.min(a.size(), (long) endEdit.getEndA() + context); final int bEnd = (int) Math.min(b.size(), (long) endEdit.getEndB() + context); - writeHunkHeader(aCur, aEnd, bCur, bEnd); + writeHunkHeader(aCur, aEnd, bCur, bEnd, + getFuncName(a, aCur - 1, diffDriver)); while (aCur < aEnd || bCur < bEnd) { if (aCur < curEdit.getBeginA() || endIdx + 1 < curIdx) { @@ -881,8 +931,30 @@ public class DiffFormatter implements AutoCloseable { * @throws java.io.IOException * if an IO error occurred */ - protected void writeHunkHeader(int aStartLine, int aEndLine, - int bStartLine, int bEndLine) throws IOException { + protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, + int bEndLine) throws IOException { + writeHunkHeader(aStartLine, aEndLine, bStartLine, bEndLine, null); + } + + /** + * Output a hunk header + * + * @param aStartLine + * within first source + * @param aEndLine + * within first source + * @param bStartLine + * within second source + * @param bEndLine + * within second source + * @param funcName + * function name of this hunk + * @throws java.io.IOException + * if an IO error occurred + * @since 6.10.1 + */ + protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, + int bEndLine, String funcName) throws IOException { out.write('@'); out.write('@'); writeRange('-', aStartLine + 1, aEndLine - aStartLine); @@ -890,6 +962,10 @@ public class DiffFormatter implements AutoCloseable { out.write(' '); out.write('@'); out.write('@'); + if (funcName != null) { + out.write(' '); + out.write(funcName.getBytes()); + } out.write('\n'); } @@ -1247,4 +1323,46 @@ public class DiffFormatter implements AutoCloseable { private static boolean end(Edit edit, int a, int b) { return edit.getEndA() <= a && edit.getEndB() <= b; } + + private String getFuncName(RawText text, int startAt, + DiffDriver diffDriver) { + if (diffDriver != null) { + while (startAt > 0) { + String line = text.getString(startAt); + startAt--; + if (matchesAny(diffDriver.getNegatePatterns(), line)) { + continue; + } + if (matchesAny(diffDriver.getMatchPatterns(), line)) { + String funcName = line.replaceAll("^[ \\t]+", ""); + return funcName.substring(0, + Math.min(funcName.length(), 80)).trim(); + } + } + } + return null; + } + + private boolean matchesAny(List<Pattern> patterns, String text) { + if (patterns != null) { + for (Pattern p : patterns) { + if (p.matcher(text).find()) { + return true; + } + } + } + return false; + } + + private DiffDriver getDiffDriver(DiffEntry entry) { + Attribute diffAttr = entry.getDiffAttribute(); + if (diffAttr != null) { + try { + return DiffDriver.valueOf(diffAttr.getValue()); + } catch (IllegalArgumentException e) { + return null; + } + } + return null; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java index 4343642f9a..b401bbe73d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java @@ -44,8 +44,8 @@ public class PatchIdDiffFormatter extends DiffFormatter { } @Override - protected void writeHunkHeader(int aStartLine, int aEndLine, - int bStartLine, int bEndLine) throws IOException { + protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, + int bEndLine, String funcName) throws IOException { // The hunk header is not taken into account for patch id calculation } 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 8fde3903d0..f87ca90860 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 @@ -1512,7 +1512,13 @@ public class GC { * The number of pack files that were created after the last bitmap * generation. */ - public long numberOfPackFilesAfterBitmap; + public long numberOfPackFilesSinceBitmap; + + /** + * The number of objects stored in pack files and as loose object + * created after the last bitmap generation. + */ + public long numberOfObjectsSinceBitmap; /** * The number of objects stored as loose objects. @@ -1549,8 +1555,10 @@ public class GC { final StringBuilder b = new StringBuilder(); b.append("numberOfPackedObjects=").append(numberOfPackedObjects); //$NON-NLS-1$ b.append(", numberOfPackFiles=").append(numberOfPackFiles); //$NON-NLS-1$ - b.append(", numberOfPackFilesAfterBitmap=") //$NON-NLS-1$ - .append(numberOfPackFilesAfterBitmap); + b.append(", numberOfPackFilesSinceBitmap=") //$NON-NLS-1$ + .append(numberOfPackFilesSinceBitmap); + b.append(", numberOfObjectsSinceBitmap=") //$NON-NLS-1$ + .append(numberOfObjectsSinceBitmap); b.append(", numberOfLooseObjects=").append(numberOfLooseObjects); //$NON-NLS-1$ b.append(", numberOfLooseRefs=").append(numberOfLooseRefs); //$NON-NLS-1$ b.append(", numberOfPackedRefs=").append(numberOfPackedRefs); //$NON-NLS-1$ @@ -1571,14 +1579,19 @@ public class GC { public RepoStatistics getStatistics() throws IOException { RepoStatistics ret = new RepoStatistics(); Collection<Pack> packs = repo.getObjectDatabase().getPacks(); + long latestBitmapTime = Long.MIN_VALUE; for (Pack p : packs) { - ret.numberOfPackedObjects += p.getIndex().getObjectCount(); + long packedObjects = p.getIndex().getObjectCount(); + ret.numberOfPackedObjects += packedObjects; ret.numberOfPackFiles++; ret.sizeOfPackedObjects += p.getPackFile().length(); if (p.getBitmapIndex() != null) { ret.numberOfBitmaps += p.getBitmapIndex().getBitmapCount(); + latestBitmapTime = p.getFileSnapshot().lastModifiedInstant() + .toEpochMilli(); } else { - ret.numberOfPackFilesAfterBitmap++; + ret.numberOfPackFilesSinceBitmap++; + ret.numberOfObjectsSinceBitmap += packedObjects; } } File objDir = repo.getObjectsDirectory(); @@ -1595,6 +1608,9 @@ public class GC { continue; ret.numberOfLooseObjects++; ret.sizeOfLooseObjects += f.length(); + if (f.lastModified() > latestBitmapTime) { + ret.numberOfObjectsSinceBitmap ++; + } } } } |