1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * See LICENSE.txt included in this distribution for the specific 9 * language governing permissions and limitations under the License. 10 * 11 * When distributing Covered Code, include this CDDL HEADER in each 12 * file and include the License file at LICENSE.txt. 13 * If applicable, add the following below this CDDL HEADER, with the 14 * fields enclosed by brackets "[]" replaced with your own identifying 15 * information: Portions Copyright [yyyy] [name of copyright owner] 16 * 17 * CDDL HEADER END 18 */ 19 20 /* 21 * Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved. 22 * Portions Copyright (c) 2017, 2019, Chris Fraire <cfraire@me.com>. 23 * Portions Copyright (c) 2019, Krystof Tulinger <k.tulinger@seznam.cz>. 24 */ 25 package org.opengrok.indexer.history; 26 27 import java.io.File; 28 import java.io.FileWriter; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.Writer; 32 import java.net.URISyntaxException; 33 import java.nio.file.Path; 34 import java.nio.file.Paths; 35 import java.util.Arrays; 36 import java.util.Collections; 37 import java.util.Date; 38 import java.util.HashSet; 39 import java.util.List; 40 import java.util.Set; 41 import java.util.TreeSet; 42 import java.util.stream.Collectors; 43 44 import org.eclipse.jgit.api.Git; 45 import org.eclipse.jgit.lib.Constants; 46 import org.eclipse.jgit.lib.ObjectId; 47 import org.eclipse.jgit.lib.Ref; 48 import org.eclipse.jgit.revwalk.RevCommit; 49 import org.eclipse.jgit.revwalk.RevWalk; 50 import org.eclipse.jgit.storage.file.FileRepositoryBuilder; 51 import org.eclipse.jgit.transport.URIish; 52 import org.junit.jupiter.api.AfterAll; 53 import org.junit.jupiter.api.AfterEach; 54 import org.junit.jupiter.api.BeforeAll; 55 import org.junit.jupiter.api.BeforeEach; 56 import org.junit.jupiter.api.Test; 57 import org.junit.jupiter.params.ParameterizedTest; 58 import org.junit.jupiter.params.provider.ValueSource; 59 import org.opengrok.indexer.configuration.CommandTimeoutType; 60 import org.opengrok.indexer.configuration.RuntimeEnvironment; 61 import org.opengrok.indexer.util.IOUtils; 62 import org.opengrok.indexer.util.TestRepository; 63 64 import static org.junit.jupiter.api.Assertions.assertEquals; 65 import static org.junit.jupiter.api.Assertions.assertNotEquals; 66 import static org.junit.jupiter.api.Assertions.assertNotNull; 67 import static org.junit.jupiter.api.Assertions.assertNull; 68 import static org.junit.jupiter.api.Assertions.assertThrows; 69 import static org.junit.jupiter.api.Assertions.assertTrue; 70 71 /** 72 * @author austvik 73 */ 74 public class GitRepositoryTest { 75 76 private static TestRepository repository = new TestRepository(); 77 private GitRepository instance; 78 79 @BeforeAll setUpClass()80 public static void setUpClass() throws IOException, URISyntaxException { 81 repository.create(GitRepositoryTest.class.getResource("/repositories")); 82 } 83 84 @AfterAll tearDownClass()85 public static void tearDownClass() { 86 repository.destroy(); 87 repository = null; 88 } 89 90 @BeforeEach setUp()91 public void setUp() { 92 instance = new GitRepository(); 93 } 94 95 @AfterEach tearDown()96 public void tearDown() { 97 instance = null; 98 } 99 checkCurrentVersion(File root, int timestamp, String commitId, String shortComment)100 private void checkCurrentVersion(File root, int timestamp, String commitId, String shortComment) 101 throws Exception { 102 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 103 assertNotNull(gitrepo); 104 String ver = gitrepo.determineCurrentVersion(); 105 assertNotNull(ver); 106 Date date = new Date((long) (timestamp) * 1000); 107 assertEquals(Repository.format(date) + " " + commitId + " " + shortComment, ver); 108 } 109 110 @Test testDetermineCurrentVersionWithEmptyRepository()111 void testDetermineCurrentVersionWithEmptyRepository() throws Exception { 112 File emptyGitDir = new File(repository.getSourceRoot(), "gitEmpty"); 113 try (Git git = Git.init().setDirectory(emptyGitDir).call()) { 114 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(git.getRepository().getWorkTree()); 115 assertNotNull(gitrepo); 116 String ver = gitrepo.determineCurrentVersion(); 117 assertNull(ver); 118 removeRecursive(emptyGitDir); 119 } 120 } 121 122 @Test testDetermineCurrentVersionOfKnownRepository()123 void testDetermineCurrentVersionOfKnownRepository() throws Exception { 124 File root = new File(repository.getSourceRoot(), "git"); 125 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 126 assertNotNull(gitrepo); 127 String ver = gitrepo.determineCurrentVersion(); 128 assertNotNull(ver); 129 Date date = new Date((long) (1485438707) * 1000); 130 assertEquals(Repository.format(date) + " 84599b3 Kryštof Tulinger renaming directories", ver); 131 } 132 133 @Test testDetermineCurrentVersionAfterChange()134 void testDetermineCurrentVersionAfterChange() throws Exception { 135 File root = new File(repository.getSourceRoot(), "git"); 136 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 137 assertNotNull(gitrepo); 138 String ver; 139 // Clone under source root to avoid problems with prohibited symlinks. 140 File localPath = new File(repository.getSourceRoot(), "gitCloneTestCurrentVersion"); 141 String cloneUrl = root.toURI().toString(); 142 try (Git gitClone = Git.cloneRepository() 143 .setURI(cloneUrl) 144 .setDirectory(localPath) 145 .call()) { 146 File cloneRoot = gitClone.getRepository().getWorkTree(); 147 // Check the checkout went okay. 148 checkCurrentVersion(cloneRoot, 1485438707, "84599b3", 149 "Kryštof Tulinger renaming directories"); 150 151 // Create new file, commit and check the current version string. 152 File myFile = new File(cloneRoot, "testfile"); 153 if (!myFile.createNewFile()) { 154 throw new IOException("Could not create file " + myFile); 155 } 156 gitClone.add() 157 .addFilepattern("testfile") 158 .call(); 159 String comment = "Added testfile"; 160 String authorName = "Foo Bar"; 161 gitClone.commit() 162 .setMessage(comment) 163 .setAuthor(authorName, "foo@bar.com") 164 .call(); 165 166 gitrepo = (GitRepository) RepositoryFactory.getRepository(cloneRoot); 167 assertNotNull(gitrepo); 168 ver = gitrepo.determineCurrentVersion(); 169 assertNotNull(ver); 170 // Not able to set commit ID and date so only check the rest. 171 assertTrue(ver.endsWith(authorName + " " + comment), "ends with author and commit comment"); 172 173 removeRecursive(cloneRoot); 174 } 175 } 176 177 @Test testDetermineBranchBasic()178 void testDetermineBranchBasic() throws Exception { 179 // First check branch of known repository. 180 File root = new File(repository.getSourceRoot(), "git"); 181 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 182 String branch = gitrepo.determineBranch(); 183 assertNotNull(branch); 184 assertEquals("master", branch); 185 } 186 187 @Test testDetermineBranchAfterChange()188 void testDetermineBranchAfterChange() throws Exception { 189 // Clone the test repository and create new branch there. 190 // Clone under source root to avoid problems with prohibited symlinks. 191 File root = new File(repository.getSourceRoot(), "git"); 192 File localPath = new File(repository.getSourceRoot(), "gitCloneTestDetermineBranch"); 193 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 194 String branch; 195 String cloneUrl = root.toURI().toString(); 196 try (Git gitClone = Git.cloneRepository() 197 .setURI(cloneUrl) 198 .setDirectory(localPath) 199 .call()) { 200 201 Ref ref = gitClone.checkout().setCreateBranch(true).setName("foo").call(); 202 assertNotNull(ref); 203 204 File cloneRoot = gitClone.getRepository().getWorkTree(); 205 gitrepo = (GitRepository) RepositoryFactory.getRepository(cloneRoot); 206 branch = gitrepo.determineBranch(); 207 assertNotNull(branch); 208 assertEquals("foo", branch); 209 210 removeRecursive(cloneRoot); 211 } 212 } 213 214 @Test testGetHistoryInBranch()215 void testGetHistoryInBranch() throws Exception { 216 // Clone the test repository and create new branch there. 217 // Clone under source root to avoid problems with prohibited symlinks. 218 File root = new File(repository.getSourceRoot(), "git"); 219 File localPath = new File(repository.getSourceRoot(), "testGetHistoryInBranch"); 220 String cloneUrl = root.toURI().toString(); 221 try (Git gitClone = Git.cloneRepository() 222 .setURI(cloneUrl) 223 .setDirectory(localPath) 224 .call()) { 225 226 Ref ref = gitClone.checkout().setCreateBranch(true).setName("foo").call(); 227 assertNotNull(ref); 228 229 File cloneRoot = gitClone.getRepository().getWorkTree(); 230 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(cloneRoot); 231 232 History history = gitrepo.getHistory(cloneRoot); 233 assertNotNull(history); 234 int numEntries = history.getHistoryEntries().size(); 235 assertTrue(numEntries > 0); 236 237 RevCommit commit = gitClone.commit(). 238 setAuthor("Snufkin", "snufkin@moomin.valley"). 239 setMessage("fresh commit on a new branch").setAllowEmpty(true).call(); 240 assertNotNull(commit); 241 242 history = gitrepo.getHistory(cloneRoot); 243 assertEquals(numEntries + 1, history.getHistoryEntries().size()); 244 245 removeRecursive(cloneRoot); 246 } 247 } 248 249 @Test testDetermineParentEmpty()250 void testDetermineParentEmpty() throws Exception { 251 File root = new File(repository.getSourceRoot(), "git"); 252 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 253 String parent = gitrepo.determineParent(); 254 assertNull(parent); 255 } 256 257 @Test testDetermineParent()258 void testDetermineParent() throws Exception { 259 File root = new File(repository.getSourceRoot(), "git"); 260 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 261 String parent; 262 // Clone the repository and create new origin there. 263 // Clone under source root to avoid problems with prohibited symlinks. 264 File localPath = new File(repository.getSourceRoot(), "gitCloneTestDetermineParent"); 265 String cloneUrl = root.toURI().toString(); 266 try (Git gitClone = Git.cloneRepository() 267 .setURI(cloneUrl) 268 .setDirectory(localPath) 269 .call()) { 270 271 String uri = "http://foo.bar"; 272 gitClone.remoteAdd().setName("origin").setUri(new URIish(uri)).call(); 273 274 File cloneRoot = gitClone.getRepository().getWorkTree(); 275 gitrepo = (GitRepository) RepositoryFactory.getRepository(cloneRoot); 276 parent = gitrepo.determineParent(); 277 assertNotNull(parent); 278 assertEquals(uri, parent); 279 280 removeRecursive(cloneRoot); 281 } 282 } 283 284 /** 285 * Test of fileHasAnnotation method, of class GitRepository. 286 */ 287 @Test fileHasAnnotation()288 void fileHasAnnotation() { 289 boolean result = instance.fileHasAnnotation(null); 290 assertTrue(result); 291 } 292 293 /** 294 * Test of fileHasHistory method, of class GitRepository. 295 */ 296 @Test fileHasHistory()297 void fileHasHistory() { 298 boolean result = instance.fileHasHistory(null); 299 assertTrue(result); 300 } 301 302 /** 303 * For the following renamed tests the structure in the git repo is as following: 304 * <pre> 305 * ce4c98ec - new file renamed.c (with some content) 306 * b6413947 - renamed.c renamed to moved/renamed.c 307 * 1086eaf5 - modification of file moved/renamed.c (different content) 308 * 67dfbe26 - moved/renamed.c renamed to moved/renamed2.c 309 * 84599b3c - moved/renamed2.c renamed to moved2/renamed2.c 310 * </pre> 311 */ 312 @Test testRenamedFiles()313 void testRenamedFiles() throws Exception { 314 String[][] tests = new String[][] { 315 {Paths.get("moved2", "renamed2.c").toString(), "84599b3c", Paths.get("moved2", "renamed2.c").toString()}, 316 {Paths.get("moved2", "renamed2.c").toString(), "67dfbe26", Paths.get("moved", "renamed2.c").toString()}, 317 {Paths.get("moved2", "renamed2.c").toString(), "67dfbe26", Paths.get("moved", "renamed2.c").toString()}, 318 {Paths.get("moved2", "renamed2.c").toString(), "1086eaf5", Paths.get("moved", "renamed.c").toString()}, 319 {Paths.get("moved2", "renamed2.c").toString(), "b6413947", Paths.get("moved", "renamed.c").toString()}, 320 {Paths.get("moved2", "renamed2.c").toString(), "ce4c98ec", "renamed.c"}, 321 {Paths.get("moved2", "renamed2.c").toString(), "bb74b7e8", "renamed.c"} 322 }; 323 324 File root = new File(repository.getSourceRoot(), "git"); 325 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 326 gitrepo.setHandleRenamedFiles(true); 327 328 for (String[] test : tests) { 329 String file = Paths.get(root.getCanonicalPath(), test[0]).toString(); 330 String changeset = test[1]; 331 String expectedName = test[2]; 332 333 String originalName = gitrepo.findOriginalName(file, changeset); 334 assertEquals(expectedName, originalName); 335 } 336 } 337 testAnnotationOfFile(GitRepository gitrepo, File file, String revision, Set<String> revSet)338 private void testAnnotationOfFile(GitRepository gitrepo, File file, String revision, Set<String> revSet) throws Exception { 339 Annotation annotation = gitrepo.annotate(file, revision); 340 341 assertNotNull(annotation); 342 assertEquals(revSet, annotation.getRevisions()); 343 } 344 345 @Test testAnnotationOfRenamedFileWithHandlingOff()346 void testAnnotationOfRenamedFileWithHandlingOff() throws Exception { 347 String[] revisions = {"84599b3c"}; 348 Set<String> revSet = new HashSet<>(); 349 Collections.addAll(revSet, revisions); 350 351 File root = new File(repository.getSourceRoot(), "git"); 352 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 353 gitrepo.setHandleRenamedFiles(false); 354 File renamedFile = Paths.get(root.getAbsolutePath(), "moved2", "renamed2.c").toFile(); 355 testAnnotationOfFile(gitrepo, renamedFile, null, revSet); 356 } 357 358 @Test testAnnotationOfRenamedFileWithHandlingOn()359 void testAnnotationOfRenamedFileWithHandlingOn() throws Exception { 360 String[] revisions = {"1086eaf5", "ce4c98ec"}; 361 Set<String> revSet = new HashSet<>(); 362 Collections.addAll(revSet, revisions); 363 364 File root = new File(repository.getSourceRoot(), "git"); 365 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 366 gitrepo.setHandleRenamedFiles(true); 367 File renamedFile = Paths.get(root.getAbsolutePath(), "moved2", "renamed2.c").toFile(); 368 testAnnotationOfFile(gitrepo, renamedFile, null, revSet); 369 } 370 371 @Test testAnnotationOfRenamedFilePastWithHandlingOn()372 void testAnnotationOfRenamedFilePastWithHandlingOn() throws Exception { 373 String[] revisions = {"1086eaf5", "ce4c98ec"}; 374 Set<String> revSet = new HashSet<>(); 375 Collections.addAll(revSet, revisions); 376 377 File root = new File(repository.getSourceRoot(), "git"); 378 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 379 gitrepo.setHandleRenamedFiles(true); 380 File renamedFile = Paths.get(root.getAbsolutePath(), "moved2", "renamed2.c").toFile(); 381 testAnnotationOfFile(gitrepo, renamedFile, "1086eaf5", revSet); 382 } 383 384 @Test testInvalidRenamedFiles()385 void testInvalidRenamedFiles() throws Exception { 386 String[][] tests = new String[][] { 387 {"", "67dfbe26"}, 388 {"moved/renamed2.c", ""}, 389 {"", ""}, 390 {null, "67dfbe26"}, 391 {"moved/renamed2.c", null} 392 393 }; 394 File root = new File(repository.getSourceRoot(), "git"); 395 GitRepository gitRepository = (GitRepository) RepositoryFactory.getRepository(root); 396 assertThrows(IOException.class, () -> { 397 for (String[] test : tests) { 398 String file = test[0]; 399 String changeset = test[1]; 400 gitRepository.findOriginalName(file, changeset); 401 } 402 }); 403 } 404 405 /** 406 * Test that {@code getHistoryGet()} returns historical contents of renamed 407 * file. 408 * @see #testRenamedFiles for git repo structure info 409 */ 410 @Test testGetRenamedFileContent()411 void testGetRenamedFileContent() throws Exception { 412 String old_content 413 = "#include <stdio.h>\n" 414 + "#include <stdlib.h>\n" 415 + "\n" 416 + "int main ( int argc, const char * argv[] )\n" 417 + "{\n" 418 + "\tint i;\n" 419 + "\tfor ( i = 1; i < argc; i ++ )\n" 420 + "\t{\n" 421 + "\t\tprintf ( \"%s called with %d\\n\", argv [ 0 ], argv [ i ] );\n" 422 + "\t}\n" 423 + "\n" 424 + "\treturn 0;\n" 425 + "}\n"; 426 427 String new_content 428 = "#include <stdio.h>\n" 429 + "#include <stdlib.h>\n" 430 + "\n" 431 + "int foo ( const char * path )\n" 432 + "{\n" 433 + "\treturn path && *path == 'A';\n" 434 + "}\n" 435 + "\n" 436 + "int main ( int argc, const char * argv[] )\n" 437 + "{\n" 438 + "\tint i;\n" 439 + "\tfor ( i = 1; i < argc; i ++ )\n" 440 + "\t{\n" 441 + "\t\tprintf ( \"%s called with %d\\n\", argv [ 0 ], argv [ i ] );\n" 442 + "\t}\n" 443 + "\n" 444 + "\tprintf ( \"Hello, world!\\n\" );\n" 445 + "\n" 446 + "\tif ( foo ( argv [ 0 ] ) )\n" 447 + "\t{\n" 448 + "\t\tprintf ( \"Correct call\\n\" );\n" 449 + "\t}\n" 450 + "\n" 451 + "\treturn 0;\n" 452 + "}\n"; 453 454 final List<String[]> tests = Arrays.asList( 455 // old content (after revision 1086eaf5 inclusively) 456 new String[] {Paths.get("moved2", "renamed2.c").toString(), "84599b3c", new_content}, 457 new String[] {Paths.get("moved2", "renamed2.c").toString(), "67dfbe26", new_content}, 458 new String[] {Paths.get("moved2", "renamed2.c").toString(), "1086eaf5", new_content}, 459 460 new String[] {Paths.get("moved", "renamed2.c").toString(), "67dfbe26", new_content}, 461 new String[] {Paths.get("moved", "renamed2.c").toString(), "1086eaf5", new_content}, 462 463 new String[] {Paths.get("moved", "renamed.c").toString(), "1086eaf5", new_content}, 464 465 // old content (before revision b6413947 inclusively) 466 new String[] {Paths.get("moved2", "renamed2.c").toString(), "b6413947", old_content}, 467 new String[] {Paths.get("moved2", "renamed2.c").toString(), "ce4c98ec", old_content}, 468 new String[] {Paths.get("moved", "renamed2.c").toString(), "b6413947", old_content}, 469 new String[] {Paths.get("moved", "renamed2.c").toString(), "ce4c98ec", old_content}, 470 new String[] {Paths.get("moved", "renamed.c").toString(), "b6413947", old_content}, 471 new String[] {Paths.get("moved", "renamed.c").toString(), "ce4c98ec", old_content}, 472 new String[] {Paths.get("renamed.c").toString(), "ce4c98ec", old_content} 473 ); 474 475 for (String[] params : tests) { 476 runRenamedTest(params[0], params[1], params[2]); 477 } 478 } 479 480 /** 481 * Test that {@code getHistoryGet()} returns historical contents of renamed 482 * file. 483 * @see #testRenamedFiles for git repo structure info 484 */ 485 @Test testGetHistoryForNonExistentRenamed()486 void testGetHistoryForNonExistentRenamed() throws Exception { 487 final List<String[]> tests = Arrays.asList( 488 new String[] {Paths.get("moved", "renamed2.c").toString(), "84599b3c"}, 489 490 new String[] {Paths.get("moved", "renamed.c").toString(), "84599b3c"}, 491 new String[] {Paths.get("moved", "renamed.c").toString(), "67dfbe26"}, 492 493 new String[] {Paths.get("renamed.c").toString(), "84599b3c"}, 494 new String[] {Paths.get("renamed.c").toString(), "67dfbe26"}, 495 new String[] {Paths.get("renamed.c").toString(), "1086eaf5"}, 496 new String[] {Paths.get("renamed.c").toString(), "b6413947"} 497 ); 498 499 for (String[] params : tests) { 500 runRenamedTest(params[0], params[1], null); 501 } 502 } 503 runRenamedTest(String fname, String cset, String content)504 private void runRenamedTest(String fname, String cset, String content) throws Exception { 505 File root = new File(repository.getSourceRoot(), "git"); 506 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 507 byte[] buffer = new byte[4096]; 508 509 InputStream input = gitrepo.getHistoryGet(root.getCanonicalPath(), fname, cset); 510 if (content == null) { 511 assertNull(input, String.format("Expecting the revision %s for file %s does not exist", cset, fname)); 512 } else { 513 assertNotNull(input, String.format("Expecting the revision %s for file %s does exist", cset, fname)); 514 int len = input.read(buffer); 515 assertNotEquals(-1, len, String.format("Expecting the revision %s for file %s does have some content", cset, fname)); 516 String str = new String(buffer, 0, len); 517 assertEquals(content, str, String.format("Expecting the revision %s for file %s does match the expected content", cset, fname)); 518 } 519 } 520 521 @ParameterizedTest 522 @ValueSource(booleans = {true, false}) testHistory(boolean renamedHandling)523 void testHistory(boolean renamedHandling) throws Exception { 524 RuntimeEnvironment.getInstance().setHandleHistoryOfRenamedFiles(renamedHandling); 525 File root = new File(repository.getSourceRoot(), "git"); 526 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 527 528 List<HistoryEntry> entries = List.of( 529 new HistoryEntry("84599b3c", new Date(1485438707000L), 530 "Kryštof Tulinger <krystof.tulinger@oracle.com>", 531 " renaming directories\n\n", true, 532 Set.of(File.separator + Paths.get("git", "moved2", "renamed2.c"))), 533 new HistoryEntry("67dfbe26", new Date(1485263397000L), 534 "Kryštof Tulinger <krystof.tulinger@oracle.com>", 535 " renaming renamed -> renamed2\n\n", true, 536 Set.of(File.separator + Paths.get("git", "moved", "renamed2.c"))), 537 new HistoryEntry("1086eaf5", new Date(1485263368000L), 538 "Kryštof Tulinger <krystof.tulinger@oracle.com>", 539 " adding some lines into renamed.c\n\n", true, 540 Set.of(File.separator + Paths.get("git", "moved", "renamed.c"))), 541 new HistoryEntry("b6413947", new Date(1485263264000L), 542 "Kryštof Tulinger <krystof.tulinger@oracle.com>", 543 " moved renamed.c to new location\n\n", true, 544 Set.of(File.separator + Paths.get("git", "moved", "renamed.c"))), 545 new HistoryEntry("ce4c98ec", new Date(1485263232000L), // start in the sub-test below 546 "Kryštof Tulinger <krystof.tulinger@oracle.com>", 547 " adding simple file for renamed file testing\n\n", true, 548 Set.of(File.separator + Paths.get("git", "renamed.c"))), 549 new HistoryEntry("aa35c258", new Date(1218571965000L), 550 "Trond Norbye <trond@sunray-srv.norbye.org>", 551 " Add lint make target and fix lint warnings\n\n", true, 552 Set.of(File.separator + Paths.get("git", "Makefile"), 553 File.separator + Paths.get("git", "main.c"))), 554 new HistoryEntry("84821564", new Date(1218571643000L), 555 "Trond Norbye <trond@sunray-srv.norbye.org>", 556 " Add the result of a make on Solaris x86\n\n", true, 557 Set.of(File.separator + Paths.get("git", "main.o"), 558 File.separator + Paths.get("git", "testsprog"))), 559 new HistoryEntry("bb74b7e8", new Date(1218571573000L), 560 "Trond Norbye <trond@sunray-srv.norbye.org>", 561 " Added a small test program\n\n", true, 562 Set.of(File.separator + Paths.get("git", "Makefile"), 563 File.separator + Paths.get("git", "header.h"), 564 File.separator + Paths.get("git", "main.c")))); 565 566 List<String> expectedRenamedFiles = List.of( 567 File.separator + Paths.get("git", "moved", "renamed2.c"), 568 File.separator + Paths.get("git", "moved2", "renamed2.c"), 569 File.separator + Paths.get("git", "moved", "renamed.c")); 570 571 History history = gitrepo.getHistory(root); 572 assertNotNull(history); 573 assertNotNull(history.getHistoryEntries()); 574 assertEquals(entries.size(), history.getHistoryEntries().size()); 575 576 History expectedHistory; 577 if (renamedHandling) { 578 expectedHistory = new History(entries, expectedRenamedFiles); 579 } else { 580 expectedHistory = new History(entries); 581 } 582 assertEquals(expectedHistory, history); 583 584 // Retry with start changeset. 585 history = gitrepo.getHistory(root, "ce4c98ec"); 586 assertNotNull(history); 587 assertNotNull(history.getHistoryEntries()); 588 assertEquals(4, history.getHistoryEntries().size()); 589 if (renamedHandling) { 590 expectedHistory = new History(entries.subList(0, 4), expectedRenamedFiles); 591 assertEquals(expectedRenamedFiles.size(), history.getRenamedFiles().size()); 592 } else { 593 expectedHistory = new History(entries.subList(0, 4)); 594 assertEquals(0, history.getRenamedFiles().size()); 595 } 596 assertEquals(expectedHistory, history); 597 } 598 599 @Test testSingleHistory()600 void testSingleHistory() throws Exception { 601 RuntimeEnvironment.getInstance().setHandleHistoryOfRenamedFiles(false); 602 File root = new File(repository.getSourceRoot(), "git"); 603 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 604 605 History history = gitrepo.getHistory(new File(root.getAbsolutePath(), 606 Paths.get("moved2", "renamed2.c").toString())); 607 assertNotNull(history); 608 assertNotNull(history.getHistoryEntries()); 609 assertEquals(1, history.getHistoryEntries().size()); 610 assertEquals("84599b3c", history.getHistoryEntries().get(0).getRevision()); 611 } 612 613 @Test testRenamedSingleHistory()614 void testRenamedSingleHistory() throws Exception { 615 RuntimeEnvironment.getInstance().setHandleHistoryOfRenamedFiles(true); 616 File root = new File(repository.getSourceRoot(), "git"); 617 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 618 619 History history = gitrepo.getHistory(new File(root.getAbsolutePath(), "moved2/renamed2.c")); 620 assertNotNull(history); 621 assertNotNull(history.getHistoryEntries()); 622 assertEquals(5, history.getHistoryEntries().size()); 623 624 assertNotNull(history.getRenamedFiles()); 625 assertEquals(0, history.getRenamedFiles().size()); 626 627 assertEquals("84599b3c", history.getHistoryEntries().get(0).getRevision()); 628 assertEquals("67dfbe26", history.getHistoryEntries().get(1).getRevision()); 629 assertEquals("1086eaf5", history.getHistoryEntries().get(2).getRevision()); 630 assertEquals("b6413947", history.getHistoryEntries().get(3).getRevision()); 631 assertEquals("ce4c98ec", history.getHistoryEntries().get(4).getRevision()); 632 } 633 634 @Test testGetHistorySinceTillNullNull()635 void testGetHistorySinceTillNullNull() throws Exception { 636 File root = new File(repository.getSourceRoot(), "git"); 637 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 638 639 History history = gitrepo.getHistory(root, null, null); 640 assertNotNull(history); 641 assertNotNull(history.getHistoryEntries()); 642 assertEquals(8, history.getHistoryEntries().size()); 643 List<String> revisions = history.getHistoryEntries().stream().map(HistoryEntry::getRevision). 644 collect(Collectors.toList()); 645 assertEquals(List.of("84599b3c", "67dfbe26", "1086eaf5", "b6413947", "ce4c98ec", "aa35c258", "84821564", 646 "bb74b7e8"), revisions); 647 } 648 649 @Test testGetHistorySinceTillNullRev()650 void testGetHistorySinceTillNullRev() throws Exception { 651 File root = new File(repository.getSourceRoot(), "git"); 652 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 653 654 History history = gitrepo.getHistory(root, null, "ce4c98ec"); 655 assertNotNull(history); 656 assertNotNull(history.getHistoryEntries()); 657 assertEquals(4, history.getHistoryEntries().size()); 658 List<String> revisions = history.getHistoryEntries().stream().map(HistoryEntry::getRevision). 659 collect(Collectors.toList()); 660 assertEquals(List.of("ce4c98ec", "aa35c258", "84821564", "bb74b7e8"), revisions); 661 } 662 663 @Test testGetHistorySinceTillRevNull()664 void testGetHistorySinceTillRevNull() throws Exception { 665 File root = new File(repository.getSourceRoot(), "git"); 666 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 667 668 History history = gitrepo.getHistory(root, "aa35c258", null); 669 assertNotNull(history); 670 assertNotNull(history.getHistoryEntries()); 671 assertEquals(5, history.getHistoryEntries().size()); 672 List<String> revisions = history.getHistoryEntries().stream().map(HistoryEntry::getRevision). 673 collect(Collectors.toList()); 674 assertEquals(List.of("84599b3c", "67dfbe26", "1086eaf5", "b6413947", "ce4c98ec"), revisions); 675 } 676 677 @Test testGetHistorySinceTillRevRev()678 void testGetHistorySinceTillRevRev() throws Exception { 679 File root = new File(repository.getSourceRoot(), "git"); 680 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(root); 681 682 History history = gitrepo.getHistory(root, "ce4c98ec", "1086eaf5"); 683 assertNotNull(history); 684 assertNotNull(history.getHistoryEntries()); 685 assertEquals(2, history.getHistoryEntries().size()); 686 List<String> revisions = history.getHistoryEntries().stream().map(HistoryEntry::getRevision). 687 collect(Collectors.toList()); 688 assertEquals(List.of("1086eaf5", "b6413947"), revisions); 689 } 690 691 @Test testBuildTagListEmpty()692 void testBuildTagListEmpty() throws Exception { 693 File root = new File(repository.getSourceRoot(), "git"); 694 // Clone under source root to avoid problems with prohibited symlinks. 695 File localPath = new File(repository.getSourceRoot(), "testBuildTagListEmpty"); 696 String cloneUrl = root.toURI().toString(); 697 try (Git gitClone = Git.cloneRepository() 698 .setURI(cloneUrl) 699 .setDirectory(localPath) 700 .call()) { 701 702 File cloneRoot = gitClone.getRepository().getWorkTree(); 703 704 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(cloneRoot); 705 gitrepo.buildTagList(new File(gitrepo.getDirectoryName()), CommandTimeoutType.INDEXER); 706 assertEquals(0, gitrepo.getTagList().size()); 707 708 removeRecursive(cloneRoot); 709 } 710 } 711 712 @Test testBuildTagListMultipleTags()713 void testBuildTagListMultipleTags() throws Exception { 714 File root = new File(repository.getSourceRoot(), "git"); 715 // Clone under source root to avoid problems with prohibited symlinks. 716 File localPath = new File(repository.getSourceRoot(), "testBuildTagListMultipleTags"); 717 String cloneUrl = root.toURI().toString(); 718 try (Git gitClone = Git.cloneRepository() 719 .setURI(cloneUrl) 720 .setDirectory(localPath) 721 .call()) { 722 723 File cloneRoot = gitClone.getRepository().getWorkTree(); 724 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(cloneRoot); 725 726 // Tag the HEAD first. 727 Ref ref = gitClone.tag().setName("one").call(); 728 assertNotNull(ref); 729 gitrepo.buildTagList(new File(gitrepo.getDirectoryName()), CommandTimeoutType.INDEXER); 730 Set<TagEntry> tags = gitrepo.getTagList(); 731 assertEquals(1, tags.size()); 732 Date date = new Date((long) (1485438707) * 1000); 733 TagEntry tagEntry = new GitTagEntry("84599b3cccb3eeb5aa9aec64771678d6526bcecb", date, "one"); 734 assertEquals(tagEntry, tags.toArray()[0]); 735 736 // Tag again so that single changeset has multiple tags. 737 ref = gitClone.tag().setName("two").call(); 738 assertNotNull(ref); 739 gitrepo.buildTagList(new File(gitrepo.getDirectoryName()), CommandTimeoutType.INDEXER); 740 tags = gitrepo.getTagList(); 741 assertEquals(1, tags.size()); 742 Set<TagEntry> expectedTags = new TreeSet<>(); 743 date = new Date((long) (1485438707) * 1000); 744 tagEntry = new GitTagEntry("84599b3cccb3eeb5aa9aec64771678d6526bcecb", date, "one, two"); 745 expectedTags.add(tagEntry); 746 assertEquals(expectedTags, tags); 747 748 removeRecursive(cloneRoot); 749 } 750 } 751 752 @Test testBuildTagListNotHead()753 void testBuildTagListNotHead() throws Exception { 754 File root = new File(repository.getSourceRoot(), "git"); 755 // Clone under source root to avoid problems with prohibited symlinks. 756 File localPath = new File(repository.getSourceRoot(), "testBuildTagListNotHead"); 757 String cloneUrl = root.toURI().toString(); 758 try (Git gitClone = Git.cloneRepository() 759 .setURI(cloneUrl) 760 .setDirectory(localPath) 761 .call()) { 762 763 File cloneRoot = gitClone.getRepository().getWorkTree(); 764 GitRepository gitrepo = (GitRepository) RepositoryFactory.getRepository(cloneRoot); 765 766 // Tag specific changeset (not HEAD) and recheck. 767 org.eclipse.jgit.lib.Repository repo = gitClone.getRepository(); 768 RevCommit commit; 769 ObjectId objectId = repo.resolve("b6413947a5"); 770 try (RevWalk walk = new RevWalk(repo)) { 771 commit = walk.parseCommit(objectId); 772 } 773 assertNotNull(commit); 774 Ref ref = gitClone.tag().setName("three").setObjectId(commit).call(); 775 assertNotNull(ref); 776 gitrepo.buildTagList(new File(gitrepo.getDirectoryName()), CommandTimeoutType.INDEXER); 777 Set<TagEntry> tags = gitrepo.getTagList(); 778 assertEquals(1, tags.size()); 779 Date date = new Date((long) (1485263264) * 1000); 780 Set<TagEntry> expectedTags = new TreeSet<>(); 781 TagEntry tagEntry = new GitTagEntry("b6413947a59f481ddc0a05e0d181731233557f6e", date, "three"); 782 expectedTags.add(tagEntry); 783 assertEquals(expectedTags, tags); 784 785 removeRecursive(cloneRoot); 786 } 787 } 788 removeRecursive(final File cloneRoot)789 private void removeRecursive(final File cloneRoot) { 790 try { 791 IOUtils.removeRecursive(cloneRoot.toPath()); 792 } catch (IOException e) { 793 // ignore 794 } 795 } 796 addSubmodule(String submoduleName)797 private String addSubmodule(String submoduleName) throws Exception { 798 // Create new Git repository first. 799 File newRepoFile = new File(repository.getSourceRoot(), submoduleName); 800 Git newRepo = Git.init().setDirectory(newRepoFile).call(); 801 assertNotNull(newRepo); 802 803 // Add this repository as a submodule to the existing Git repository. 804 org.eclipse.jgit.lib.Repository mainRepo = new FileRepositoryBuilder(). 805 setGitDir(Paths.get(repository.getSourceRoot(), "git", Constants.DOT_GIT).toFile()) 806 .build(); 807 String parent = newRepoFile.toPath().toUri().toString(); 808 try (Git git = new Git(mainRepo)) { 809 git.submoduleAdd(). 810 setURI(parent). 811 setPath(submoduleName). 812 call(); 813 } 814 815 return parent; 816 } 817 818 @Test testSubmodule()819 void testSubmodule() throws Exception { 820 String submoduleName = "submodule"; 821 String submoduleOriginPath = addSubmodule(submoduleName); 822 Path submodulePath = Paths.get(repository.getSourceRoot(), "git", submoduleName); 823 824 Repository subRepo = RepositoryFactory.getRepository(submodulePath.toFile()); 825 assertNotNull(subRepo); 826 assertNotNull(subRepo.getParent()); 827 assertEquals(submoduleOriginPath, subRepo.getParent()); 828 829 // Test relative path too. JGit always writes absolute path so overwrite the contents directly. 830 File gitFile = Paths.get(submodulePath.toString(), Constants.DOT_GIT).toFile(); 831 assertTrue(gitFile.isFile()); 832 try (Writer writer = new FileWriter(gitFile)) { 833 writer.write(Constants.GITDIR + ".." + File.separator + Constants.DOT_GIT + 834 File.separator + Constants.MODULES + File.separator + submoduleName); 835 } 836 subRepo = RepositoryFactory.getRepository(submodulePath.toFile()); 837 assertNotNull(subRepo); 838 assertNotNull(subRepo.getParent()); 839 840 removeRecursive(submodulePath.toFile()); 841 } 842 } 843