1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.codec.digest;
19
20 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21 import static org.junit.jupiter.api.Assertions.assertEquals;
22 import static org.junit.jupiter.api.Assertions.assertFalse;
23 import static org.junit.jupiter.api.Assertions.assertNotEquals;
24 import static org.junit.jupiter.api.Assertions.assertThrows;
25
26 import java.io.ByteArrayInputStream;
27 import java.io.IOException;
28 import java.nio.charset.StandardCharsets;
29 import java.nio.file.Files;
30 import java.nio.file.Path;
31 import java.nio.file.Paths;
32 import java.nio.file.attribute.PosixFilePermissions;
33 import java.security.MessageDigest;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.List;
37 import java.util.stream.Stream;
38
39 import org.apache.commons.codec.DecoderException;
40 import org.apache.commons.codec.binary.Hex;
41 import org.apache.commons.codec.digest.GitIdentifiers.DirectoryEntry;
42 import org.junit.jupiter.api.Assumptions;
43 import org.junit.jupiter.api.Test;
44 import org.junit.jupiter.api.io.TempDir;
45 import org.junit.jupiter.params.ParameterizedTest;
46 import org.junit.jupiter.params.provider.Arguments;
47 import org.junit.jupiter.params.provider.MethodSource;
48 import org.junit.jupiter.params.provider.ValueSource;
49
50
51
52
53 class GitIdentifiersTest {
54
55 private static final byte[] ZERO_ID = new byte[20];
56
57
58
59
60
61
62
63
64
65
66 private static final byte[] HELLO_CONTENT = "hello\n".getBytes(StandardCharsets.UTF_8);
67
68 private static final byte[] HELLO_BLOB_ID_SHA1 = hex("ce013625030ba8dba906f756967f9e9ca394464a");
69
70 private static final byte[] HELLO_BLOB_ID_SHA256 = hex("2cf8d83d9ee29543b34a87727421fdecb7e3f3a183d337639025de576db9ebb4");
71
72
73 private static final byte[] RUN_CONTENT = "#!/bin/sh\n".getBytes(StandardCharsets.UTF_8);
74
75 private static final byte[] RUN_BLOB_ID_SHA1 = hex("1a2485251c33a70432394c93fb89330ef214bfc9");
76
77 private static final byte[] RUN_BLOB_ID_SHA256 = hex("1249034e3cf9007362d695b09b1fbdb4c578903bf10b665749b94743f8177ce1");
78
79
80 private static final String LINK_CONTENT = "src";
81
82 private static final byte[] LINK_BLOB_ID_SHA1 = hex("e8310385c56dc4bbe379f43400f3181f6a59f260");
83
84 private static final byte[] LINK_BLOB_ID_SHA256 = hex("e1bdca538422554ea204da85e0cec156b12b6808473083610ff95ea390843ab6");
85
86
87 private static final String LINK_TXT_CONTENT = "src/hello.txt";
88
89 private static final byte[] LINK_TXT_BLOB_ID_SHA1 = hex("132a953033e00dcff94f5cccb261f52cd1d71173");
90
91 private static final byte[] LINK_TXT_BLOB_ID_SHA256 = hex("2499925193a48a84a546a2f7cd3ce7789d4e073ef1e7276fe682bfbb2b636cef");
92
93
94
95
96
97
98
99
100
101
102
103
104 private static final byte[] SRC_TREE_ID_SHA1 = hex("5575b4a0141a2287ec2836a620e5d6aa8fb203ba");
105
106
107
108
109
110
111
112 private static final byte[] SRC_TREE_ID_SHA256 = hex("5b4e74befcb98e3050c511d02353d00565b2172be0a2bc5de833f011ad27f694");
113
114
115
116
117
118
119
120
121
122 private static final byte[] MAIN_TREE_ID_SHA1 = hex("3217900fd0a6624cd6aa169c2a9f289f7f34432b");
123
124
125
126
127
128
129
130
131 private static final byte[] MAIN_TREE_ID_SHA256 = hex("58e9a59940e4d2ae7e374b63fedf3b7bba8cfdc60308f64abd066db137300bcd");
132
133 static Stream<Arguments> blobIdProvider() {
134 return Stream.of(Arguments.of("DigestUtilsTest/hello.txt", "5f4a83288e67f1be2d6fcdad84165a86c6a970d7"),
135 Arguments.of("DigestUtilsTest/greetings.txt", "6cf4f797455661e61d1ee6913fc29344f5897243"),
136 Arguments.of("DigestUtilsTest/subdir/nested.txt", "07a392ddb4dbff06a373a7617939f30b2dcfe719"));
137 }
138
139
140 private static byte[] hex(final String hex) {
141 try {
142 return Hex.decodeHex(hex);
143 } catch (final DecoderException e) {
144 throw new AssertionError(e);
145 }
146 }
147
148 private static Path resourcePath(final String resourceName) throws Exception {
149 return Paths.get(GitIdentifiersTest.class.getClassLoader().getResource(resourceName).toURI());
150 }
151
152 static Stream<Arguments> virtualTreeProvider() {
153 return Stream.of(
154 Arguments.of(MessageDigestAlgorithms.SHA_1, HELLO_BLOB_ID_SHA1, LINK_BLOB_ID_SHA1, LINK_TXT_BLOB_ID_SHA1, RUN_BLOB_ID_SHA1,
155 SRC_TREE_ID_SHA1, MAIN_TREE_ID_SHA1),
156 Arguments.of(MessageDigestAlgorithms.SHA_256, HELLO_BLOB_ID_SHA256, LINK_BLOB_ID_SHA256, LINK_TXT_BLOB_ID_SHA256, RUN_BLOB_ID_SHA256,
157 SRC_TREE_ID_SHA256, MAIN_TREE_ID_SHA256));
158 }
159
160 @ParameterizedTest
161 @MethodSource("blobIdProvider")
162 void testBlobIdByteArray(final String resourceName, final String expectedSha1Hex) throws Exception {
163 final byte[] data = Files.readAllBytes(resourcePath(resourceName));
164 assertArrayEquals(Hex.decodeHex(expectedSha1Hex), GitIdentifiers.blobId(DigestUtils.getSha1Digest(), data));
165 }
166
167 @ParameterizedTest
168 @ValueSource(ints = { 0, 1, 8191, 8192, 8193, 20000 })
169 void testBlobIdInputStreamLengths(final int length) throws IOException {
170 final byte[] data = new byte[length];
171 Arrays.fill(data, (byte) 42);
172 assertArrayEquals(GitIdentifiers.blobId(DigestUtils.getSha1Digest(), data),
173 GitIdentifiers.blobId(DigestUtils.getSha1Digest(), length, new ByteArrayInputStream(data)));
174 for (final long declaredSize : new long[] { -1, length - 1L, length + 1L }) {
175 assertThrows(IOException.class, () -> GitIdentifiers.blobId(DigestUtils.getSha1Digest(), declaredSize, new ByteArrayInputStream(data)));
176 }
177 }
178
179 @ParameterizedTest
180 @MethodSource("blobIdProvider")
181 void testBlobIdInputStreamWithSize(final String resourceName, final String expectedSha1Hex) throws Exception {
182 final byte[] data = Files.readAllBytes(resourcePath(resourceName));
183 assertArrayEquals(Hex.decodeHex(expectedSha1Hex),
184 GitIdentifiers.blobId(DigestUtils.getSha1Digest(), data.length, new ByteArrayInputStream(data)));
185 }
186
187 @ParameterizedTest
188 @MethodSource("blobIdProvider")
189 void testBlobIdPath(final String resourceName, final String expectedSha1Hex) throws Exception {
190 assertArrayEquals(Hex.decodeHex(expectedSha1Hex), GitIdentifiers.blobId(DigestUtils.getSha1Digest(), resourcePath(resourceName)));
191 }
192
193 @Test
194 void testBlobIdSymlink(@TempDir final Path tempDir) throws Exception {
195 final Path subDir = Files.createDirectory(tempDir.resolve("subdir"));
196 Files.write(subDir.resolve("file.txt"), "hello".getBytes(StandardCharsets.UTF_8));
197 try {
198 final Path linkToDir = Files.createSymbolicLink(tempDir.resolve("link-to-dir"), Paths.get("subdir"));
199 final Path linkToFile = Files.createSymbolicLink(tempDir.resolve("link-to-file"), Paths.get("subdir/file.txt"));
200 final MessageDigest sha1 = DigestUtils.getSha1Digest();
201 assertArrayEquals(Hex.decodeHex("8bbe8a53790056316b23b7c270f10ab6bf6bb1b4"), GitIdentifiers.blobId(sha1, linkToDir));
202 assertArrayEquals(Hex.decodeHex("dfe6ef8392ae13a11ff85419b4fd906d997b6cb7"), GitIdentifiers.blobId(sha1, linkToFile));
203 } catch (final UnsupportedOperationException e) {
204 Assumptions.abort("Symbolic links not supported on this filesystem");
205 }
206 }
207
208 @Test
209 void testDirectoryEntryConstructor() {
210 assertThrows(NullPointerException.class, () -> new DirectoryEntry(null, GitIdentifiers.FileMode.REGULAR, ZERO_ID));
211 assertThrows(NullPointerException.class, () -> new DirectoryEntry("hello.txt", null, ZERO_ID));
212 assertThrows(NullPointerException.class, () -> new DirectoryEntry("hello.txt", GitIdentifiers.FileMode.REGULAR, null));
213 assertThrows(IllegalArgumentException.class, () -> new DirectoryEntry("/", GitIdentifiers.FileMode.REGULAR, ZERO_ID));
214 }
215
216
217
218
219 @Test
220 void testDirectoryEntryEqualityBasedOnNameOnly() {
221 final byte[] otherId = new byte[20];
222 Arrays.fill(otherId, (byte) 0xff);
223 final DirectoryEntry regular = new DirectoryEntry("foo", GitIdentifiers.FileMode.REGULAR, ZERO_ID);
224 final DirectoryEntry executable = new DirectoryEntry("foo", GitIdentifiers.FileMode.EXECUTABLE, otherId);
225
226 assertEquals(regular, executable);
227 assertEquals(regular.hashCode(), executable.hashCode());
228
229 assertNotEquals(regular, new DirectoryEntry("bar", GitIdentifiers.FileMode.REGULAR, ZERO_ID));
230
231 assertEquals(regular, regular);
232
233 assertFalse(regular.equals(null));
234 assertFalse(regular.equals("foo"));
235 }
236
237
238
239
240
241
242
243
244 @Test
245 void testDirectoryEntrySortOrder() {
246 final DirectoryEntry alpha = new DirectoryEntry("alpha.txt", GitIdentifiers.FileMode.REGULAR, ZERO_ID);
247 final DirectoryEntry fooTxt = new DirectoryEntry("foo.txt", GitIdentifiers.FileMode.REGULAR, ZERO_ID);
248 final DirectoryEntry fooDir = new DirectoryEntry("foo", GitIdentifiers.FileMode.DIRECTORY, ZERO_ID);
249 final DirectoryEntry foobar = new DirectoryEntry("foobar", GitIdentifiers.FileMode.REGULAR, ZERO_ID);
250 final DirectoryEntry zeta = new DirectoryEntry("zeta.txt", GitIdentifiers.FileMode.REGULAR, ZERO_ID);
251 final List<DirectoryEntry> entries = new ArrayList<>(Arrays.asList(zeta, foobar, fooDir, alpha, fooTxt));
252 entries.sort(DirectoryEntry::compareTo);
253 assertEquals(Arrays.asList(alpha, fooTxt, fooDir, foobar, zeta), entries);
254 }
255
256 @ParameterizedTest
257 @ValueSource(strings = {"..", "bad\0dir", "\uD800", "\uDC00"})
258 void testRejectsInvalidDirectoryNames(final String name) {
259 final GitIdentifiers.TreeIdBuilder builder = GitIdentifiers.treeIdBuilder(DigestUtils.getSha1Digest());
260 assertThrows(IllegalArgumentException.class, () -> builder.addDirectory("parent/" + name));
261 assertThrows(IllegalArgumentException.class, () -> builder.addFile(GitIdentifiers.FileMode.REGULAR, name + "/file", HELLO_CONTENT));
262 }
263
264 @ParameterizedTest
265 @ValueSource(strings = {"", ".", "..", "a\0b", "\uD800", "\uDC00", "a\uD800b", "\uD800\uD800", "\uDC00\uD800"})
266 void testRejectsInvalidEntryNames(final String name) {
267 assertThrows(IllegalArgumentException.class, () -> new DirectoryEntry(name, GitIdentifiers.FileMode.REGULAR, ZERO_ID));
268 final GitIdentifiers.TreeIdBuilder builder = GitIdentifiers.treeIdBuilder(DigestUtils.getSha1Digest());
269 assertThrows(IllegalArgumentException.class, () -> builder.addFile(GitIdentifiers.FileMode.REGULAR, name, HELLO_CONTENT));
270 assertThrows(IllegalArgumentException.class,
271 () -> builder.addFile(GitIdentifiers.FileMode.REGULAR, name, HELLO_CONTENT.length, new ByteArrayInputStream(HELLO_CONTENT)));
272 assertThrows(IllegalArgumentException.class, () -> builder.addSymbolicLink(name, "target"));
273 }
274
275 @ParameterizedTest
276 @ValueSource(strings = {"?", "a\nb", "\uD83D\uDE00"})
277 void testTreeIdAcceptsValidEntryNames(final String name) throws Exception {
278 final GitIdentifiers.TreeIdBuilder builder = GitIdentifiers.treeIdBuilder(DigestUtils.getSha1Digest());
279 builder.addFile(GitIdentifiers.FileMode.REGULAR, name, HELLO_CONTENT);
280 assertEquals(20, builder.get().length);
281 final GitIdentifiers.TreeIdBuilder directory = GitIdentifiers.treeIdBuilder(DigestUtils.getSha1Digest());
282 directory.addDirectory(name).addFile(GitIdentifiers.FileMode.REGULAR, "file", HELLO_CONTENT);
283 assertEquals(20, directory.get().length);
284 }
285
286 @ParameterizedTest
287 @MethodSource("virtualTreeProvider")
288 void testTreeIdBuilder(final String algorithm, final byte[] helloId, final byte[] linkId, final byte[] linkTxtId, final byte[] runId,
289 final byte[] srcTreeId, final byte[] mainTreeId) throws Exception {
290 final MessageDigest md = DigestUtils.getDigest(algorithm);
291
292
293 assertArrayEquals(helloId, GitIdentifiers.blobId(md, HELLO_CONTENT));
294 assertArrayEquals(linkId, GitIdentifiers.blobId(md, LINK_CONTENT.getBytes(StandardCharsets.UTF_8)));
295 assertArrayEquals(linkTxtId, GitIdentifiers.blobId(md, LINK_TXT_CONTENT.getBytes(StandardCharsets.UTF_8)));
296 assertArrayEquals(runId, GitIdentifiers.blobId(md, RUN_CONTENT));
297
298
299 final GitIdentifiers.TreeIdBuilder builder = GitIdentifiers.treeIdBuilder(md);
300 builder.addSymbolicLink("link.txt", LINK_TXT_CONTENT);
301 builder.addFile(GitIdentifiers.FileMode.REGULAR, "src/hello.txt", HELLO_CONTENT);
302 builder.addSymbolicLink("link", LINK_CONTENT);
303 builder.addFile(GitIdentifiers.FileMode.EXECUTABLE, "src/run.sh", RUN_CONTENT);
304
305
306 assertArrayEquals(mainTreeId, builder.get());
307 assertArrayEquals(srcTreeId, builder.addDirectory("src").get());
308 }
309
310 @Test
311 void testTreeIdBuilderAddFileInputStream() throws Exception {
312 final MessageDigest md = DigestUtils.getSha1Digest();
313 final byte[] content = "Hello, World!\n".getBytes(StandardCharsets.UTF_8);
314
315 final GitIdentifiers.TreeIdBuilder byteArrayBuilder = GitIdentifiers.treeIdBuilder(md);
316 byteArrayBuilder.addFile(GitIdentifiers.FileMode.REGULAR, "file.txt", content);
317 final byte[] expected = byteArrayBuilder.get();
318
319 final GitIdentifiers.TreeIdBuilder sizedStreamBuilder = GitIdentifiers.treeIdBuilder(md);
320 sizedStreamBuilder.addFile(GitIdentifiers.FileMode.REGULAR, "file.txt", content.length, new ByteArrayInputStream(content));
321 assertArrayEquals(expected, sizedStreamBuilder.get());
322 }
323
324 @Test
325 void testTreeIdBuilderInvalidPathSegments() {
326 final MessageDigest md = DigestUtils.getSha1Digest();
327 final byte[] data = {};
328
329 assertThrows(IllegalArgumentException.class,
330 () -> GitIdentifiers.treeIdBuilder(md).addFile(GitIdentifiers.FileMode.REGULAR, "..", data));
331 assertThrows(IllegalArgumentException.class,
332 () -> GitIdentifiers.treeIdBuilder(md).addDirectory(".."));
333
334 assertThrows(IllegalArgumentException.class,
335 () -> GitIdentifiers.treeIdBuilder(md).addFile(GitIdentifiers.FileMode.REGULAR, "subdir/../file.txt", data));
336 assertThrows(IllegalArgumentException.class,
337 () -> GitIdentifiers.treeIdBuilder(md).addDirectory("subdir/.."));
338 }
339
340 @Test
341 void testTreeIdBuilderNestedFileEquivalentToDirectoryAndFile() throws Exception {
342 final MessageDigest md = DigestUtils.getSha1Digest();
343 final byte[] content = "hello\n".getBytes(StandardCharsets.UTF_8);
344
345 final GitIdentifiers.TreeIdBuilder direct = GitIdentifiers.treeIdBuilder(md);
346 direct.addFile(GitIdentifiers.FileMode.REGULAR, "nested/file.txt", content);
347
348 final GitIdentifiers.TreeIdBuilder indirect = GitIdentifiers.treeIdBuilder(md);
349 indirect.addDirectory("nested").addFile(GitIdentifiers.FileMode.REGULAR, "file.txt", content);
350
351 assertArrayEquals(direct.get(), indirect.get());
352 }
353
354 @ParameterizedTest
355 @ValueSource(strings = {"", "."})
356 void testTreeIdBuilderNoopPathSegments(final String segment) throws Exception {
357 final MessageDigest md = DigestUtils.getSha1Digest();
358 final byte[] content = "hello\n".getBytes(StandardCharsets.UTF_8);
359
360
361 final GitIdentifiers.TreeIdBuilder canonical = GitIdentifiers.treeIdBuilder(md);
362 canonical.addFile(GitIdentifiers.FileMode.REGULAR, "subdir/file.txt", content);
363 final byte[] expected = canonical.get();
364
365
366 final GitIdentifiers.TreeIdBuilder withLeading = GitIdentifiers.treeIdBuilder(md);
367 withLeading.addFile(GitIdentifiers.FileMode.REGULAR, segment + "/subdir/file.txt", content);
368 assertArrayEquals(expected, withLeading.get());
369
370
371 final GitIdentifiers.TreeIdBuilder withIntermediate = GitIdentifiers.treeIdBuilder(md);
372 withIntermediate.addFile(GitIdentifiers.FileMode.REGULAR, "subdir/" + segment + "/file.txt", content);
373 assertArrayEquals(expected, withIntermediate.get());
374
375
376 final GitIdentifiers.TreeIdBuilder viaDirectory = GitIdentifiers.treeIdBuilder(md);
377 viaDirectory.addDirectory(segment + "/subdir/" + segment).addFile(GitIdentifiers.FileMode.REGULAR, "file.txt", content);
378 assertArrayEquals(expected, viaDirectory.get());
379 }
380
381 @ParameterizedTest
382 @ValueSource(longs = { -1, 0, 5, 7 })
383 void testTreeIdBuilderRejectsWrongStreamSize(final long declaredSize) throws IOException {
384 final GitIdentifiers.TreeIdBuilder builder = GitIdentifiers.treeIdBuilder(DigestUtils.getSha1Digest());
385 builder.addFile(GitIdentifiers.FileMode.REGULAR, "hello.txt", HELLO_CONTENT);
386 final byte[] expected = builder.get();
387 assertThrows(IOException.class,
388 () -> builder.addFile(GitIdentifiers.FileMode.REGULAR, "hello.txt", declaredSize, new ByteArrayInputStream(HELLO_CONTENT)));
389 assertArrayEquals(expected, builder.get());
390 }
391
392 @Test
393 void testTreeIdPath() throws Exception {
394 assertArrayEquals(Hex.decodeHex("e4b21f6d78ceba6eb7c211ac15e3337ec4614e8a"),
395 GitIdentifiers.treeId(DigestUtils.getSha1Digest(), resourcePath("DigestUtilsTest")));
396 }
397
398 @ParameterizedTest
399 @MethodSource("virtualTreeProvider")
400 void testTreeIdPathUnix(final String algorithm, final byte[] helloId, final byte[] linkId, final byte[] linkTxtId,
401 final byte[] runId, final byte[] srcTreeId, final byte[] mainTreeId, final @TempDir Path tempDir) throws Exception {
402 final MessageDigest md = DigestUtils.getDigest(algorithm);
403
404
405 final Path link = tempDir.resolve("link");
406 final Path linkTxt = tempDir.resolve("link.txt");
407 final Path src = tempDir.resolve("src");
408 final Path hello = src.resolve("hello.txt");
409 final Path run = src.resolve("run.sh");
410
411
412 try {
413 Files.createSymbolicLink(link, Paths.get(LINK_CONTENT));
414 Files.createSymbolicLink(linkTxt, Paths.get(LINK_TXT_CONTENT));
415 } catch (final UnsupportedOperationException e) {
416 Assumptions.abort("Symbolic links not supported on this filesystem");
417 }
418 Files.createDirectory(src);
419 Files.write(hello, HELLO_CONTENT);
420 Files.write(run, RUN_CONTENT);
421 Files.setPosixFilePermissions(run, PosixFilePermissions.fromString("rwxr-xr-x"));
422
423
424 assertArrayEquals(helloId, GitIdentifiers.blobId(md, hello));
425 assertArrayEquals(linkId, GitIdentifiers.blobId(md, link));
426 assertArrayEquals(linkTxtId, GitIdentifiers.blobId(md, linkTxt));
427 assertArrayEquals(runId, GitIdentifiers.blobId(md, run));
428
429
430 assertArrayEquals(mainTreeId, GitIdentifiers.treeId(md, tempDir));
431 assertArrayEquals(srcTreeId, GitIdentifiers.treeId(md, src));
432 }
433
434 @ParameterizedTest
435 @ValueSource(strings = {"x", "parent/x"})
436 void testTreeIdRejectsFileDirectoryConflicts(final String name) throws Exception {
437 final GitIdentifiers.TreeIdBuilder fileFirst = GitIdentifiers.treeIdBuilder(DigestUtils.getSha1Digest());
438 fileFirst.addFile(GitIdentifiers.FileMode.REGULAR, name, HELLO_CONTENT);
439 fileFirst.addDirectory(name);
440 assertThrows(IllegalStateException.class, fileFirst::get);
441 final GitIdentifiers.TreeIdBuilder directoryFirst = GitIdentifiers.treeIdBuilder(DigestUtils.getSha1Digest());
442 directoryFirst.addDirectory(name);
443 directoryFirst.addSymbolicLink(name, "target");
444 assertThrows(IllegalStateException.class, directoryFirst::get);
445 }
446
447 @Test
448 void testTreeIdRejectsNulSerializationCollision() throws Exception {
449 final MessageDigest md = DigestUtils.getSha1Digest();
450 final byte[] a = "2161978".getBytes(StandardCharsets.UTF_8);
451 final byte[] b = "payload".getBytes(StandardCharsets.UTF_8);
452 final byte[] blobId = GitIdentifiers.blobId(md, a);
453 assertEquals("615d6b396b134e0c1a617b0b7050632d627d154f", Hex.encodeHexString(blobId));
454 final GitIdentifiers.TreeIdBuilder legitimate = GitIdentifiers.treeIdBuilder(md);
455 legitimate.addFile(GitIdentifiers.FileMode.REGULAR, "a", a);
456 legitimate.addFile(GitIdentifiers.FileMode.REGULAR, "b", b);
457 assertEquals("cb1e930df28b6dc3ab8933ff7a8d233f1c189460", Hex.encodeHexString(legitimate.get()));
458
459 final String forgedName = "a\0" + new String(blobId, StandardCharsets.UTF_8) + "100644 b";
460 final GitIdentifiers.TreeIdBuilder forged = GitIdentifiers.treeIdBuilder(md);
461 assertThrows(IllegalArgumentException.class, () -> forged.addFile(GitIdentifiers.FileMode.REGULAR, forgedName, b));
462 }
463
464 @Test
465 void testTreeIdRejectsTrailingSlash() {
466 final GitIdentifiers.TreeIdBuilder builder = GitIdentifiers.treeIdBuilder(DigestUtils.getSha1Digest());
467 assertThrows(IllegalArgumentException.class, () -> builder.addFile(GitIdentifiers.FileMode.REGULAR, "dir/", HELLO_CONTENT));
468 }
469
470
471
472
473
474
475
476
477
478
479 @Test
480 void testTreeIdSortsSupplementaryPlaneNamesLikeGit(@TempDir final Path tempDir) throws Exception {
481 final String fullWidthA = "\uFF21";
482 final String grinningFace = "\uD83D\uDE00";
483 final byte[] content = "x".getBytes(StandardCharsets.UTF_8);
484 final String expected = "9f9c1fc3580195f51d3e71b384ef1d57740e2151";
485 final MessageDigest md = DigestUtils.getSha1Digest();
486
487
488 final GitIdentifiers.TreeIdBuilder builder = GitIdentifiers.treeIdBuilder(md);
489 builder.addFile(GitIdentifiers.FileMode.REGULAR, grinningFace, content);
490 builder.addFile(GitIdentifiers.FileMode.REGULAR, fullWidthA, content);
491 assertEquals(expected, Hex.encodeHexString(builder.get()));
492
493 try {
494 Files.write(tempDir.resolve(fullWidthA), content);
495 Files.write(tempDir.resolve(grinningFace), content);
496 } catch (final IOException e) {
497 Assumptions.abort("Filesystem cannot hold the test entry names: " + e);
498 }
499 assertEquals(expected, Hex.encodeHexString(GitIdentifiers.treeId(md, tempDir)));
500 }
501 }