33import java .io .IOException ;
44import java .nio .file .LinkOption ;
55import java .nio .file .Paths ;
6+ import java .util .Optional ;
7+ import java .util .stream .StreamSupport ;
68
79import org .eclipse .jgit .api .BlameCommand ;
10+ import org .eclipse .jgit .api .Git ;
811import org .eclipse .jgit .api .errors .GitAPIException ;
912import org .eclipse .jgit .api .errors .JGitInternalException ;
1013import org .eclipse .jgit .blame .BlameResult ;
@@ -107,7 +110,11 @@ public Blames blame(final FileLocations locations) {
107110
108111 private String getWorkspacePath () {
109112 try {
110- return Paths .get (workspace .getRemote ()).toAbsolutePath ().normalize ().toRealPath (LinkOption .NOFOLLOW_LINKS ).toString ();
113+ return Paths .get (workspace .getRemote ())
114+ .toAbsolutePath ()
115+ .normalize ()
116+ .toRealPath (LinkOption .NOFOLLOW_LINKS )
117+ .toString ();
111118 }
112119 catch (IOException | InvalidPathException exception ) {
113120 return workspace .getRemote ();
@@ -119,6 +126,7 @@ private String getWorkspacePath() {
119126 */
120127 static class BlameCallback implements RepositoryCallback <Blames > {
121128 private static final long serialVersionUID = 8794666938104738260L ;
129+ private static final int WHOLE_FILE = 0 ;
122130
123131 private final ObjectId headCommit ;
124132 private final String workspacePath ;
@@ -139,7 +147,7 @@ public Blames invoke(final Repository repository, final VirtualChannel channel)
139147 BlameRunner blameRunner = new BlameRunner (repository , headCommit );
140148
141149 for (String file : locations .getRelativePaths ()) {
142- run (file , blameRunner );
150+ run (file , blameRunner , new LastCommitRunner ( repository ) );
143151
144152 if (Thread .interrupted ()) { // Cancel request by user
145153 String message = "Blaming has been interrupted while computing blame information" ;
@@ -164,10 +172,12 @@ public Blames invoke(final Repository repository, final VirtualChannel channel)
164172 * @param fileName
165173 * the file to get the blames for
166174 * @param blameRunner
167- * the runner to invoke Git
175+ * the runner to invoke Git blame
176+ * @param lastCommitRunner
177+ * the runner to find the last commit
168178 */
169179 @ VisibleForTesting
170- void run (final String fileName , final BlameRunner blameRunner ) {
180+ void run (final String fileName , final BlameRunner blameRunner , final LastCommitRunner lastCommitRunner ) {
171181 try {
172182 BlameResult blame = blameRunner .run (fileName );
173183 if (blame == null ) {
@@ -176,27 +186,11 @@ void run(final String fileName, final BlameRunner blameRunner) {
176186 else {
177187 for (int line : locations .getLines (fileName )) {
178188 FileBlame fileBlame = new FileBlame (workspacePath + "/" + fileName );
179- int lineIndex = line - 1 ; // first line is index 0
180- if (lineIndex < blame .getResultContents ().size ()) {
181- PersonIdent who = blame .getSourceAuthor (lineIndex );
182- if (who == null ) {
183- who = blame .getSourceCommitter (lineIndex );
184- }
185- if (who == null ) {
186- blames .logError ("- no author or committer information found for line %d in file %s" ,
187- lineIndex , fileName );
188- }
189- else {
190- fileBlame .setName (line , who .getName ());
191- fileBlame .setEmail (line , who .getEmailAddress ());
192- }
193- RevCommit commit = blame .getSourceCommit (lineIndex );
194- if (commit == null ) {
195- blames .logError ("- no commit ID found for line %d in file %s" , lineIndex , fileName );
196- }
197- else {
198- fileBlame .setCommit (line , commit .getName ());
199- }
189+ if (line <= 0 ) {
190+ fillWithLastCommit (fileName , fileBlame , lastCommitRunner );
191+ }
192+ else if (line <= blame .getResultContents ().size ()) {
193+ fillWithBlameResult (fileName , fileBlame , blame , line );
200194 }
201195 blames .add (fileBlame );
202196 }
@@ -208,6 +202,47 @@ void run(final String fileName, final BlameRunner blameRunner) {
208202 }
209203 blames .logSummary ();
210204 }
205+
206+ private void fillWithBlameResult (final String fileName , final FileBlame fileBlame , final BlameResult blame ,
207+ final int line ) {
208+ int lineIndex = line - 1 ; // first line is index 0
209+ PersonIdent who = blame .getSourceAuthor (lineIndex );
210+ if (who == null ) {
211+ who = blame .getSourceCommitter (lineIndex );
212+ }
213+ if (who == null ) {
214+ blames .logError ("- no author or committer information found for line %d in file %s" ,
215+ lineIndex , fileName );
216+ }
217+ else {
218+ fileBlame .setName (line , who .getName ());
219+ fileBlame .setEmail (line , who .getEmailAddress ());
220+ }
221+ RevCommit commit = blame .getSourceCommit (lineIndex );
222+ if (commit == null ) {
223+ blames .logError ("- no commit ID found for line %d in file %s" , lineIndex , fileName );
224+ }
225+ else {
226+ fileBlame .setCommit (line , commit .getName ());
227+ }
228+ }
229+
230+ private void fillWithLastCommit (final String fileName , final FileBlame fileBlame ,
231+ final LastCommitRunner lastCommitRunner ) throws GitAPIException {
232+ Optional <RevCommit > commit = lastCommitRunner .run (fileName );
233+ if (commit .isPresent ()) {
234+ RevCommit revCommit = commit .get ();
235+ fileBlame .setCommit (WHOLE_FILE , revCommit .getName ());
236+ PersonIdent who = revCommit .getAuthorIdent ();
237+ if (who == null ) {
238+ who = revCommit .getCommitterIdent ();
239+ }
240+ if (who != null ) {
241+ fileBlame .setName (WHOLE_FILE , who .getName ());
242+ fileBlame .setEmail (WHOLE_FILE , who .getEmailAddress ());
243+ }
244+ }
245+ }
211246 }
212247
213248 /**
@@ -230,4 +265,23 @@ BlameResult run(final String fileName) throws GitAPIException {
230265 return blame .call ();
231266 }
232267 }
268+
269+ /**
270+ * Executes the Git log command for a given file.
271+ */
272+ static class LastCommitRunner {
273+ private final Repository repo ;
274+
275+ LastCommitRunner (final Repository repo ) {
276+ this .repo = repo ;
277+ }
278+
279+ Optional <RevCommit > run (final String fileName ) throws GitAPIException {
280+ try (Git git = new Git (repo )) {
281+ Iterable <RevCommit > commits = git .log ().addPath (fileName ).call ();
282+
283+ return StreamSupport .stream (commits .spliterator (), false ).findFirst ();
284+ }
285+ }
286+ }
233287}
0 commit comments