This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-codec.git


The following commit(s) were added to refs/heads/master by this push:
     new 9e8cb6ac Small fixes to `GitIdentifiers` (#429)
9e8cb6ac is described below

commit 9e8cb6acd1cd7f6aa3362be8e088ebc5c130faff
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Sun Apr 12 19:00:09 2026 +0200

    Small fixes to `GitIdentifiers` (#429)
    
    * Remove `FileMode.getMode()`
    
    The octal value associated to `FileMode` is not very useful to users, since 
it is only used internally, when generating the tree id. Therefore we can 
probably remove the getter for now.
    
    * Add filesystem test
    
    * Sort methods
    
    * Fix formatting
    
    * Enable test on Windows
    
    The test will check anyway if symbolic links are supported.
    
    * Remove unused imports
---
 .../commons/codec/digest/GitIdentifiers.java       |  15 --
 .../commons/codec/digest/GitIdentifiersTest.java   | 176 +++++++++++++++++----
 2 files changed, 142 insertions(+), 49 deletions(-)

diff --git a/src/main/java/org/apache/commons/codec/digest/GitIdentifiers.java 
b/src/main/java/org/apache/commons/codec/digest/GitIdentifiers.java
index fb9b7b51..ab9c92fc 100644
--- a/src/main/java/org/apache/commons/codec/digest/GitIdentifiers.java
+++ b/src/main/java/org/apache/commons/codec/digest/GitIdentifiers.java
@@ -185,29 +185,14 @@ public class GitIdentifiers {
             return REGULAR;
         }
 
-        /**
-         * The octal mode as used by Git.
-         */
-        private final String mode;
-
         /**
          * Serialized {@code mode}: since this is mutable, it must remain 
private.
          */
         private final byte[] modeBytes;
 
         FileMode(final String mode) {
-            this.mode = mode;
             this.modeBytes = mode.getBytes(StandardCharsets.US_ASCII);
         }
-
-        /**
-         * Gets the octal mode as used by Git.
-         *
-         * @return The octal mode.
-         */
-        public String getMode() {
-            return mode;
-        }
     }
 
     /**
diff --git 
a/src/test/java/org/apache/commons/codec/digest/GitIdentifiersTest.java 
b/src/test/java/org/apache/commons/codec/digest/GitIdentifiersTest.java
index 8c8b7c45..e311f89f 100644
--- a/src/test/java/org/apache/commons/codec/digest/GitIdentifiersTest.java
+++ b/src/test/java/org/apache/commons/codec/digest/GitIdentifiersTest.java
@@ -28,12 +28,14 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermissions;
 import java.security.MessageDigest;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Stream;
 
+import org.apache.commons.codec.DecoderException;
 import org.apache.commons.codec.binary.Hex;
 import org.apache.commons.codec.digest.GitIdentifiers.DirectoryEntry;
 import org.junit.jupiter.api.Assumptions;
@@ -49,31 +51,109 @@ import org.junit.jupiter.params.provider.ValueSource;
  */
 class GitIdentifiersTest {
 
-
     private static final byte[] ZERO_ID = new byte[20];
 
+    // Virtual tree:
+    //
+    // link      -> src           (symlink)
+    // link.txt  -> src/hello.txt (symlink)
+    // src/
+    //   hello.txt                (regular file)
+    //   run.sh                   (executable file)
+
+    /** Content of {@code src/hello.txt}. */
+    private static final byte[] HELLO_CONTENT = 
"hello\n".getBytes(StandardCharsets.UTF_8);
+    /** SHA-1 blob id of {@link #HELLO_CONTENT}: {@code printf 'hello\n' | git 
hash-object --stdin} */
+    private static final byte[] HELLO_BLOB_ID_SHA1 = 
hex("ce013625030ba8dba906f756967f9e9ca394464a");
+    /** SHA-256 blob id of {@link #HELLO_CONTENT}. */
+    private static final byte[] HELLO_BLOB_ID_SHA256 = 
hex("2cf8d83d9ee29543b34a87727421fdecb7e3f3a183d337639025de576db9ebb4");
+
+    /** Content of {@code src/run.sh}. */
+    private static final byte[] RUN_CONTENT = 
"#!/bin/sh\n".getBytes(StandardCharsets.UTF_8);
+    /** SHA-1 blob id of {@link #RUN_CONTENT}: {@code printf '#!/bin/sh\n' | 
git hash-object --stdin} */
+    private static final byte[] RUN_BLOB_ID_SHA1 = 
hex("1a2485251c33a70432394c93fb89330ef214bfc9");
+    /** SHA-256 blob id of {@link #RUN_CONTENT}. */
+    private static final byte[] RUN_BLOB_ID_SHA256 = 
hex("1249034e3cf9007362d695b09b1fbdb4c578903bf10b665749b94743f8177ce1");
+
+    /** Target of symlink {@code link}. */
+    private static final String LINK_CONTENT = "src";
+    /** SHA-1 blob id of the symlink target {@link #LINK_CONTENT}: {@code 
printf 'src' | git hash-object --stdin} */
+    private static final byte[] LINK_BLOB_ID_SHA1 = 
hex("e8310385c56dc4bbe379f43400f3181f6a59f260");
+    /** SHA-256 blob id of the symlink target {@link #LINK_CONTENT}. */
+    private static final byte[] LINK_BLOB_ID_SHA256 = 
hex("e1bdca538422554ea204da85e0cec156b12b6808473083610ff95ea390843ab6");
+
+    /** Target of symlink {@code link.txt}. */
+    private static final String LINK_TXT_CONTENT = "src/hello.txt";
+    /** SHA-1 blob id of the symlink target {@link #LINK_TXT_CONTENT}: {@code 
printf 'src/hello.txt' | git hash-object --stdin} */
+    private static final byte[] LINK_TXT_BLOB_ID_SHA1 = 
hex("132a953033e00dcff94f5cccb261f52cd1d71173");
+    /** SHA-256 blob id of the symlink target {@link #LINK_TXT_CONTENT}. */
+    private static final byte[] LINK_TXT_BLOB_ID_SHA256 = 
hex("2499925193a48a84a546a2f7cd3ce7789d4e073ef1e7276fe682bfbb2b636cef");
+
+    // Tree ids can be recomputed in a git repository with:
+    //   git init /tmp/t && cd /tmp/t
+    // followed by writing the blob objects and calling git mktree.
+
+    /**
+     * SHA-1 tree id of {@code src/} (hello.txt + run.sh):
+     * <pre>
+     * printf '100644 blob 
ce013625030ba8dba906f756967f9e9ca394464a\thello.txt\n
+     *         100755 blob 1a2485251c33a70432394c93fb89330ef214bfc9\trun.sh\n' 
| git mktree
+     * </pre>
+     */
+    private static final byte[] SRC_TREE_ID_SHA1 = 
hex("5575b4a0141a2287ec2836a620e5d6aa8fb203ba");
+    /**
+     * SHA-256 tree id of {@code src/}:
+     * <pre>
+     * printf '100644 blob 
2cf8d83d9ee29543b34a87727421fdecb7e3f3a183d337639025de576db9ebb4\thello.txt\n
+     *         100755 blob 
1249034e3cf9007362d695b09b1fbdb4c578903bf10b665749b94743f8177ce1\trun.sh\n' | 
git mktree
+     * </pre>
+     */
+    private static final byte[] SRC_TREE_ID_SHA256 = 
hex("5b4e74befcb98e3050c511d02353d00565b2172be0a2bc5de833f011ad27f694");
+
+    /**
+     * SHA-1 tree id of the main directory (link + link.txt + src/):
+     * <pre>
+     * printf '120000 blob e8310385c56dc4bbe379f43400f3181f6a59f260\tlink\n
+     *         120000 blob 132a953033e00dcff94f5cccb261f52cd1d71173\tlink.txt\n
+     *         040000 tree 5575b4a0141a2287ec2836a620e5d6aa8fb203ba\tsrc\n' | 
git mktree
+     * </pre>
+     */
+    private static final byte[] MAIN_TREE_ID_SHA1 = 
hex("3217900fd0a6624cd6aa169c2a9f289f7f34432b");
+    /**
+     * SHA-256 tree id of the main directory:
+     * <pre>
+     * printf '120000 blob 
e1bdca538422554ea204da85e0cec156b12b6808473083610ff95ea390843ab6\tlink\n
+     *         120000 blob 
2499925193a48a84a546a2f7cd3ce7789d4e073ef1e7276fe682bfbb2b636cef\tlink.txt\n
+     *         040000 tree 
5b4e74befcb98e3050c511d02353d00565b2172be0a2bc5de833f011ad27f694\tsrc\n' | git 
mktree
+     * </pre>
+     */
+    private static final byte[] MAIN_TREE_ID_SHA256 = 
hex("58e9a59940e4d2ae7e374b63fedf3b7bba8cfdc60308f64abd066db137300bcd");
+
     static Stream<Arguments> blobIdProvider() {
         return Stream.of(Arguments.of("DigestUtilsTest/hello.txt", 
"5f4a83288e67f1be2d6fcdad84165a86c6a970d7"),
                 Arguments.of("DigestUtilsTest/greetings.txt", 
"6cf4f797455661e61d1ee6913fc29344f5897243"),
                 Arguments.of("DigestUtilsTest/subdir/nested.txt", 
"07a392ddb4dbff06a373a7617939f30b2dcfe719"));
     }
 
+    /** Decodes a compile-time hex literal; throws {@link AssertionError} on 
malformed input. */
+    private static byte[] hex(final String hex) {
+        try {
+            return Hex.decodeHex(hex);
+        } catch (final DecoderException e) {
+            throw new AssertionError(e);
+        }
+    }
+
     private static Path resourcePath(final String resourceName) throws 
Exception {
         return 
Paths.get(GitIdentifiersTest.class.getClassLoader().getResource(resourceName).toURI());
     }
 
-    static Stream<Arguments> testTreeIdBuilder() {
+    static Stream<Arguments> virtualTreeProvider() {
         return Stream.of(
-                Arguments.of(MessageDigestAlgorithms.SHA_1,
-                        "ce013625030ba8dba906f756967f9e9ca394464a",  // blob 
id of "hello\n"
-                        "8bbe8a53790056316b23b7c270f10ab6bf6bb1b4",  // blob 
id of "subdir"
-                        "1a2485251c33a70432394c93fb89330ef214bfc9",  // blob 
id of "#!/bin/sh\n"
-                        "4b825dc642cb6eb9a060e54bf8d69288fbee4904"), // tree 
id of empty directory
-                Arguments.of(MessageDigestAlgorithms.SHA_256,
-                        
"2cf8d83d9ee29543b34a87727421fdecb7e3f3a183d337639025de576db9ebb4",
-                        
"33910dae80b0db75dbad7fa521dbbf1885a07edfab1228871c41a2e94ccd7edb",
-                        
"1249034e3cf9007362d695b09b1fbdb4c578903bf10b665749b94743f8177ce1",
-                        
"6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321"));
+                Arguments.of(MessageDigestAlgorithms.SHA_1, 
HELLO_BLOB_ID_SHA1, LINK_BLOB_ID_SHA1, LINK_TXT_BLOB_ID_SHA1, RUN_BLOB_ID_SHA1,
+                        SRC_TREE_ID_SHA1, MAIN_TREE_ID_SHA1),
+                Arguments.of(MessageDigestAlgorithms.SHA_256, 
HELLO_BLOB_ID_SHA256, LINK_BLOB_ID_SHA256, LINK_TXT_BLOB_ID_SHA256, 
RUN_BLOB_ID_SHA256,
+                        SRC_TREE_ID_SHA256, MAIN_TREE_ID_SHA256));
     }
 
     @ParameterizedTest
@@ -159,34 +239,27 @@ class GitIdentifiersTest {
     }
 
     @ParameterizedTest
-    @MethodSource
-    void testTreeIdBuilder(final String algorithm, final String helloHex, 
final String linkHex, final String runHex, final String srcHex) throws 
Exception {
-        final byte[] helloContent = "hello\n".getBytes(StandardCharsets.UTF_8);
-        final byte[] runContent = 
"#!/bin/sh\n".getBytes(StandardCharsets.UTF_8);
-        final String linkTarget = "subdir";
+    @MethodSource("virtualTreeProvider")
+    void testTreeIdBuilder(final String algorithm, final byte[] helloId, final 
byte[] linkId, final byte[] linkTxtId, final byte[] runId,
+            final byte[] srcTreeId, final byte[] mainTreeId) throws Exception {
         final MessageDigest md = DigestUtils.getDigest(algorithm);
 
         // Verify individual blob IDs against pre-computed constants.
-        assertArrayEquals(Hex.decodeHex(helloHex), GitIdentifiers.blobId(md, 
helloContent));
-        assertArrayEquals(Hex.decodeHex(linkHex), GitIdentifiers.blobId(md, 
linkTarget.getBytes(StandardCharsets.UTF_8)));
-        assertArrayEquals(Hex.decodeHex(runHex), GitIdentifiers.blobId(md, 
runContent));
+        assertArrayEquals(helloId, GitIdentifiers.blobId(md, HELLO_CONTENT));
+        assertArrayEquals(linkId, GitIdentifiers.blobId(md, 
LINK_CONTENT.getBytes(StandardCharsets.UTF_8)));
+        assertArrayEquals(linkTxtId, GitIdentifiers.blobId(md, 
LINK_TXT_CONTENT.getBytes(StandardCharsets.UTF_8)));
+        assertArrayEquals(runId, GitIdentifiers.blobId(md, RUN_CONTENT));
 
         // Entries are supplied out of order to verify that the builder sorts 
them correctly.
         final GitIdentifiers.TreeIdBuilder builder = 
GitIdentifiers.treeIdBuilder(md);
-        builder.addDirectory("src");
-        builder.addFile(GitIdentifiers.FileMode.EXECUTABLE, "run.sh", 
runContent);
-        builder.addFile(GitIdentifiers.FileMode.REGULAR, "hello.txt", 
helloContent);
-        builder.addSymbolicLink("link.txt", linkTarget);
-
-        // Expected tree body: entries in Git sort order (hello.txt, link.txt, 
run.sh, src/).
-        // Each entry: hex-encoded "<mode> <name>\0" followed by the object id.
-        final byte[] treeBody = 
Hex.decodeHex("3130303634342068656c6c6f2e74787400" + helloHex +   // 100644 
hello.txt\0
-                "313230303030206c696e6b2e74787400" + linkHex +   // 120000 
link.txt\0
-                "3130303735352072756e2e736800" + runHex +   // 100755 run.sh\0
-                "34303030302073726300" + srcHex);   // 40000 src\0
-        md.reset();
-        DigestUtils.updateDigest(md, ("tree " + treeBody.length + 
"\0").getBytes(StandardCharsets.UTF_8));
-        assertArrayEquals(DigestUtils.updateDigest(md, treeBody).digest(), 
builder.build());
+        builder.addSymbolicLink("link.txt", LINK_TXT_CONTENT);
+        builder.addFile(GitIdentifiers.FileMode.REGULAR, "src/hello.txt", 
HELLO_CONTENT);
+        builder.addSymbolicLink("link", LINK_CONTENT);
+        builder.addFile(GitIdentifiers.FileMode.EXECUTABLE, "src/run.sh", 
RUN_CONTENT);
+
+        // Check trees
+        assertArrayEquals(mainTreeId, builder.build());
+        assertArrayEquals(srcTreeId, builder.addDirectory("src").build());
     }
 
     @Test
@@ -266,4 +339,39 @@ class GitIdentifiersTest {
                 GitIdentifiers.treeId(DigestUtils.getSha1Digest(), 
resourcePath("DigestUtilsTest")));
     }
 
+    @ParameterizedTest
+    @MethodSource("virtualTreeProvider")
+    void testTreeIdPathUnix(final String algorithm, final byte[] helloId, 
final byte[] linkId, final byte[] linkTxtId,
+            final byte[] runId, final byte[] srcTreeId, final byte[] 
mainTreeId, final @TempDir Path tempDir) throws Exception {
+        final MessageDigest md = DigestUtils.getDigest(algorithm);
+
+        // Files
+        final Path link = tempDir.resolve("link");
+        final Path linkTxt = tempDir.resolve("link.txt");
+        final Path src = tempDir.resolve("src");
+        final Path hello = src.resolve("hello.txt");
+        final Path run = src.resolve("run.sh");
+
+        // Create the same structure as the virtual tree.
+        try {
+            Files.createSymbolicLink(link, Paths.get(LINK_CONTENT));
+            Files.createSymbolicLink(linkTxt, Paths.get(LINK_TXT_CONTENT));
+        } catch (final UnsupportedOperationException e) {
+            Assumptions.abort("Symbolic links not supported on this 
filesystem");
+        }
+        Files.createDirectory(src);
+        Files.write(hello, HELLO_CONTENT);
+        Files.write(run, RUN_CONTENT);
+        Files.setPosixFilePermissions(run, 
PosixFilePermissions.fromString("rwxr-xr-x"));
+
+        // Verify individual blob IDs against pre-computed constants.
+        assertArrayEquals(helloId, GitIdentifiers.blobId(md, hello));
+        assertArrayEquals(linkId, GitIdentifiers.blobId(md, link));
+        assertArrayEquals(linkTxtId, GitIdentifiers.blobId(md, linkTxt));
+        assertArrayEquals(runId, GitIdentifiers.blobId(md, run));
+
+        // Check trees
+        assertArrayEquals(mainTreeId, GitIdentifiers.treeId(md, tempDir));
+        assertArrayEquals(srcTreeId, GitIdentifiers.treeId(md, src));
+    }
 }

Reply via email to