Skip to content

Commit

Permalink
SONARSCPER-4 Do not fail when a file has no blame information
Browse files Browse the repository at this point in the history
  • Loading branch information
henryju committed Jan 6, 2016
1 parent 2c2fe3d commit da0d87f
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 140 deletions.
104 changes: 68 additions & 36 deletions src/main/java/org/sonar/plugins/scm/perforce/PerforceBlameCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,30 @@
*/
package org.sonar.plugins.scm.perforce;

import com.perforce.p4java.core.file.FileSpecBuilder;
import com.google.common.annotations.VisibleForTesting;
import com.perforce.p4java.core.file.FileSpecOpStatus;
import com.perforce.p4java.core.file.IFileAnnotation;
import com.perforce.p4java.core.file.IFileRevisionData;
import com.perforce.p4java.core.file.IFileSpec;
import com.perforce.p4java.exception.P4JavaException;
import com.perforce.p4java.impl.generic.core.file.FileSpec;
import com.perforce.p4java.option.server.GetFileAnnotationsOptions;
import com.perforce.p4java.option.server.GetRevisionHistoryOptions;
import com.perforce.p4java.server.IOptionsServer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.scm.BlameCommand;
import org.sonar.api.batch.scm.BlameLine;

import java.util.List;
import java.util.Map;

public class PerforceBlameCommand extends BlameCommand {

private static final Logger LOG = LoggerFactory.getLogger(PerforceBlameCommand.class);
Expand All @@ -52,37 +59,64 @@ public void blame(BlameInput input, BlameOutput output) {
PerforceExecutor executor = new PerforceExecutor(config, fs.baseDir());
try {
for (InputFile inputFile : input.filesToBlame()) {
PerforceBlameResult p4Result = new PerforceBlameResult();
List<IFileSpec> fileSpecs = createFileSpec(inputFile);
try {
// Get file annotations
List<IFileAnnotation> fileAnnotations = executor.getServer().getFileAnnotations(fileSpecs,
getFileAnnotationOptions());
blame(inputFile, executor.getServer(), output);
}
} finally {
executor.clean();
}
}

// Process the file annotations as blame lines
p4Result.processBlameLines(fileAnnotations);
@VisibleForTesting
void blame(InputFile inputFile, IOptionsServer server, BlameOutput output) {
IFileSpec fileSpec = createFileSpec(inputFile);
List<IFileAnnotation> fileAnnotations;
List<IFileRevisionData> revisions;
try {
// Get file annotations
List<IFileSpec> fileSpecs = Arrays.asList(fileSpec);
fileAnnotations = server.getFileAnnotations(fileSpecs, getFileAnnotationOptions());
if (fileAnnotations.size() == 1 && fileAnnotations.get(0).getDepotPath() == null) {
LOG.debug("File " + inputFile + " is not submitted. Skipping it.");
return;
}
// Get revision history
Map<IFileSpec, List<IFileRevisionData>> revisionMap = server.getRevisionHistory(fileSpecs, getRevisionHistoryOptions());
Entry<IFileSpec, List<IFileRevisionData>> singleEntry = revisionMap.entrySet().iterator().next();
IFileSpec resultFileSpec = singleEntry.getKey();
if (!FileSpecOpStatus.VALID.equals(resultFileSpec.getOpStatus()) && !FileSpecOpStatus.INFO.equals(resultFileSpec.getOpStatus())) {
String statusMessage = resultFileSpec.getStatusMessage();
LOG.debug("Unable to get revisions of file " + inputFile + " [" + statusMessage + "]. Skipping it.");
return;
}
revisions = singleEntry.getValue();
} catch (P4JavaException e) {
throw new IllegalStateException(e.getLocalizedMessage(), e);
}

// Get revision history
Map<IFileSpec, List<IFileRevisionData>> revisionMap = executor.getServer().getRevisionHistory(fileSpecs,
getRevisionHistoryOptions());
computeBlame(inputFile, output, fileAnnotations, revisions);
}

// Process the revision data map
p4Result.processRevisionHistory(revisionMap);
} catch (P4JavaException e) {
throw new IllegalStateException(e.getLocalizedMessage(), e);
}
private void computeBlame(InputFile inputFile, BlameOutput output, List<IFileAnnotation> fileAnnotations, List<IFileRevisionData> revisions) {
Map<Integer, Date> changelistDates = new HashMap<>();
Map<Integer, String> changelistAuthors = new HashMap<>();
for (IFileRevisionData revision : revisions) {
changelistDates.put(revision.getChangelistId(), revision.getDate());
changelistAuthors.put(revision.getChangelistId(), revision.getUserName());
}

// Combine the results
List<BlameLine> lines = p4Result.createBlameLines();
if (lines.size() == (inputFile.lines() - 1)) {
// SONARPLUGINS-3097 Perforce do not report blame on last empty line
lines.add(lines.get(lines.size() - 1));
}
output.blameResult(inputFile, lines);
}
} finally {
executor.clean();
List<BlameLine> lines = new ArrayList<>();
for (IFileAnnotation fileAnnotation : fileAnnotations) {
int lowerChangelistId = fileAnnotation.getLower();
lines.add(new BlameLine()
.revision(String.valueOf(lowerChangelistId))
.date(changelistDates.get(lowerChangelistId))
.author(changelistAuthors.get(lowerChangelistId)));
}
if (lines.size() == (inputFile.lines() - 1)) {
// SONARPLUGINS-3097 Perforce do not report blame on last empty line
lines.add(lines.get(lines.size() - 1));
}
output.blameResult(inputFile, lines);
}

