Skip to content

Commit 6aeb8ed

Browse files
authored
create history cache for file based repositories (#4283)
fixes #4280
1 parent 991b86b commit 6aeb8ed

File tree

7 files changed

+129
-28
lines changed

7 files changed

+129
-28
lines changed

opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/AnalyzerGuru.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
import org.opengrok.indexer.configuration.Project;
113113
import org.opengrok.indexer.configuration.RuntimeEnvironment;
114114
import org.opengrok.indexer.history.Annotation;
115+
import org.opengrok.indexer.history.History;
115116
import org.opengrok.indexer.history.HistoryEntry;
116117
import org.opengrok.indexer.history.HistoryException;
117118
import org.opengrok.indexer.history.HistoryGuru;
@@ -633,13 +634,15 @@ public void populateDocument(Document doc, File file, String path, AbstractAnaly
633634
private static void populateDocumentHistory(Document doc, File file) {
634635
try {
635636
HistoryGuru histGuru = HistoryGuru.getInstance();
636-
HistoryReader hr = histGuru.getHistoryReader(file);
637-
if (hr != null) {
637+
History history = histGuru.getHistory(file, false);
638+
if (history != null) {
639+
HistoryReader hr = new HistoryReader(history);
638640
doc.add(new TextField(QueryBuilder.HIST, hr));
639-
HistoryEntry histEntry = histGuru.getLastHistoryEntry(file, false, true);
641+
HistoryEntry histEntry = history.getLastHistoryEntry();
640642
if (histEntry != null) {
641643
doc.add(new TextField(QueryBuilder.LASTREV, histEntry.getRevision(), Store.YES));
642644
}
645+
histGuru.storeHistory(file, history);
643646
}
644647
} catch (HistoryException e) {
645648
LOGGER.log(Level.WARNING, "An error occurred while reading history: ", e);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,11 @@ private static File getTagsFile(File file) {
292292
return new File(file.getAbsolutePath() + ".t");
293293
}
294294

295+
@Override
296+
public void storeFile(History history, File file, Repository repository) throws HistoryException {
297+
storeFile(history, file, repository, false);
298+
}
299+
295300
/**
296301
* Store {@link History} object in a file.
297302
*

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ interface HistoryCache extends Cache {
7070
*/
7171
void store(History history, Repository repository, @Nullable String tillRevision) throws CacheException;
7272

73+
/**
74+
* Store the history for a file in given repository.
75+
*
76+
* @param history The history to store
77+
* @param file file
78+
* @param repository The repository whose history to store
79+
* @throws HistoryException if the history cannot be stored
80+
*/
81+
void storeFile(History history, File file, Repository repository) throws HistoryException;
82+
7383
/**
7484
* Get the revision identifier for the latest cached revision in a repository.
7585
*

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

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -349,18 +349,6 @@ private void completeAnnotationWithHistory(File file, Annotation annotation, Rep
349349
}
350350
}
351351

352-
/**
353-
* Get the appropriate history reader for given file.
354-
*
355-
* @param file The file to get the history reader for
356-
* @throws HistoryException If an error occurs while getting the history
357-
* @return A {@link HistoryReader} that may be used to read out history data for a named file
358-
*/
359-
public HistoryReader getHistoryReader(File file) throws HistoryException {
360-
History history = getHistory(file, false);
361-
return history == null ? null : new HistoryReader(history);
362-
}
363-
364352
/**
365353
* Get the history for the specified file.
366354
*
@@ -906,6 +894,26 @@ public Collection<RepositoryInfo> getRepositories() {
906894
return repositories.values().stream().map(RepositoryInfo::new).collect(Collectors.toSet());
907895
}
908896

897+
/**
898+
* Store history for a file into history cache. If the related repository does not support
899+
* getting the history for directories, it will return right away without storing the history.
900+
* @param file file
901+
* @param history {@link History} instance
902+
*/
903+
public void storeHistory(File file, History history) {
904+
Repository repository = getRepository(file);
905+
if (repository.hasHistoryForDirectories()) {
906+
return;
907+
}
908+
909+
try {
910+
historyCache.storeFile(history, file, repository);
911+
} catch (HistoryException e) {
912+
LOGGER.log(Level.WARNING,
913+
String.format("cannot create history cache for '%s' in repository %s", file, repository), e);
914+
}
915+
}
916+
909917
private void createHistoryCache(Repository repository, String sinceRevision) {
910918
String path = repository.getDirectoryName();
911919
String type = repository.getClass().getSimpleName();
@@ -1027,13 +1035,23 @@ public void createHistoryCache(Collection<String> repositories) {
10271035
/**
10281036
* Clear entry for single file from history cache.
10291037
* @param path path to the file relative to the source root
1038+
* @param removeHistory whether to remove history cache entry for the path
10301039
*/
1031-
public void clearHistoryCacheFile(String path) {
1040+
public void clearHistoryCacheFile(String path, boolean removeHistory) {
10321041
if (!useHistoryCache()) {
10331042
return;
10341043
}
10351044

1036-
historyCache.clearFile(path);
1045+
Repository repository = getRepository(new File(env.getSourceRootFile(), path));
1046+
if (repository == null) {
1047+
return;
1048+
}
1049+
1050+
// Repositories that do not support getting history for directories do not undergo
1051+
// incremental history cache generation, so for these the removeHistory parameter is not honored.
1052+
if (!repository.hasHistoryForDirectories() || removeHistory) {
1053+
historyCache.clearFile(path);
1054+
}
10371055
}
10381056

10391057
/**

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@
1818
*/
1919

2020
/*
21-
* Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved.
21+
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
2222
*/
2323
package org.opengrok.indexer.history;
2424

2525
import java.io.IOException;
2626
import java.io.Reader;
2727
import java.io.StringReader;
2828
import java.util.List;
29+
30+
import org.jetbrains.annotations.NotNull;
2931
import org.opengrok.indexer.util.IOUtils;
3032

3133
/**
@@ -36,12 +38,12 @@ public class HistoryReader extends Reader {
3638
private final List<HistoryEntry> entries;
3739
private Reader input;
3840

39-
HistoryReader(History history) {
41+
public HistoryReader(History history) {
4042
entries = history.getHistoryEntries();
4143
}
4244

4345
@Override
44-
public int read(char[] cbuf, int off, int len) throws IOException {
46+
public int read(char @NotNull [] cbuf, int off, int len) throws IOException {
4547
if (input == null) {
4648
input = createInternalReader();
4749
}

opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexDatabase.java

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

2020
/*
21-
* Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved.
21+
* Copyright (c) 2008, 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.index;
@@ -1044,8 +1044,8 @@ private void removeXrefFile(String path) {
10441044
completer.add(pending);
10451045
}
10461046

1047-
private void removeHistoryFile(String path) {
1048-
HistoryGuru.getInstance().clearHistoryCacheFile(path);
1047+
private void removeHistoryCacheFile(String path, boolean removeHistory) {
1048+
HistoryGuru.getInstance().clearHistoryCacheFile(path, removeHistory);
10491049
}
10501050

10511051
private void removeAnnotationFile(String path) {
@@ -1056,7 +1056,7 @@ private void removeAnnotationFile(String path) {
10561056
* Remove a stale file from the index database and potentially also from history cache,
10571057
* and queue the removal of the associated xref file.
10581058
*
1059-
* @param removeHistory if false, do not remove history cache and annotation cache for this file
1059+
* @param removeHistory if false, do not remove history cache for this file
10601060
* @throws java.io.IOException if an error occurs
10611061
*/
10621062
private void removeFile(boolean removeHistory) throws IOException {
@@ -1070,9 +1070,7 @@ private void removeFile(boolean removeHistory) throws IOException {
10701070

10711071
removeXrefFile(path);
10721072

1073-
if (removeHistory) {
1074-
removeHistoryFile(path);
1075-
}
1073+
removeHistoryCacheFile(path, removeHistory);
10761074

10771075
/*
10781076
* Even when the history should not be removed (incremental reindex), annotation should,

opengrok-indexer/src/test/java/org/opengrok/indexer/index/IndexDatabaseTest.java

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import java.io.File;
2727
import java.io.FileOutputStream;
28+
import java.io.FileWriter;
2829
import java.io.IOException;
2930
import java.nio.charset.StandardCharsets;
3031
import java.nio.file.Files;
@@ -70,8 +71,10 @@
7071
import org.opengrok.indexer.history.RepositoryFactory;
7172
import org.opengrok.indexer.history.RepositoryInfo;
7273
import org.opengrok.indexer.history.RepositoryWithHistoryTraversal;
74+
import org.opengrok.indexer.history.SCCSRepository;
7375
import org.opengrok.indexer.search.QueryBuilder;
7476
import org.opengrok.indexer.search.SearchEngine;
77+
import org.opengrok.indexer.util.Executor;
7578
import org.opengrok.indexer.util.ForbiddenSymlinkException;
7679
import org.opengrok.indexer.util.IOUtils;
7780
import org.opengrok.indexer.util.TandemPath;
@@ -91,6 +94,7 @@
9194
import static org.mockito.Mockito.verify;
9295
import static org.mockito.Mockito.when;
9396
import static org.opengrok.indexer.condition.RepositoryInstalled.Type.CVS;
97+
import static org.opengrok.indexer.condition.RepositoryInstalled.Type.SCCS;
9498

9599
/**
96100
* Unit tests for the {@code IndexDatabase} class.
@@ -222,8 +226,14 @@ private void checkDataExistence(String fileName, boolean shouldExist) {
222226
} else {
223227
assertFalse(historyGuru.hasAnnotationCacheForFile(file));
224228
}
229+
} else if (dirName.equals("historycache")) {
230+
if (shouldExist) {
231+
assertTrue(historyGuru.hasHistoryCacheForFile(file));
232+
} else {
233+
assertFalse(historyGuru.hasHistoryCacheForFile(file));
234+
}
225235
} else {
226-
cacheFile = TandemPath.join(fileName, dirName.equals(IndexDatabase.XREF_DIR) ? ".gz" : "");
236+
cacheFile = TandemPath.join(fileName, ".gz");
227237
File dataFile = new File(dataDir, cacheFile);
228238

229239
if (shouldExist) {
@@ -973,4 +983,59 @@ void testAnnotationCacheProjectTunable(boolean useAnnotationCache, boolean isHis
973983
env.setDataRoot(dataRootOrig);
974984
IOUtils.removeRecursive(dataRoot);
975985
}
986+
987+
private static void runSccsCommand(File reposRoot, String... args) {
988+
List<String> cmdargs = new ArrayList<>();
989+
SCCSRepository repo = new SCCSRepository();
990+
cmdargs.add(repo.getRepoCommand());
991+
Collections.addAll(cmdargs, args);
992+
Executor exec = new Executor(cmdargs, reposRoot);
993+
int exitCode = exec.exec();
994+
assertEquals(0, exitCode, "command '" + cmdargs.toString() + "'failed."
995+
+ "\nexit code: " + exitCode
996+
+ "\nstdout:\n" + exec.getOutputString()
997+
+ "\nstderr:\n" + exec.getErrorString());
998+
}
999+
1000+
@Test
1001+
@EnabledForRepository(SCCS)
1002+
void testHistoryCacheForFileBasedRepository() throws Exception {
1003+
String projectName = "teamware";
1004+
Project project = env.getProjects().get(projectName);
1005+
assertNotNull(project);
1006+
IndexDatabase idb = new IndexDatabase(project);
1007+
assertNotNull(idb);
1008+
1009+
String fileName = "header.h";
1010+
File repoRoot = new File(repository.getSourceRoot(), projectName);
1011+
File file = new File(repoRoot, fileName);
1012+
assertTrue(file.exists());
1013+
1014+
// Check that the history cache entry for the file exists.
1015+
checkDataExistence(projectName + File.separator + fileName, true);
1016+
HistoryGuru historyGuru = HistoryGuru.getInstance();
1017+
HistoryEntry historyEntry = historyGuru.getLastHistoryEntry(file, false, false);
1018+
assertNotNull(historyEntry);
1019+
assertEquals("1.1", historyEntry.getRevision());
1020+
1021+
// Update and change the file.
1022+
runSccsCommand(repoRoot, "clean");
1023+
runSccsCommand(repoRoot, "get", "-e", fileName);
1024+
try (FileWriter fileWriter = new FileWriter(file, true)) {
1025+
fileWriter.write("#define FOO 42");
1026+
}
1027+
runSccsCommand(repoRoot, "delget", "-yfoo", fileName);
1028+
idb.update();
1029+
1030+
// Recheck the history cache entry.
1031+
assertTrue(historyGuru.hasHistoryCacheForFile(file));
1032+
historyEntry = historyGuru.getLastHistoryEntry(file, false, false);
1033+
assertNotNull(historyEntry);
1034+
assertEquals("1.2", historyEntry.getRevision());
1035+
1036+
// The SCCS "clean" operation above wiped all the other files, so their history cache entries should be gone.
1037+
File otherFile = new File(repoRoot, "main.c");
1038+
assertFalse(otherFile.exists());
1039+
assertFalse(historyGuru.hasHistoryCacheForFile(otherFile));
1040+
}
9761041
}

0 commit comments

Comments
 (0)