Skip to content

use longer revision for Mercurial repository annotation search links #4767

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 7 commits into from
Apr 24, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ public final Set<String> getRevisions() {
return annotationData.getRevisions();
}

final Set<String> getDisplayRevisions() {
return annotationData.getDisplayRevisions();
}

@TestOnly
Set<String> getAuthors() {
return annotationData.getAuthors();
Expand All @@ -138,8 +142,7 @@ Set<String> getAuthors() {
* Gets the author who last modified the specified line.
*
* @param line line number (counting from 1)
* @return author, or an empty string if there is no information about the
* specified line
* @return author, or an empty string if there is no information about the specified line
*/
public String getAuthor(int line) {
return annotationData.getAuthor(line);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ public AnnotationData(String filename) {
this.filename = filename;
}

/**
* The <code>transient</code> does not matter here since the object is serialized explicitly
* in {@link FileAnnotationCache#store(File, Annotation)}.
*/
private transient List<AnnotationLine> annotationLines = new ArrayList<>();
private int widestRevision;
private int widestAuthor;
Expand Down Expand Up @@ -224,6 +228,14 @@ public Set<String> getRevisions() {
return ret;
}

Set<String> getDisplayRevisions() {
Set<String> ret = new HashSet<>();
for (AnnotationLine ln : annotationLines) {
ret.add(ln.getDisplayRevision());
}
return ret;
}

@TestOnly
Set<String> getAuthors() {
return annotationLines.stream().map(AnnotationLine::getAuthor).collect(Collectors.toSet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@

import org.apache.lucene.document.Document;
import org.apache.lucene.queryparser.classic.ParseException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import org.opengrok.indexer.analysis.AnalyzerFactory;
Expand Down Expand Up @@ -352,32 +353,35 @@ public Annotation annotate(File file, @Nullable String rev, boolean fallback) th
}

private void completeAnnotationWithHistory(File file, Annotation annotation, Repository repo) {
History history = null;
try {
history = getHistory(file);
History history = getHistory(file);
if (history != null) {
completeAnnotationWithHistory(annotation, history, repo);
}
} catch (HistoryException ex) {
LOGGER.log(Level.WARNING, "Cannot get messages for tooltip: ", ex);
}
}

if (history != null) {
Set<String> revs = annotation.getRevisions();
int revsMatched = 0;
// !!! cannot do this because of not matching rev ids (keys)
// first is the most recent one, so we need the position of "rev"
// until the end of the list
//if (hent.indexOf(rev)>0) {
// hent = hent.subList(hent.indexOf(rev), hent.size());
//}
for (HistoryEntry he : history.getHistoryEntries()) {
String hist_rev = he.getRevision();
String short_rev = repo.getRevisionForAnnotate(hist_rev);
if (revs.contains(short_rev)) {
annotation.addDesc(short_rev, he.getDescription());
// History entries are coming from recent to older,
// file version should be from oldest to newer.
annotation.addFileVersion(short_rev, revs.size() - revsMatched);
revsMatched++;
}
@VisibleForTesting
static void completeAnnotationWithHistory(Annotation annotation, @NotNull History history, Repository repo) {
Set<String> revs = annotation.getRevisions();
int revsMatched = 0;
// !!! cannot do this because of not matching rev ids (keys)
// first is the most recent one, so we need the position of "rev"
// until the end of the list
//if (hent.indexOf(rev)>0) {
// hent = hent.subList(hent.indexOf(rev), hent.size());
//}
for (HistoryEntry historyEntry : history.getHistoryEntries()) {
String historyEntryRevision = historyEntry.getRevision();
String revisionForAnnotate = repo.getRevisionForAnnotate(historyEntryRevision);
if (revs.contains(revisionForAnnotate)) {
annotation.addDesc(revisionForAnnotate, historyEntry.getDescription());
// History entries are coming from recent to older,
// file version should be from oldest to newer.
annotation.addFileVersion(revisionForAnnotate, revs.size() - revsMatched);
revsMatched++;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

/*
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
*/
package org.opengrok.indexer.history;

Expand All @@ -28,10 +28,13 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jetbrains.annotations.NotNull;
import org.opengrok.indexer.logger.LoggerFactory;
import org.opengrok.indexer.util.Executor;
import org.opengrok.indexer.web.Util;
Expand All @@ -49,10 +52,12 @@ class MercurialAnnotationParser implements Executor.StreamHandler {

/**
* Pattern used to extract author/revision from the {@code hg annotate} command.
* Obviously, this has to be in concordance with the output template used by
* {@link MercurialRepository#annotate(File, String)}.
*/
private static final Pattern ANNOTATION_PATTERN = Pattern.compile("^\\s*(\\d+):");
private static final Pattern ANNOTATION_PATTERN = Pattern.compile("^(\\d+)\\t([0-9a-f]+):");

MercurialAnnotationParser(File file, HashMap<String, HistoryEntry> revs) {
MercurialAnnotationParser(File file, @NotNull HashMap<String, HistoryEntry> revs) {
this.file = file;
this.revs = revs;
}
Expand All @@ -69,13 +74,12 @@ public void processStream(InputStream input) throws IOException {
++lineno;
matcher.reset(line);
if (matcher.find()) {
String rev = matcher.group(1);
String author = "N/A";
String displayRev = matcher.group(1);
String fullRev = displayRev + ":" + matcher.group(2);
// Use the history index hash map to get the author.
if (revs.get(rev) != null) {
author = revs.get(rev).getAuthor();
}
annotation.addLine(rev, Util.getEmail(author.trim()), true);
String author = Optional.ofNullable(revs.get(fullRev)).map(HistoryEntry::getAuthor).
orElse("N/A");
annotation.addLine(fullRev, Util.getEmail(author.trim()), true, displayRev);
} else {
LOGGER.log(Level.WARNING,
"Error: did not find annotation in line {0} for ''{1}'': [{2}]",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,7 @@ private HistoryRevResult getHistoryRev(BufferSink sink, String fullpath, String
* of a file in historical revision.
*
* @param fullpath file path
* @param fullRevToFind revision number (in the form of
* {rev}:{node|short})
* @param fullRevToFind revision number (in the form of <code>{rev}:{node|short}</code>)
* @return original filename
*/
private String findOriginalName(String fullpath, String fullRevToFind) throws IOException {
Expand Down Expand Up @@ -464,7 +463,11 @@ public Annotation annotate(File file, String revision) throws IOException {
ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
argv.add(RepoCommand);
argv.add("annotate");
argv.add("-n");
argv.add("--template");
/*
* This has to be in concordance with TEMPLATE_REVS, in particular the format of the 'node'.
*/
argv.add("{lines % '{rev}\t{node|short}:{line}'}");
if (!this.isHandleRenamedFiles()) {
argv.add("--no-follow");
}
Expand All @@ -486,20 +489,12 @@ public Annotation annotate(File file, String revision) throws IOException {
try {
History hist = HistoryGuru.getInstance().getHistory(file, false);
if (Objects.isNull(hist)) {
LOGGER.log(Level.SEVERE,
"Error: cannot get history for file ''{0}''", file);
LOGGER.log(Level.SEVERE, "Error: cannot get history for file ''{0}''", file);
return null;
}
for (HistoryEntry e : hist.getHistoryEntries()) {
// Chop out the colon and all hexadecimal what follows.
// This is because the whole changeset identification is
// stored in history index while annotate only needs the
// revision identifier.
revs.put(e.getRevision().replaceFirst(":[a-f0-9]+", ""), e);
}
hist.getHistoryEntries().forEach(rev -> revs.put(rev.getRevision(), rev));
} catch (HistoryException he) {
LOGGER.log(Level.SEVERE,
"Error: cannot get history for file ''{0}''", file);
LOGGER.log(Level.SEVERE, "Error: cannot get history for file ''{0}''", file);
return null;
}

Expand All @@ -509,13 +504,6 @@ public Annotation annotate(File file, String revision) throws IOException {
return annotator.getAnnotation();
}

@Override
protected String getRevisionForAnnotate(String historyRevision) {
String[] brev = historyRevision.split(":");

return brev[0];
}

@Override
public boolean fileHasAnnotation(File file) {
return true;
Expand Down
20 changes: 10 additions & 10 deletions opengrok-indexer/src/main/java/org/opengrok/indexer/web/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -717,41 +717,41 @@ public static void readableLine(int num, Writer out, Annotation annotation, Stri

private static void writeAnnotation(int num, Writer out, Annotation annotation, String userPageLink,
String userPageSuffix, String project) throws IOException {
String r = annotation.getRevision(num);
String revision = annotation.getRevision(num);
boolean enabled = annotation.isEnabled(num);
out.write("<span class=\"blame\">");
if (enabled) {
out.write(ANCHOR_CLASS_START);
out.write("r title-tooltip");
out.write("\" style=\"background-color: ");
out.write(annotation.getColors().getOrDefault(r, "inherit"));
out.write(annotation.getColors().getOrDefault(revision, "inherit"));
out.write("\" href=\"");
out.write(uriEncode(annotation.getFilename()));
out.write("?");
out.write(QueryParameters.ANNOTATION_PARAM_EQ_TRUE);
out.write(AMP);
out.write(QueryParameters.REVISION_PARAM_EQ);
out.write(uriEncode(r));
String msg = annotation.getDesc(r);
out.write(uriEncode(revision));
String msg = annotation.getDesc(revision);
out.write("\" title=\"");
if (msg != null) {
out.write(Util.encode(msg));
}
if (annotation.getFileVersion(r) != 0) {
out.write("&lt;br/&gt;version: " + annotation.getFileVersion(r) + "/"
if (annotation.getFileVersion(revision) != 0) {
out.write("&lt;br/&gt;version: " + annotation.getFileVersion(revision) + "/"
+ annotation.getRevisions().size());
}
out.write(CLOSE_QUOTED_TAG);
}
StringBuilder buf = new StringBuilder();
final boolean most_recent_revision = annotation.getFileVersion(r) == annotation.getRevisions().size();
final boolean isMostRecentRevision = annotation.getFileVersion(revision) == annotation.getRevisions().size();
// print an asterisk for the most recent revision
if (most_recent_revision) {
if (isMostRecentRevision) {
buf.append("<span class=\"most_recent_revision\">");
buf.append('*');
}
htmlize(annotation.getRevisionForDisplay(num), buf);
if (most_recent_revision) {
if (isMostRecentRevision) {
buf.append(SPAN_END); // recent revision span
}
out.write(buf.toString());
Expand All @@ -773,7 +773,7 @@ private static void writeAnnotation(int num, Writer out, Annotation annotation,
out.write(AMP);
out.write(QueryParameters.HIST_SEARCH_PARAM_EQ);
out.write(QUOTE);
out.write(uriEncode(r));
out.write(uriEncode(revision));
out.write("&quot;&amp;");
out.write(QueryParameters.TYPE_SEARCH_PARAM_EQ);
out.write("\" title=\"Search history for this revision");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/
package org.opengrok.indexer.history;

import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
Expand All @@ -37,6 +37,7 @@
import org.opengrok.indexer.util.Executor;
import org.opengrok.indexer.util.IOUtils;
import org.opengrok.indexer.util.TestRepository;
import org.opengrok.indexer.web.Util;

import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -435,25 +436,40 @@ void testAnnotationNegative() throws Exception {
assertNull(annotation);
}

private static Stream<Pair<String, List<String>>> provideParametersForPositiveAnnotationTest() {
return Stream.of(Pair.of("8:6a8c423f5624", List.of("7", "8")),
Pair.of("7:db1394c05268", List.of("7")));
private static Stream<Triple<String, List<String>, List<String>>> provideParametersForPositiveAnnotationTest() {
return Stream.of(Triple.of("8:6a8c423f5624", List.of("7", "8"), List.of("8:6a8c423f5624", "7:db1394c05268")),
Triple.of("7:db1394c05268", List.of("7"), List.of("7:db1394c05268")));
}

@ParameterizedTest
@MethodSource("provideParametersForPositiveAnnotationTest")
void testAnnotationPositive(Pair<String, List<String>> pair) throws Exception {
void testAnnotationPositive(Triple<String, List<String>, List<String>> triple) throws Exception {
MercurialRepository hgRepo = (MercurialRepository) RepositoryFactory.getRepository(repositoryRoot);
assertNotNull(hgRepo);
File file = new File(repositoryRoot, "novel.txt");
assertTrue(file.exists());
// The annotate() method calls uses HistoryGuru's getHistory() method which requires the RepositoryLookup
// to be initialized. Do so via setRepositories().
RuntimeEnvironment.getInstance().setRepositories(repository.getSourceRoot());
Annotation annotation = hgRepo.annotate(file, pair.getKey());
Annotation annotation = hgRepo.annotate(file, triple.getLeft());
assertNotNull(annotation);
List<String> displayRevisions = new ArrayList<>(annotation.getDisplayRevisions());
assertEquals(triple.getMiddle(), displayRevisions);
List<String> revisions = new ArrayList<>(annotation.getRevisions());
assertEquals(pair.getValue(), revisions);
assertEquals(triple.getRight(), revisions);
History history = HistoryGuru.getInstance().getHistory(file);
assertNotNull(history);
assertFalse(history.getHistoryEntries().isEmpty());
HistoryGuru.completeAnnotationWithHistory(annotation, history, hgRepo);
List<HistoryEntry> relevantEntries = history.getHistoryEntries().stream().
filter(e -> annotation.getRevisions().contains(e.getRevision())).
collect(Collectors.toList());
assertFalse(relevantEntries.isEmpty());
for (HistoryEntry entry : relevantEntries) {
assertTrue(annotation.getRevisions().contains(entry.getRevision()));
assertEquals(entry.getDescription(), annotation.getDesc(entry.getRevision()));
assertTrue(annotation.getAuthors().contains(Util.getEmail(entry.getAuthor())));
}
}

/**
Expand Down
Loading