/**
Expand Down Expand Up @@ -111,13 +145,11 @@ private static GetFileAnnotationsOptions getFileAnnotationOptions() {
* Creates file spec for the specified file taking into an account that we are interested in a revision that we have
* in the current client workspace.
* @param inputFile file to create file spec for
* @return list of file specs containing the only one spec for the specified file.
*/
private static List<IFileSpec> createFileSpec(InputFile inputFile) {
List<IFileSpec> fileSpecs = FileSpecBuilder
.makeFileSpecList(new String[] {PerforceExecutor.encodeWildcards(inputFile.absolutePath())});
fileSpecs.get(0).setEndRevision(IFileSpec.HAVE_REVISION);
return fileSpecs;
private static IFileSpec createFileSpec(InputFile inputFile) {
IFileSpec fileSpec = new FileSpec(PerforceExecutor.encodeWildcards(inputFile.absolutePath()));
fileSpec.setEndRevision(IFileSpec.HAVE_REVISION);
return fileSpec;
}

}
101 changes: 0 additions & 101 deletions src/main/java/org/sonar/plugins/scm/perforce/PerforceBlameResult.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ public void receivedServerMessage(int key, int genericCode, int severityCode, St

@Override
public void receivedServerInfoLine(int key, String infoLine) {
LOG.info(infoLine);
LOG.debug(infoLine);
}

@Override
Expand All @@ -214,12 +214,12 @@ public void receivedServerErrorLine(int key, String errorLine) {

@Override
public void issuingServerCommand(int key, String command) {
LOG.info(command);
LOG.debug(command);
}

@Override
public void completedServerCommand(int key, long millisecsTaken) {
LOG.info("Command completed in " + millisecsTaken + "ms");
LOG.debug("Command completed in " + millisecsTaken + "ms");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* SonarQube :: Plugins :: SCM :: Perforce
* Copyright (C) 2014 SonarSource
* [email protected]
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.plugins.scm.perforce;

import com.perforce.p4java.core.file.FileSpecOpStatus;
import com.perforce.p4java.core.file.IFileAnnotation;
import com.perforce.p4java.core.file.IFileRevisionData;
import com.perforce.p4java.core.file.IFileSpec;
import com.perforce.p4java.option.server.GetFileAnnotationsOptions;
import com.perforce.p4java.option.server.GetRevisionHistoryOptions;
import com.perforce.p4java.server.IOptionsServer;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.scm.BlameCommand.BlameOutput;
import org.sonar.api.batch.scm.BlameLine;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyList;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

public class PerforceBlameCommandTest {

@Test
public void testBlameUnSubmittedFile() throws Exception {
BlameOutput blameOutput = mock(BlameOutput.class);
IOptionsServer server = mock(IOptionsServer.class);
PerforceBlameCommand command = new PerforceBlameCommand(mock(PerforceConfiguration.class));

IFileAnnotation annotation = mock(IFileAnnotation.class);
when(annotation.getDepotPath()).thenReturn(null);

when(server.getFileAnnotations(anyList(), any(GetFileAnnotationsOptions.class))).thenReturn(Arrays.asList(annotation));

command.blame(mock(InputFile.class), server, blameOutput);

verifyZeroInteractions(blameOutput);
}

@Test
public void testBlameSubmittedFile() throws Exception {
BlameOutput blameOutput = mock(BlameOutput.class);
IOptionsServer server = mock(IOptionsServer.class);
PerforceBlameCommand command = new PerforceBlameCommand(mock(PerforceConfiguration.class));

IFileAnnotation annotation = mock(IFileAnnotation.class);
when(annotation.getDepotPath()).thenReturn("foo/bar/src/Foo.java");
when(annotation.getLower()).thenReturn(3);

when(server.getFileAnnotations(anyList(), any(GetFileAnnotationsOptions.class))).thenReturn(Arrays.asList(annotation));

Map<IFileSpec, List<IFileRevisionData>> result = new HashMap<>();
IFileSpec fileSpecResult = mock(IFileSpec.class);
when(fileSpecResult.getOpStatus()).thenReturn(FileSpecOpStatus.VALID);
IFileRevisionData revision3 = mock(IFileRevisionData.class);
when(revision3.getChangelistId()).thenReturn(3);
Date date = new Date();
when(revision3.getDate()).thenReturn(date);
when(revision3.getUserName()).thenReturn("jhenry");
result.put(fileSpecResult, Arrays.asList(revision3));

when(server.getRevisionHistory(anyList(), any(GetRevisionHistoryOptions.class))).thenReturn(result);

InputFile inputFile = mock(InputFile.class);
command.blame(inputFile, server, blameOutput);

verify(blameOutput).blameResult(inputFile, Arrays.asList(new BlameLine().revision("3").date(date).author("jhenry")));
}

}

0 comments on commit da0d87f

Please sign in to comment.