Skip to content

Commit b1d785b

Browse files
author
Vladimir Kotal
authored
use JGit for annotations (#3505)
1 parent 49f018c commit b1d785b

File tree

3 files changed

+76
-214
lines changed

3 files changed

+76
-214
lines changed

opengrok-indexer/src/main/java/org/opengrok/indexer/history/GitAnnotationParser.java

Lines changed: 0 additions & 91 deletions
This file was deleted.

opengrok-indexer/src/main/java/org/opengrok/indexer/history/GitRepository.java

Lines changed: 75 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,17 @@
5151
import java.util.regex.Matcher;
5252
import java.util.regex.Pattern;
5353

54+
import org.eclipse.jgit.api.BlameCommand;
5455
import org.eclipse.jgit.api.Git;
5556
import org.eclipse.jgit.api.errors.GitAPIException;
57+
import org.eclipse.jgit.blame.BlameResult;
58+
import org.eclipse.jgit.diff.RawText;
59+
import org.eclipse.jgit.diff.RawTextComparator;
5660
import org.eclipse.jgit.lib.Constants;
5761
import org.eclipse.jgit.lib.ObjectId;
5862
import org.eclipse.jgit.lib.ObjectLoader;
5963
import org.eclipse.jgit.lib.ObjectReader;
64+
import org.eclipse.jgit.lib.PersonIdent;
6065
import org.eclipse.jgit.lib.Ref;
6166
import org.eclipse.jgit.revwalk.RevCommit;
6267
import org.eclipse.jgit.revwalk.RevTree;
@@ -70,7 +75,6 @@
7075
import org.opengrok.indexer.configuration.RuntimeEnvironment;
7176
import org.opengrok.indexer.logger.LoggerFactory;
7277
import org.opengrok.indexer.util.Executor;
73-
import org.opengrok.indexer.util.HeadHandler;
7478
import org.opengrok.indexer.util.LazilyInstantiate;
7579
import org.opengrok.indexer.util.Version;
7680

@@ -125,6 +129,8 @@ public class GitRepository extends Repository {
125129
*/
126130
private static final LazilyInstantiate<Boolean> GIT_IS_WORKING = LazilyInstantiate.using(GitRepository::isGitWorking);
127131

132+
public static final int GIT_ABBREV_LEN = 8;
133+
128134
public GitRepository() {
129135
type = "git";
130136
/*
@@ -227,6 +233,15 @@ Executor getRenamedFilesExecutor(final File file, String sinceRevision) throws I
227233
return new Executor(cmd, new File(getDirectoryName()), sinceRevision != null);
228234
}
229235

236+
/**
237+
* Be careful, git uses only forward slashes in its command and output (not in file path).
238+
* Using backslashes together with git show will get empty output and 0 status code.
239+
* @return string with separator characters replaced with forward slash
240+
*/
241+
private static String getGitFilePath(String filePath) {
242+
return filePath.replace(File.separatorChar, '/');
243+
}
244+
230245
/**
231246
* Try to get file contents for given revision.
232247
*
@@ -241,15 +256,10 @@ private HistoryRevResult getHistoryRev(OutputStream out, String fullpath, String
241256
HistoryRevResult result = new HistoryRevResult();
242257
File directory = new File(getDirectoryName());
243258

244-
/*
245-
* Be careful, git uses only forward slashes in its command and output (not in file path).
246-
* Using backslashes together with git show will get empty output and 0 status code.
247-
*/
248259
String filename;
249260
result.success = false;
250261
try {
251-
filename = Paths.get(getCanonicalDirectoryName()).relativize(
252-
Paths.get(fullpath)).toString().replace(File.separatorChar, '/');
262+
filename = getGitFilePath(Paths.get(getCanonicalDirectoryName()).relativize(Paths.get(fullpath)).toString());
253263
} catch (IOException e) {
254264
LOGGER.log(Level.WARNING, String.format("Failed to relativize '%s' in for repository '%s'",
255265
fullpath, directory), e);
@@ -447,37 +457,6 @@ String findOriginalName(String fullpath, String changeset)
447457
return originalFile;
448458
}
449459

450-
/**
451-
* Get first revision of given file without following renames.
452-
* @param fullpath file path to get first revision of
453-
*/
454-
private String getFirstRevision(String fullpath) {
455-
String[] argv = {
456-
ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK),
457-
"rev-list",
458-
"--reverse",
459-
"HEAD",
460-
"--",
461-
fullpath
462-
};
463-
464-
Executor executor = new Executor(Arrays.asList(argv), new File(getDirectoryName()),
465-
RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
466-
HeadHandler headHandler = new HeadHandler(1);
467-
int status = executor.exec(false, headHandler);
468-
469-
String line;
470-
if (headHandler.count() > 0 && (line = headHandler.get(0)) != null) {
471-
return line.trim();
472-
}
473-
474-
LOGGER.log(Level.WARNING,
475-
"Failed to get first revision for: \"{0}\" Exit code: {1}",
476-
new Object[]{fullpath, String.valueOf(status)});
477-
478-
return null;
479-
}
480-
481460
/**
482461
* Annotate the specified file/revision.
483462
*
@@ -488,61 +467,72 @@ private String getFirstRevision(String fullpath) {
488467
*/
489468
@Override
490469
public Annotation annotate(File file, String revision) throws IOException {
491-
List<String> cmd = new ArrayList<>();
492-
ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
493-
cmd.add(RepoCommand);
494-
cmd.add(BLAME);
495-
cmd.add("-c"); // to get correctly formed changeset IDs
496-
cmd.add(ABBREV_BLAME);
497-
if (revision != null) {
498-
cmd.add(revision);
499-
} else {
500-
// {@code git blame} follows renames by default. If renamed file handling is off, its output would
501-
// contain invalid revisions. Therefore, the revision range needs to be constrained.
502-
if (!isHandleRenamedFiles()) {
503-
String firstRevision = getFirstRevision(file.getAbsolutePath());
504-
if (firstRevision == null) {
505-
return null;
506-
}
507-
cmd.add(firstRevision + "..");
508-
}
470+
String filePath = getPathRelativeToCanonicalRepositoryRoot(file.getCanonicalPath());
471+
472+
if (revision == null) {
473+
revision = getFirstRevision(filePath);
509474
}
510-
cmd.add("--");
511-
cmd.add(getPathRelativeToCanonicalRepositoryRoot(file.getCanonicalPath()));
475+
Annotation annotation = getAnnotation(revision, filePath);
512476

513-
Executor executor = new Executor(cmd, new File(getDirectoryName()),
514-
RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
515-
GitAnnotationParser parser = new GitAnnotationParser(file.getName());
516-
int status = executor.exec(true, parser);
517-
518-
// File might have changed its location if it was renamed.
519-
// Try to lookup its original name and get the annotation again.
520-
if (status != 0 && isHandleRenamedFiles()) {
521-
cmd.clear();
522-
ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
523-
cmd.add(RepoCommand);
524-
cmd.add(BLAME);
525-
cmd.add("-c"); // to get correctly formed changeset IDs
526-
cmd.add(ABBREV_BLAME);
527-
if (revision != null) {
528-
cmd.add(revision);
477+
if (annotation.getRevisions().isEmpty() && isHandleRenamedFiles()) {
478+
// The file might have changed its location if it was renamed.
479+
// Try to lookup its original name and get the annotation again.
480+
String origName = findOriginalName(file.getCanonicalPath(), revision);
481+
if (origName != null) {
482+
annotation = getAnnotation(revision, origName);
529483
}
530-
cmd.add("--");
531-
cmd.add(findOriginalName(file.getCanonicalPath(), revision));
532-
executor = new Executor(cmd, new File(getDirectoryName()),
533-
RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
534-
parser = new GitAnnotationParser(file.getName());
535-
status = executor.exec(true, parser);
536484
}
537485

538-
if (status != 0) {
486+
return annotation;
487+
}
488+
489+
private String getFirstRevision(String filePath) {
490+
String revision = null;
491+
try (org.eclipse.jgit.lib.Repository repository = getJGitRepository(getDirectoryName())) {
492+
Iterable<RevCommit> commits = new Git(repository).log().
493+
addPath(getGitFilePath(filePath)).
494+
setMaxCount(1).
495+
call();
496+
RevCommit commit = commits.iterator().next();
497+
if (commit != null) {
498+
revision = commit.getId().getName();
499+
} else {
500+
LOGGER.log(Level.WARNING, String.format("cannot get first revision of '%s' in repository '%s'",
501+
filePath, getDirectoryName()));
502+
}
503+
} catch (IOException | GitAPIException e) {
539504
LOGGER.log(Level.WARNING,
540-
"Failed to get annotations for: \"{0}\" Exit code: {1}",
541-
new Object[]{file.getAbsolutePath(), String.valueOf(status)});
542-
return null;
505+
String.format("cannot get first revision of '%s' in repository '%s'",
506+
filePath, getDirectoryName()), e);
543507
}
508+
return revision;
509+
}
510+
511+
@NotNull
512+
private Annotation getAnnotation(String revision, String filePath) throws IOException {
513+
Annotation annotation = new Annotation(filePath);
544514

545-
return parser.getAnnotation();
515+
try (org.eclipse.jgit.lib.Repository repository = getJGitRepository(getDirectoryName())) {
516+
BlameCommand blameCommand = new Git(repository).blame().setFilePath(getGitFilePath(filePath));
517+
ObjectId commitId = repository.resolve(revision);
518+
blameCommand.setStartCommit(commitId);
519+
blameCommand.setFollowFileRenames(isHandleRenamedFiles());
520+
final BlameResult result = blameCommand.setTextComparator(RawTextComparator.WS_IGNORE_ALL).call();
521+
if (result != null) {
522+
final RawText rawText = result.getResultContents();
523+
for (int i = 0; i < rawText.size(); i++) {
524+
final PersonIdent sourceAuthor = result.getSourceAuthor(i);
525+
final RevCommit sourceCommit = result.getSourceCommit(i);
526+
annotation.addLine(sourceCommit.getId().abbreviate(GIT_ABBREV_LEN).
527+
name(), sourceAuthor.getName(), true);
528+
}
529+
}
530+
} catch (GitAPIException e) {
531+
LOGGER.log(Level.FINER,
532+
String.format("failed to get annotation for file '%s' in repository '%s' in revision '%s'",
533+
filePath, getDirectoryName(), revision));
534+
}
535+
return annotation;
546536
}
547537

548538
@Override

opengrok-indexer/src/test/java/org/opengrok/indexer/history/GitRepositoryTest.java

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@
1818
*/
1919

2020
/*
21-
* Copyright (c) 2008, 2020, Oracle and/or its affiliates. All rights reserved.
21+
* Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
2222
* Portions Copyright (c) 2017, 2019, Chris Fraire <[email protected]>.
2323
* Portions Copyright (c) 2019, Krystof Tulinger <[email protected]>.
2424
*/
2525
package org.opengrok.indexer.history;
2626

27-
import java.io.ByteArrayInputStream;
2827
import java.io.File;
2928
import java.io.IOException;
3029
import java.io.InputStream;
@@ -246,42 +245,6 @@ public void testDetermineParent() throws Exception {
246245
}
247246
}
248247

249-
/**
250-
* Test of parseAnnotation method, of class GitRepository.
251-
*/
252-
@Test
253-
public void parseAnnotation() throws Exception {
254-
String revId1 = "cd283405560689372626a69d5331c467bce71656";
255-
String revId2 = "30ae764b12039348766291100308556675ca11ab";
256-
String revId3 = "2394823984cde2390345435a9237bd7c25932342";
257-
String author1 = "Author Name";
258-
String author2 = "Author With Long Name";
259-
String author3 = "Author Named Jr.";
260-
String output = revId1 + " file1.ext (" + author1 + " 2005-06-06 16:38:26 -0400 272) \n" +
261-
revId2 + " file2.h (" + author2 + " 2007-09-10 23:02:45 -0400 273) if (some code)\n" +
262-
revId3 + " file2.c (" + author3 + " 2006-09-20 21:47:42 -0700 274) call_function(i);\n";
263-
String fileName = "something.ext";
264-
265-
GitAnnotationParser parser = new GitAnnotationParser(fileName);
266-
parser.processStream(new ByteArrayInputStream(output.getBytes()));
267-
Annotation result = parser.getAnnotation();
268-
269-
assertNotNull(result);
270-
assertEquals(3, result.size());
271-
for (int i = 1; i <= 3; i++) {
272-
assertTrue("isEnabled()", result.isEnabled(i));
273-
}
274-
assertEquals(revId1, result.getRevision(1));
275-
assertEquals(revId2, result.getRevision(2));
276-
assertEquals(revId3, result.getRevision(3));
277-
assertEquals(author1, result.getAuthor(1));
278-
assertEquals(author2, result.getAuthor(2));
279-
assertEquals(author3, result.getAuthor(3));
280-
assertEquals(author2.length(), result.getWidestAuthor());
281-
assertEquals(revId1.length(), result.getWidestRevision());
282-
assertEquals(fileName, result.getFilename());
283-
}
284-
285248
/**
286249
* Test of fileHasAnnotation method, of class GitRepository.
287250
*/

0 commit comments

Comments
 (0)