51
51
import java .util .regex .Matcher ;
52
52
import java .util .regex .Pattern ;
53
53
54
+ import org .eclipse .jgit .api .BlameCommand ;
54
55
import org .eclipse .jgit .api .Git ;
55
56
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 ;
56
60
import org .eclipse .jgit .lib .Constants ;
57
61
import org .eclipse .jgit .lib .ObjectId ;
58
62
import org .eclipse .jgit .lib .ObjectLoader ;
59
63
import org .eclipse .jgit .lib .ObjectReader ;
64
+ import org .eclipse .jgit .lib .PersonIdent ;
60
65
import org .eclipse .jgit .lib .Ref ;
61
66
import org .eclipse .jgit .revwalk .RevCommit ;
62
67
import org .eclipse .jgit .revwalk .RevTree ;
70
75
import org .opengrok .indexer .configuration .RuntimeEnvironment ;
71
76
import org .opengrok .indexer .logger .LoggerFactory ;
72
77
import org .opengrok .indexer .util .Executor ;
73
- import org .opengrok .indexer .util .HeadHandler ;
74
78
import org .opengrok .indexer .util .LazilyInstantiate ;
75
79
import org .opengrok .indexer .util .Version ;
76
80
@@ -125,6 +129,8 @@ public class GitRepository extends Repository {
125
129
*/
126
130
private static final LazilyInstantiate <Boolean > GIT_IS_WORKING = LazilyInstantiate .using (GitRepository ::isGitWorking );
127
131
132
+ public static final int GIT_ABBREV_LEN = 8 ;
133
+
128
134
public GitRepository () {
129
135
type = "git" ;
130
136
/*
@@ -227,6 +233,15 @@ Executor getRenamedFilesExecutor(final File file, String sinceRevision) throws I
227
233
return new Executor (cmd , new File (getDirectoryName ()), sinceRevision != null );
228
234
}
229
235
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
+
230
245
/**
231
246
* Try to get file contents for given revision.
232
247
*
@@ -241,15 +256,10 @@ private HistoryRevResult getHistoryRev(OutputStream out, String fullpath, String
241
256
HistoryRevResult result = new HistoryRevResult ();
242
257
File directory = new File (getDirectoryName ());
243
258
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
- */
248
259
String filename ;
249
260
result .success = false ;
250
261
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 ());
253
263
} catch (IOException e ) {
254
264
LOGGER .log (Level .WARNING , String .format ("Failed to relativize '%s' in for repository '%s'" ,
255
265
fullpath , directory ), e );
@@ -447,37 +457,6 @@ String findOriginalName(String fullpath, String changeset)
447
457
return originalFile ;
448
458
}
449
459
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
-
481
460
/**
482
461
* Annotate the specified file/revision.
483
462
*
@@ -488,61 +467,72 @@ private String getFirstRevision(String fullpath) {
488
467
*/
489
468
@ Override
490
469
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 );
509
474
}
510
- cmd .add ("--" );
511
- cmd .add (getPathRelativeToCanonicalRepositoryRoot (file .getCanonicalPath ()));
475
+ Annotation annotation = getAnnotation (revision , filePath );
512
476
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 );
529
483
}
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 );
536
484
}
537
485
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 ) {
539
504
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 );
543
507
}
508
+ return revision ;
509
+ }
510
+
511
+ @ NotNull
512
+ private Annotation getAnnotation (String revision , String filePath ) throws IOException {
513
+ Annotation annotation = new Annotation (filePath );
544
514
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 ;
546
536
}
547
537
548
538
@ Override
0 commit comments