xref: /OpenGrok/opengrok-indexer/src/test/java/org/opengrok/indexer/history/GitRepositoryTest.java (revision 3abe03fea4a3b950b88feeba94d191b5233a8aab)
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