Plugin for static code analysis pull request (Server API)

I’m attempting to automate the static code analysis for created pull requests. Is there a way of getting diff on a specific file in the pull request via Server API?

Like this REST API request:

http://bitbucket.com/rest/api/1.0/projects/PROJ/repos/CODE/pull-requests/1/diff/path/to/file/AssemblyInfo.cs

Hi @araxnid,
The REST API that you are looking at is for Bitbucket cloud. The APIs to stream diff for Bitbucket server are as follows.
REST API: https://docs.atlassian.com/bitbucket-server/rest/6.4.0/bitbucket-rest.html#idp286
Java API: https://docs.atlassian.com/bitbucket-server/javadoc/6.4.0/api/reference/com/atlassian/bitbucket/pull/PullRequestService.html#streamDiff(com.atlassian.bitbucket.pull.PullRequestDiffRequest,%20com.atlassian.bitbucket.content.DiffContentCallback)

Regarding static code analysis, have you considered using Bitbucket code insights to integrate third-party tools with bitbucket?

Thanks,
Justin

Hi, @jthomas !
Thanks for your answer!

If it doesn’t difficult, can you show some example of how to call streamDiff?
It’s not completely clear what implementation is needed for DiffContentCallback.

Regarding static code analysis, have you considered using Bitbucket code insights to integrate third-party tools with bitbucket?

Unfortunately not, for us required custom code analysis.

Hi @araxnid,
Code insights is for custom code analysis. The feature is simply an API on the Bitbucket Server side that will accept a report and annotations on lines in the diff. It doesn’t do any analysis itself, that all has to be done by your own tool. Its simply a way for your tool to display this information on the pull request.
Once you’ve done your custom code analysis, you can create an ‘insight report’ and attach annotations to that report.
This tutorial might help with the code insights.

Hello, @khughes

Thanks for tutorial, but i really need to do custom analysis. My plugin will be integrated into our test system. This plugin will be call some rest api services base on code diff in pull request.

class FullDiffContentCallback extends AbstractDiffContentCallback {
    private final StringBuffer buffer;
    public DiffSegmentType currentSegmentType;
    public FullDiffContentCallback(StringBuffer buffer) {       
        this.buffer = buffer;
        currentSegmentType = null;
    }

    @Override
    public void onDiffStart(@Nullable Path src, @Nullable Path dst) throws IOException {
        String pathToUse = (src == null ? dst.toString() : src.toString());      
        buffer.append("<a id=\"").append(pathToUse.replace("/", "").replace(" ", "")).append("\"></a>\n");

        if (src == null) {
            buffer.append("<div style=\"border: 1px solid #ccc;margin: 10px 0;\">\n");
            buffer.append("<h4 style=\"font-family: verdana, arial, helvetica, sans-serif;font-size: 10pt;padding: 8px;background: rgb(49, 104, 49);color: #fff;margin: 0;\">Added: ").append(escapeHtml(dst.toString())).append("</h4>\n");
        } else if (dst == null) {
            buffer.append("<div style=\"border: 1px solid #ccc;margin: 10px 0;\">\n");
            buffer.append("<h4 style=\"font-family: verdana, arial, helvetica, sans-serif;font-size: 10pt;padding: 8px;background: rgb(129, 32, 13);color: #fff;margin: 0;\">");
            buffer.append("Deleted: ").append(escapeHtml(src.toString())).append("</h4>\n");
        } else if (!src.equals(dst)) {
            buffer.append("<div style=\"border: 1px solid #ccc;margin: 10px 0;\">\n");
            buffer.append("<h4 style=\"font-family: verdana, arial, helvetica, sans-serif;font-size: 10pt;padding: 8px;background: #369;color: #fff;margin: 0;\">");
            buffer.append("Renamed: ");
            buffer.append(escapeHtml(src.toString()));
            buffer.append(" => ");
            buffer.append(escapeHtml(dst.toString()));
            buffer.append("</h4>\n");
        } else {
            buffer.append("<div style=\"border: 1px solid #ccc;margin: 10px 0;\">\n");
            buffer.append("<h4 style=\"font-family: verdana, arial, helvetica, sans-serif;font-size: 10pt;padding: 8px;background: #369;color: #fff;margin: 0;\">");
            buffer.append("Modified: ").append(escapeHtml(dst.toString())).append("</h4>\n");
        }
        buffer.append("<pre style=\"line-height: 1.2em;margin: 0;width: 100%;background: #eee;overflow: auto;\">");
        buffer.append("<span style=\"display: block;\"><span style=\"color: #888;background: #fff;display: block;padding: 0 10px 10px 10px;\">\n");
        buffer.append("--- ").append(src != null ? escapeHtml(src.toString()) : "/dev/null").append("\n");
        buffer.append("+++ ").append(dst != null ? escapeHtml(dst.toString()) : "/dev/null").append("\n");
    }

