Skip to content

Annotation API #3056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions apiary.apib
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,31 @@ OpenGrok RESTful API documentation. The following endpoints are accessible under

Besides `/suggester` and `/search` endpoints, everything is accessible from `localhost` only.

## Annotation [/annotation{?path}]

### Get annotation for a file [GET]

+ Parameters
+ path (string) - path of file, relative to source root

+ Response 200 (application/json)
+ Body

[
{
"revision": "c55d5891",
"author": "Adam Hornáček",
"description": "changeset: c55d5891\nsummary: Rewrite README.txt to use markdown syntax\nuser: Adam Hornáček <[email protected]>\ndate: Wed Aug 30 17:42:12 CEST 2017",
"version": "1/15"
},
{
"revision": "5e0c6b22",
"author": "Vladimir Kotal",
"description": "changeset: 5e0c6b22\nsummary: bump year\nuser: Vladimir Kotal <[email protected]>\ndate: Thu Jul 18 14:43:01 CEST 2019",
"version": "14/15"
}
]

## Authorization framework reload [/configuration/authorization/reload]

### reloads authorization framework [POST]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import org.opengrok.indexer.util.Color;
import org.opengrok.indexer.util.LazilyInstantiate;
import org.opengrok.indexer.util.RainbowColorGenerator;
import org.opengrok.indexer.web.Util;

import java.io.IOException;
import java.io.StringWriter;
Expand All @@ -53,7 +52,7 @@ public class Annotation {
private static final Logger LOGGER = LoggerFactory.getLogger(Annotation.class);

private final List<Line> lines = new ArrayList<>();
private final Map<String, String> desc = new HashMap<>();
private final Map<String, String> desc = new HashMap<>(); // revision to description
private final Map<String, Integer> fileVersions = new HashMap<>(); // maps revision to file version
private final LazilyInstantiate<Map<String, String>> colors = LazilyInstantiate.using(this::generateColors);
private int widestRevision;
Expand Down Expand Up @@ -163,7 +162,7 @@ void addLine(String revision, String author, boolean enabled) {
}

void addDesc(String revision, String description) {
desc.put(revision, Util.encode(description));
desc.put(revision, description);
}

public String getDesc(String revision) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.opengrok.indexer.logger.LoggerFactory;
import org.opengrok.indexer.util.BufferSink;
import org.opengrok.indexer.util.Executor;
import org.opengrok.indexer.util.HeadHandler;
import org.opengrok.indexer.util.StringUtils;
import org.opengrok.indexer.util.Version;

Expand Down Expand Up @@ -389,31 +390,24 @@ private String getFirstRevision(String fullpath) throws IOException {
ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK),
"rev-list",
"--reverse",
"--max-count=1",
"HEAD",
"--",
fullpath
};

