Skip to content

Commit d24098f

Browse files
authored
skip inactive history entries when generating history context (#4461)
fixes #496
1 parent 667c06e commit d24098f

File tree

3 files changed

+108
-53
lines changed

3 files changed

+108
-53
lines changed

opengrok-indexer/src/main/java/org/opengrok/indexer/history/History.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
package org.opengrok.indexer.history;
2525

2626
import org.jetbrains.annotations.Nullable;
27+
import org.jetbrains.annotations.VisibleForTesting;
2728

2829
import java.io.Serializable;
2930
import java.util.ArrayList;
@@ -65,7 +66,8 @@ public History() {
6566
this(new ArrayList<>());
6667
}
6768

68-
History(List<HistoryEntry> entries) {
69+
@VisibleForTesting
70+
public History(List<HistoryEntry> entries) {
6971
this(entries, Collections.emptyList());
7072
}
7173

opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/HistoryContext.java

Lines changed: 63 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919

2020
/*
21-
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
21+
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
2222
* Portions Copyright (c) 2017, 2020, Chris Fraire <[email protected]>.
2323
*/
2424
package org.opengrok.indexer.search.context;
@@ -34,6 +34,8 @@
3434
import java.util.logging.Logger;
3535

3636
import org.apache.lucene.search.Query;
37+
import org.jetbrains.annotations.Nullable;
38+
import org.jetbrains.annotations.VisibleForTesting;
3739
import org.opengrok.indexer.history.History;
3840
import org.opengrok.indexer.history.HistoryEntry;
3941
import org.opengrok.indexer.history.HistoryException;
@@ -119,32 +121,75 @@ public boolean getContext(File src, String path, Writer out, String context) thr
119121
return getHistoryContext(hist, path, out, null, context);
120122
}
121123

124+
private int matchLine(String line, String urlPrefix, String path, @Nullable Writer out, @Nullable List<Hit> hits,
125+
String rev, String nrev) throws IOException {
126+
127+
int matchedLines = 0;
128+
tokens.reInit(line);
129+
String token;
130+
int matchState;
131+
long start = -1;
132+
while ((token = tokens.next()) != null) {
133+
for (LineMatcher lineMatcher : m) {
134+
matchState = lineMatcher.match(token);
135+
if (matchState == LineMatcher.MATCHED) {
136+
if (start < 0) {
137+
start = tokens.getMatchStart();
138+
}
139+
long end = tokens.getMatchEnd();
140+
if (start > Integer.MAX_VALUE || end > Integer.MAX_VALUE) {
141+
LOGGER.log(Level.WARNING, "Unexpected out of bounds for {0}", path);
142+
} else if (hits != null) {
143+
StringBuilder sb = new StringBuilder();
144+
writeMatch(sb, line, (int) start, (int) end,
145+
true, path, urlPrefix, nrev, rev);
146+
hits.add(new Hit(path, sb.toString(), "", false, false));
147+
} else {
148+
writeMatch(out, line, (int) start, (int) end,
149+
false, path, urlPrefix, nrev, rev);
150+
}
151+
matchedLines++;
152+
break;
153+
} else if (matchState == LineMatcher.WAIT) {
154+
if (start < 0) {
155+
start = tokens.getMatchStart();
156+
}
157+
} else {
158+
start = -1;
159+
}
160+
}
161+
}
162+
163+
return matchedLines;
164+
}
165+
122166
/**
123-
* Writes matching History log entries from 'in' to 'out' or to 'hits'.
124-
* @param in the history to fetch entries from
167+
* Writes matching history log entries to either 'out' or to 'hits'.
168+
* @param history the history to fetch entries from
125169
* @param out to write matched context
126170
* @param path path to the file
127-
* @param hits list of hits
128-
* @param wcontext web context - beginning of url
171+
* @param hits list of {@link Hit} instances
172+
* @param urlPrefix URL prefix
173+
* @return whether there was at least one line that matched
129174
*/
130-
private boolean getHistoryContext(
131-
History in, String path, Writer out, List<Hit> hits, String wcontext) {
132-
if (in == null) {
175+
@VisibleForTesting
176+
boolean getHistoryContext(History history, String path, @Nullable Writer out, @Nullable List<Hit> hits,
177+
String urlPrefix) {
178+
if (history == null) {
133179
throw new IllegalArgumentException("`in' is null");
134180
}
135181
if ((out == null) == (hits == null)) {
136182
// There should be exactly one destination for the output. If
137183
// none or both are specified, it's a bug.
138-
throw new IllegalArgumentException(
139-
"Exactly one of out and hits should be non-null");
184+
throw new IllegalArgumentException("Exactly one of out and hits should be non-null");
140185
}
141186

142187
if (m == null) {
143188
return false;
144189
}
145190

146191
int matchedLines = 0;
147-
Iterator<HistoryEntry> it = in.getHistoryEntries().iterator();
192+
Iterator<HistoryEntry> it = history.getHistoryEntries().stream().filter(HistoryEntry::isActive).iterator();
148193
try {
149194
HistoryEntry he;
150195
HistoryEntry nhe = null;
@@ -153,10 +198,11 @@ private boolean getHistoryContext(
153198
if (nhe == null) {
154199
he = it.next();
155200
} else {
156-
he = nhe; //nhe is the lookahead revision
201+
he = nhe; // nhe is the lookahead revision
157202
}
158203
String line = he.getLine();
159204
String rev = he.getRevision();
205+
160206
if (it.hasNext()) {
161207
nhe = it.next();
162208
} else {
@@ -169,40 +215,7 @@ private boolean getHistoryContext(
169215
} else {
170216
nrev = nhe.getRevision();
171217
}
172-
tokens.reInit(line);
173-
String token;
174-
int matchState;
175-
long start = -1;
176-
while ((token = tokens.next()) != null) {
177-
for (LineMatcher lineMatcher : m) {
178-
matchState = lineMatcher.match(token);
179-
if (matchState == LineMatcher.MATCHED) {
180-
if (start < 0) {
181-
start = tokens.getMatchStart();
182-
}
183-
long end = tokens.getMatchEnd();
184-
if (start > Integer.MAX_VALUE || end > Integer.MAX_VALUE) {
185-
LOGGER.log(Level.INFO, "Unexpected out of bounds for {0}", path);
186-
} else if (out == null) {
187-
StringBuilder sb = new StringBuilder();
188-
writeMatch(sb, line, (int) start, (int) end,
189-
true, path, wcontext, nrev, rev);
190-
hits.add(new Hit(path, sb.toString(), "", false, false));
191-
} else {
192-
writeMatch(out, line, (int) start, (int) end,
193-
false, path, wcontext, nrev, rev);
194-
}
195-
matchedLines++;
196-
break;
197-
} else if (matchState == LineMatcher.WAIT) {
198-
if (start < 0) {
199-
start = tokens.getMatchStart();
200-
}
201-
} else {
202-
start = -1;
203-
}
204-
}
205-
}
218+
matchedLines += matchLine(line, urlPrefix, path, out, hits, rev, nrev);
206219
}
207220
} catch (Exception e) {
208221
LOGGER.log(Level.WARNING, "Could not get history context for " + path, e);
@@ -219,24 +232,23 @@ private boolean getHistoryContext(
219232
* @param end position of the first char after the match
220233
* @param flatten should multi-line log entries be flattened to a single
221234
* @param path path to the file
222-
* @param wcontext web context (begin of URL)
235+
* @param urlPrefix URL prefix
223236
* @param nrev old revision
224237
* @param rev current revision
225-
* line? If {@code true}, replace newline with space.
226238
* @throws IOException IO exception
227239
*/
228240
protected static void writeMatch(Appendable out, String line,
229241
int start, int end, boolean flatten, String path,
230-
String wcontext, String nrev, String rev)
242+
String urlPrefix, String nrev, String rev)
231243
throws IOException {
232244

233245
String prefix = line.substring(0, start);
234246
String match = line.substring(start, end);
235247
String suffix = line.substring(end);
236248

237-
if (wcontext != null && nrev != null && !wcontext.isEmpty()) {
249+
if (urlPrefix != null && nrev != null && !urlPrefix.isEmpty()) {
238250
out.append("<a href=\"");
239-
printHTML(out, wcontext + Prefix.DIFF_P +
251+
printHTML(out, urlPrefix + Prefix.DIFF_P +
240252
Util.uriEncodePath(path) +
241253
"?" + QueryParameters.REVISION_2_PARAM_EQ + Util.uriEncodePath(path) + "@" +
242254
rev + "&" + QueryParameters.REVISION_1_PARAM_EQ + Util.uriEncodePath(path) +

opengrok-indexer/src/test/java/org/opengrok/indexer/search/context/HistoryContextTest.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,21 @@
1818
*/
1919

2020
/*
21-
* Copyright (c) 2010, 2021, Oracle and/or its affiliates. All rights reserved.
21+
* Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
2222
* Portions Copyright (c) 2017, Chris Fraire <[email protected]>.
2323
*/
2424
package org.opengrok.indexer.search.context;
2525

26+
import java.io.File;
2627
import java.io.IOException;
2728
import java.io.StringWriter;
2829
import java.nio.file.Path;
2930
import java.nio.file.Paths;
3031
import java.util.ArrayList;
32+
import java.util.Date;
33+
import java.util.List;
34+
import java.util.Set;
35+
3136
import org.apache.lucene.index.Term;
3237
import org.apache.lucene.search.BooleanClause.Occur;
3338
import org.apache.lucene.search.BooleanQuery;
@@ -37,11 +42,14 @@
3742
import org.junit.jupiter.api.BeforeAll;
3843
import org.junit.jupiter.api.Test;
3944
import org.opengrok.indexer.condition.EnabledForRepository;
45+
import org.opengrok.indexer.history.History;
46+
import org.opengrok.indexer.history.HistoryEntry;
4047
import org.opengrok.indexer.search.Hit;
4148
import org.opengrok.indexer.util.TestRepository;
4249

4350
import static org.junit.jupiter.api.Assertions.assertEquals;
4451
import static org.junit.jupiter.api.Assertions.assertFalse;
52+
import static org.junit.jupiter.api.Assertions.assertNotNull;
4553
import static org.junit.jupiter.api.Assertions.assertTrue;
4654
import static org.opengrok.indexer.condition.RepositoryInstalled.Type.MERCURIAL;
4755

@@ -190,4 +198,37 @@ void testWriteMatch() throws IOException {
190198
"r1=/foo%20bar/haf%2Bhaf@1\" title=\"diff to previous version\">diff</a> <b>foo</b>",
191199
sb.toString());
192200
}
201+
202+
/**
203+
* Test that inactive history entries are skipped when generating history context.
204+
*/
205+
@Test
206+
void testGetHistoryContextVsInactiveHistoryEntries() {
207+
Set<String> filePaths = Set.of(File.separator + Paths.get("teamware", "foo.c"));
208+
History history = new History(List.of(
209+
new HistoryEntry("1.2", "1.2", new Date(1485438707000L),
210+
"Totoro",
211+
"Uaaah\n", true, filePaths),
212+
new HistoryEntry("1.2", "1.2", new Date(1485438706000L),
213+
"Catbus",
214+
"Miau\n", false, filePaths),
215+
new HistoryEntry("1.1", "1.1", new Date(1485438705000L),
216+
"Totoro",
217+
"Hmmm\n", true, filePaths)
218+
));
219+
220+
ArrayList<Hit> hits = new ArrayList<>();
221+
BooleanQuery.Builder query = new BooleanQuery.Builder();
222+
HistoryEntry lastHistoryEntry = history.getLastHistoryEntry();
223+
assertNotNull(lastHistoryEntry);
224+
query.add(new TermQuery(new Term("hist", lastHistoryEntry.getMessage().trim())), Occur.MUST);
225+
HistoryContext historyContext = new HistoryContext(query.build());
226+
final String path = "/foo/bar";
227+
final String prefix = "prefix";
228+
assertTrue(historyContext.getHistoryContext(history, path, null, hits, prefix));
229+
assertEquals(1, hits.size());
230+
assertTrue(hits.get(0).getLine().
231+
startsWith(String.format("<a href=\"%s/diff%s?r2=%[email protected]&amp;r1=%[email protected]\"",
232+
prefix, path, path, path)));
233+
}
193234
}

0 commit comments

Comments
 (0)