    @Override
    public void onBinary(@Nullable Path src, @Nullable Path dst) throws IOException {
        String pathToUse = (src == null ? dst.toString() : src.toString());      
        buffer.append("<a id=\"").append(pathToUse).append("\"></a>\n");

        if (src == null) {
            buffer.append("<div style=\"border: 1px solid #ccc;margin: 10px 0;\">\n");
            buffer.append("<h4 style=\"font-family: verdana, arial, helvetica, sans-serif;font-size: 10pt;padding: 8px;background: rgb(49, 104, 49);color: #fff;margin: 0;\">");
            buffer.append("Added (binary): ").append(escapeHtml(dst.toString())).append("</h4>");
        } else if (dst == null) {
            buffer.append("<div style=\"border: 1px solid #ccc;margin: 10px 0;\">\n");
            buffer.append("<h4 style=\"font-family: verdana, arial, helvetica, sans-serif;font-size: 10pt;padding: 8px;background: rgb(129, 32, 13);color: #fff;margin: 0;\">");
            buffer.append("Deleted (binary): ").append(escapeHtml(src.toString())).append("</h4>");
        } else if (!src.equals(dst)) {
            buffer.append("<div style=\"border: 1px solid #ccc;margin: 10px 0;\">\n");
            buffer.append("<h4 style=\"font-family: verdana, arial, helvetica, sans-serif;font-size: 10pt;padding: 8px;background: #369;color: #fff;margin: 0;\">");
            buffer.append("Renamed (binary): ");
            buffer.append(escapeHtml(src.toString()));
            buffer.append(" => ");
            buffer.append(escapeHtml(dst.toString()));
        } else {
            buffer.append("<div style=\"border: 1px solid #ccc;margin: 10px 0;\">\n");
            buffer.append("<h4 style=\"font-family: verdana, arial, helvetica, sans-serif;font-size: 10pt;padding: 8px;background: #369;color: #fff;margin: 0;\">");
            buffer.append("Modified (binary): ").append(escapeHtml(dst.toString())).append("</h4>");
        }
        buffer.append("</div>");
    }

    @Override
    public void onDiffEnd(boolean truncated) throws IOException {
        if (truncated) {
            // TODO handle truncated diffs
            // buffer.append("<span>... diff truncated ...</span>");
        }
        buffer.append("</span></pre></div>");
    }


    @Override
    public void onHunkStart(int srcLine, int srcSpan, int dstLine, int dstSpan) throws IOException {
        buffer.append("<span style=\"display: block;color: #888;background: #fff;\">@@ ");
        buffer.append("-").append(srcLine).append(",").append(srcSpan).append(" +").append(dstLine).append(",").append(dstSpan);
        buffer.append("@@</span>\n");
    }

    @Override
    public void onHunkEnd(boolean truncated) throws IOException {
        if (truncated) {
            // TODO handle truncated hunks
            // buffer.append("<span>... hunk truncated ...</span>");
        }
    }

    @Override
    public void onSegmentStart(@Nonnull DiffSegmentType diffSegmentType) throws IOException {
        this.currentSegmentType = diffSegmentType;
    }

    @Override
    public void onSegmentLine(@Nonnull String line, @Nullable ConflictMarker marker, boolean truncated) throws IOException {            
        if (currentSegmentType == DiffSegmentType.CONTEXT) {
            buffer.append("<span style=\"display: block;\">&nbsp;");
            buffer.append(escapeHtml(line));
            buffer.append("</span>\n");
        } else if (currentSegmentType == DiffSegmentType.ADDED) {
            buffer.append("<ins style=\"background: #dfd;text-decoration: none;display: block;\">+");
            buffer.append(escapeHtml(line));
            buffer.append("</ins>");
        } else if (currentSegmentType == DiffSegmentType.REMOVED) {
            buffer.append("<del style=\"background: #fdd;text-decoration: none;display: block;\">-");
            buffer.append(escapeHtml(line));
            buffer.append("</del>");
        }
    }

    @Override
    public void onSegmentEnd(boolean truncated) throws IOException {
        currentSegmentType = null;
    }
}

Get example from this source