Executor executor = new Executor(Arrays.asList(argv), new File(getDirectoryName()),
RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
int status = executor.exec();

try (BufferedReader in = new BufferedReader(
new InputStreamReader(executor.getOutputStream()))) {
String line;
HeadHandler headHandler = new HeadHandler(1);
int status = executor.exec(false, headHandler);

if ((line = in.readLine()) != null) {
return line.trim();
}
String line;
if (headHandler.count() > 0 && (line = headHandler.get(0)) != null) {
return line.trim();
}

if (status != 0) {
LOGGER.log(Level.WARNING,
"Failed to get first revision for: \"{0}\" Exit code: {1}",
new Object[]{fullpath, String.valueOf(status)});
return null;
}
LOGGER.log(Level.WARNING,
"Failed to get first revision for: \"{0}\" Exit code: {1}",
new Object[]{fullpath, String.valueOf(status)});

return null;
}
Expand Down Expand Up @@ -450,10 +444,10 @@ public Annotation annotate(File file, String revision) throws IOException {
cmd.add("--");
cmd.add(getPathRelativeToCanonicalRepositoryRoot(file.getCanonicalPath()));

Executor exec = new Executor(cmd, new File(getDirectoryName()),
Executor executor = new Executor(cmd, new File(getDirectoryName()),
RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
GitAnnotationParser parser = new GitAnnotationParser(file.getName());
int status = exec.exec(true, parser);
int status = executor.exec(true, parser);

// File might have changed its location if it was renamed.
// Try to lookup its original name and get the annotation again.
Expand All @@ -469,10 +463,10 @@ public Annotation annotate(File file, String revision) throws IOException {
}
cmd.add("--");
cmd.add(findOriginalName(file.getCanonicalPath(), revision));
exec = new Executor(cmd, new File(getDirectoryName()),
executor = new Executor(cmd, new File(getDirectoryName()),
RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
parser = new GitAnnotationParser(file.getName());
status = exec.exec(true, parser);
status = executor.exec(true, parser);
}

if (status != 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* See LICENSE.txt included in this distribution for the specific
* language governing permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at LICENSE.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/

/*
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
*/

package org.opengrok.indexer.util;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
* The purpose of this class is to provide {@code StreamHandler} that limits the output
* to specified number of lines. Compared to {@code SpoolHandler} it consumes
* limited amount of heap.
*/
public class HeadHandler implements Executor.StreamHandler {
private final int maxLines;

private final List<String> lines = new ArrayList<>();
private final Charset charset;

private static final int bufferedReaderSize = 200;

/**
* Charset of the underlying reader is set to UTF-8.
* @param maxLines maximum number of lines to store
*/
public HeadHandler(int maxLines) {
this.maxLines = maxLines;
this.charset = StandardCharsets.UTF_8;
}

/**
* @param maxLines maximum number of lines to store
* @param charset character set
*/
public HeadHandler(int maxLines, Charset charset) {
this.maxLines = maxLines;
this.charset = charset;
}

/**
* @return number of lines read
*/
public int count() {
return lines.size();
}

/**
* @param index index
* @return line at given index. Will be non {@code null} for valid index.
*/
public String get(int index) {
return lines.get(index);
}

// for testing
static int getBufferedReaderSize() {
return bufferedReaderSize;
}

@Override
public void processStream(InputStream input) throws IOException {
try (BufferedInputStream bufStream = new BufferedInputStream(input);
BufferedReader reader = new BufferedReader(new InputStreamReader(bufStream, this.charset),
bufferedReaderSize)) {
int lineNum = 0;
while (lineNum < maxLines) {
String line = reader.readLine();
if (line == null) { // EOF
return;
}
lines.add(line);
lineNum++;
}

// Read and forget the rest.
byte[] buf = new byte[1024];
while ((bufStream.read(buf)) != -1) {
;
}
}
}
}
144 changes: 75 additions & 69 deletions opengrok-indexer/src/main/java/org/opengrok/indexer/web/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -713,82 +713,88 @@ public static void readableLine(int num, Writer out, Annotation annotation, Stri
out.write(closeQuotedTag);
out.write(snum);
out.write(anchorEnd);

if (annotation != null) {
String r = annotation.getRevision(num);
boolean enabled = annotation.isEnabled(num);
out.write("<span class=\"blame\">");
if (enabled) {
out.write(anchorClassStart);
out.write("r");
out.write("\" style=\"background-color: ");
out.write(annotation.getColors().getOrDefault(r, "inherit"));
out.write("\" href=\"");
out.write(URIEncode(annotation.getFilename()));
out.write("?a=true&amp;r=");
out.write(URIEncode(r));
String msg = annotation.getDesc(r);
out.write("\" title=\"");
if (msg != null) {
out.write(msg);
}
if (annotation.getFileVersion(r) != 0) {
out.write("&lt;br/&gt;version: " + annotation.getFileVersion(r) + "/"
+ annotation.getRevisions().size());
}
out.write(closeQuotedTag);
}
StringBuilder buf = new StringBuilder();
final boolean most_recent_revision = annotation.getFileVersion(r) == annotation.getRevisions().size();
// print an asterisk for the most recent revision
if (most_recent_revision) {
buf.append("<span class=\"most_recent_revision\">");
buf.append('*');
writeAnnotation(num, out, annotation, userPageLink, userPageSuffix, project);
}
}

private static void writeAnnotation(int num, Writer out, Annotation annotation, String userPageLink,
String userPageSuffix, String project) throws IOException {
String r = annotation.getRevision(num);
boolean enabled = annotation.isEnabled(num);
out.write("<span class=\"blame\">");
if (enabled) {
out.write(anchorClassStart);
out.write("r");
out.write("\" style=\"background-color: ");
out.write(annotation.getColors().getOrDefault(r, "inherit"));
out.write("\" href=\"");
out.write(URIEncode(annotation.getFilename()));
out.write("?a=true&amp;r=");
out.write(URIEncode(r));
String msg = annotation.getDesc(r);
out.write("\" title=\"");
if (msg != null) {
out.write(Util.encode(msg));
}
htmlize(r, buf);
if (most_recent_revision) {
buf.append("</span>"); // recent revision span
if (annotation.getFileVersion(r) != 0) {
out.write("&lt;br/&gt;version: " + annotation.getFileVersion(r) + "/"
+ annotation.getRevisions().size());
}
out.write(closeQuotedTag);
}
StringBuilder buf = new StringBuilder();
final boolean most_recent_revision = annotation.getFileVersion(r) == annotation.getRevisions().size();
// print an asterisk for the most recent revision
if (most_recent_revision) {
buf.append("<span class=\"most_recent_revision\">");
buf.append('*');
}
htmlize(r, buf);
if (most_recent_revision) {
buf.append("</span>"); // recent revision span
}
out.write(buf.toString());
buf.setLength(0);
if (enabled) {
RuntimeEnvironment env = RuntimeEnvironment.getInstance();

out.write(anchorEnd);

// Write link to search the revision in current project.
out.write(anchorClassStart);
out.write("search\" href=\"" + env.getUrlPrefix());
out.write("defs=&amp;refs=&amp;path=");
out.write(project);
out.write("&amp;hist=&quot;" + URIEncode(r) + "&quot;");
out.write("&amp;type=\" title=\"Search history for this changeset");
out.write(closeQuotedTag);
out.write("S");
out.write(anchorEnd);
}
String a = annotation.getAuthor(num);
if (userPageLink == null) {
out.write(HtmlConsts.SPAN_A);
htmlize(a, buf);
out.write(buf.toString());
out.write(HtmlConsts.ZSPAN);
buf.setLength(0);
if (enabled) {
RuntimeEnvironment env = RuntimeEnvironment.getInstance();

out.write(anchorEnd);

// Write link to search the revision in current project.
out.write(anchorClassStart);
out.write("search\" href=\"" + env.getUrlPrefix());
out.write("defs=&amp;refs=&amp;path=");
out.write(project);
out.write("&amp;hist=&quot;" + URIEncode(r) + "&quot;");
out.write("&amp;type=\" title=\"Search history for this changeset");
out.write(closeQuotedTag);
out.write("S");
out.write(anchorEnd);
}
String a = annotation.getAuthor(num);
if (userPageLink == null) {
out.write(HtmlConsts.SPAN_A);
htmlize(a, buf);
out.write(buf.toString());
out.write(HtmlConsts.ZSPAN);
buf.setLength(0);
} else {
out.write(anchorClassStart);
out.write("a\" href=\"");
out.write(userPageLink);
out.write(URIEncode(a));
if (userPageSuffix != null) {
out.write(userPageSuffix);
}
out.write(closeQuotedTag);
htmlize(a, buf);
out.write(buf.toString());
buf.setLength(0);
out.write(anchorEnd);
} else {
out.write(anchorClassStart);
out.write("a\" href=\"");
out.write(userPageLink);
out.write(URIEncode(a));
if (userPageSuffix != null) {
out.write(userPageSuffix);
}
out.write("</span>");
out.write(closeQuotedTag);
htmlize(a, buf);
out.write(buf.toString());
buf.setLength(0);
out.write(anchorEnd);
}
out.write("</span>");
}

/**
Expand Down
Loading