0N/A/*
0N/A * CDDL HEADER START
0N/A *
0N/A * The contents of this file are subject to the terms of the
0N/A * Common Development and Distribution License (the "License").
0N/A * You may not use this file except in compliance with the License.
0N/A *
0N/A * See LICENSE.txt included in this distribution for the specific
0N/A * language governing permissions and limitations under the License.
0N/A *
0N/A * When distributing Covered Code, include this CDDL HEADER in each
0N/A * file and include the License file at LICENSE.txt.
0N/A * If applicable, add the following below this CDDL HEADER, with the
0N/A * fields enclosed by brackets "[]" replaced with your own identifying
0N/A * information: Portions Copyright [yyyy] [name of copyright owner]
0N/A *
0N/A * CDDL HEADER END
0N/A */
0N/A
0N/A/*
1460N/A * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
0N/A */
0N/Apackage org.opensolaris.opengrok.history;
0N/A
182N/Aimport java.io.File;
182N/Aimport java.io.IOException;
182N/Aimport java.io.InputStream;
240N/Aimport java.util.ArrayList;
966N/Aimport java.util.Collection;
1460N/Aimport java.util.Collections;
1460N/Aimport java.util.Date;
8N/Aimport java.util.HashMap;
240N/Aimport java.util.List;
8N/Aimport java.util.Map;
879N/Aimport java.util.Set;
701N/Aimport java.util.concurrent.ExecutorService;
701N/Aimport java.util.concurrent.Executors;
701N/Aimport java.util.concurrent.TimeUnit;
416N/Aimport java.util.logging.Level;
590N/Aimport java.util.logging.Logger;
1327N/A
1462N/Aimport org.opensolaris.opengrok.configuration.Configuration;
58N/Aimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
186N/Aimport org.opensolaris.opengrok.index.IgnoredNames;
0N/A
0N/A/**
0N/A * The HistoryGuru is used to implement an transparent layer to the various
0N/A * source control systems.
0N/A *
0N/A * @author Chandan
0N/A */
439N/Apublic final class HistoryGuru {
1327N/A private static final Logger logger =
1327N/A Logger.getLogger(HistoryGuru.class.getName());
715N/A
0N/A /** The one and only instance of the HistoryGuru */
0N/A private static HistoryGuru instance = new HistoryGuru();
99N/A
353N/A /** The history cache to use */
456N/A private final HistoryCache historyCache;
1190N/A
1190N/A private Map<String, Repository> repositories =
1183N/A new HashMap<String, Repository>();
1183N/A private final int scanningDepth;
330N/A
0N/A /**
0N/A * Creates a new instance of HistoryGuru, and try to set the default
0N/A * source control system.
0N/A */
0N/A private HistoryGuru() {
772N/A HistoryCache cache = null;
1470N/A Configuration cfg = RuntimeEnvironment.getConfig();
1470N/A scanningDepth = cfg.getScanningDepth();
1470N/A if (cfg.isHistoryCache()) {
1470N/A if (cfg.isHistoryCacheInDB()) {
772N/A cache = new JDBCHistoryCache();
772N/A } else {
772N/A cache = new FileHistoryCache();
772N/A }
772N/A try {
772N/A cache.initialize();
772N/A } catch (HistoryException he) {
1470N/A logger.warning("Failed to initialize the history cache: "
1470N/A + he.getMessage());
1327N/A logger.log(Level.FINE, "HistoryGuru", he);
772N/A // Failed to initialize, run without a history cache
772N/A cache = null;
772N/A }
715N/A }
772N/A historyCache = cache;
0N/A }
1190N/A
0N/A /**
0N/A * Get the one and only instance of the HistoryGuru
0N/A * @return the one and only HistoryGuru instance
0N/A */
0N/A public static HistoryGuru getInstance() {
0N/A return instance;
0N/A }
83N/A
0N/A /**
1474N/A * Check whether a cache should be used for the history log.
1474N/A * @return {@code true} if the history cache has been enabled and is ready
1474N/A * for use.
772N/A */
772N/A private boolean useCache() {
772N/A return historyCache != null;
772N/A }
772N/A
772N/A /**
864N/A * Get a string with information about the history cache.
864N/A *
864N/A * @return a free form text string describing the history cache instance
864N/A * @throws HistoryException if an error occurred while getting the info
864N/A */
864N/A public String getCacheInfo() throws HistoryException {
1474N/A return useCache() ? "No cache" : historyCache.getInfo();
864N/A }
864N/A
864N/A /**
84N/A * Annotate the specified revision of a file.
84N/A *
84N/A * @param file the file to annotate
84N/A * @param rev the revision to annotate (<code>null</code> means BASE)
87N/A * @return file annotation, or <code>null</code> if the
87N/A * <code>HistoryParser</code> does not support annotation
1190N/A * @throws IOException
84N/A */
459N/A public Annotation annotate(File file, String rev) throws IOException {
543N/A Annotation ret = null;
543N/A
290N/A Repository repos = getRepository(file);
176N/A if (repos != null) {
543N/A ret = repos.annotate(file, rev);
878N/A History hist = null;
878N/A try {
878N/A hist = repos.getHistory(file);
878N/A } catch (HistoryException ex) {
1327N/A logger.log(Level.FINEST, "Cannot get messages for tooltip", ex);
878N/A }
1182N/A if (hist != null && ret != null) {
1474N/A Set<String> revs = ret.getRevisions();
1182N/A // !!! cannot do this because of not matching rev ids (keys)
1190N/A // first is the most recent one, so we need the position of "rev"
1182N/A // until the end of the list
878N/A //if (hent.indexOf(rev)>0) {
1182N/A // hent = hent.subList(hent.indexOf(rev), hent.size());
878N/A //}
1182N/A for (HistoryEntry he : hist.getHistoryEntries()) {
1474N/A String cmr = he.getRevision();
1190N/A //TODO this is only for mercurial, for other SCMs it might also
1190N/A // be a problem, we need to revise how we shorten the rev # for
1182N/A // annotate
1474N/A String[] brev = cmr.split(":");
878N/A if (revs.contains(brev[0])) {
1474N/A ret.addDesc(brev[0], he.getRevision(), he.getMessage(),
1384N/A he.getAuthor(), he.getDate());
878N/A }
878N/A }
878N/A }
84N/A }
176N/A
543N/A return ret;
84N/A }
84N/A
84N/A /**
1190N/A * Get the appropriate history reader for the file specified by parent and
1183N/A * basename.
0N/A *
1473N/A * @param file The file to get the history reader for (canonical path incl.
1473N/A * source root).
615N/A * @throws HistoryException If an error occurs while getting the history
1183N/A * @return A HistorReader that may be used to read out history data for a
1183N/A * named file
0N/A */
1461N/A @SuppressWarnings("resource")
615N/A public HistoryReader getHistoryReader(File file) throws HistoryException {
839N/A History history = getHistory(file, false);
829N/A return history == null ? null : new HistoryReader(history);
829N/A }
829N/A
829N/A /**
1466N/A * Get the history for the specified file. The type of the returned history
1466N/A * (directory or file) gets automatically determined: If file physically
1466N/A * exists and is a directory, directory is assumed, file otherwise.
829N/A *
1473N/A * @param path The file to get the history for (canonical path incl.
1473N/A * source root).
1466N/A * @return The history for the given path including a list of files touched
1466N/A * by each changeset.
829N/A * @throws HistoryException on error when accessing the history
829N/A */
1466N/A public History getHistory(File path) throws HistoryException {
1466N/A return getHistory(path, true);
839N/A }
839N/A
839N/A /**
1466N/A * Get the history for the specified file. The type of the returned history
1466N/A * (directory or file) gets automatically determined: If file physically
1466N/A * exists and is a directory, directory is assumed, file otherwise.
839N/A *
1473N/A * @param path The file to get the history for (canonical path incl.
1473N/A * source root).
1466N/A * @param withFiles If {@code true} the returned history will contain
1466N/A * a list of files touched by each changeset (the file list may be
1466N/A * skipped if {@code false}, but it doesn't have to)
1466N/A * @return The history for the given path.
839N/A * @throws HistoryException on error when accessing the history
839N/A */
1466N/A public History getHistory(File path, boolean withFiles)
1466N/A throws HistoryException
1466N/A {
1466N/A return getHistory(path, withFiles, null);
1466N/A }
1466N/A
1466N/A /**
1466N/A * Get the history for the specified path. The type of the returned history
1466N/A * (directory or file) gets automatically determined: If <var>path</var>
1466N/A * physically exists and is a directory, directory is assumed, file otherwise.
1466N/A *
1473N/A * @param path The path name to get the history for (canonical path
1473N/A * incl. source root).
1466N/A * @param withFiles If {@code true} the returned history will contain
1466N/A * a list of files touched by each changeset (the file list may be
1466N/A * skipped if {@code false}, but it doesn't have to)
1466N/A * @param isDir If {@code null} it behaves like
1466N/A * {@link #getHistory(File, boolean)} (auto detect history type).
1466N/A * If {@code true} doesn't check the file and blindly assumes type
1466N/A * directory. Otherwise a file history gets returned.
1466N/A * @return the history for the given path
1466N/A * @throws HistoryException on error when accessing the history
1466N/A * @see #isDirectory(String)
1466N/A */
1466N/A public History getHistory(File path, boolean withFiles, Boolean isDir)
1466N/A throws HistoryException
1466N/A {
1466N/A File dir = path;
1466N/A if (isDir == null) {
1466N/A if (!path.isDirectory()) {
1466N/A dir = path.getParentFile();
1466N/A isDir = Boolean.FALSE;
1466N/A } else {
1466N/A isDir = Boolean.TRUE;
1466N/A }
1466N/A } else if (!isDir.booleanValue()) {
1466N/A dir = path.getParentFile();
1466N/A }
772N/A final Repository repos = getRepository(dir);
25N/A
829N/A History history = null;
829N/A
1466N/A if (repos != null && repos.isWorking() && repos.fileHasHistory(path)
1470N/A && (!repos.isRemote() || RuntimeEnvironment.getConfig()
1190N/A .isRemoteScmSupported()))
1183N/A {
887N/A if (useCache() && historyCache.supportsRepository(repos)) {
1466N/A history = historyCache.get(path, repos, withFiles, isDir);
839N/A } else {
1466N/A history = repos.getHistory(path);
839N/A }
25N/A }
25N/A
829N/A return history;
0N/A }
50N/A
0N/A /**
0N/A * Get a named revision of the specified file.
1473N/A * @param parent The directory (canonical path incl. source root) containing
1473N/A * the file.
1473N/A * @param basename The name of the file.
1473N/A * @param rev The revision to get.
0N/A * @return An InputStream containing the named revision of the file.
1473N/A * @see "Repository.getHistoryGet(String, String, String)"
0N/A */
1190N/A public InputStream getRevision(String parent, String basename, String rev)
1183N/A {
543N/A InputStream ret = null;
543N/A
353N/A Repository rep = getRepository(new File(parent));
353N/A if (rep != null) {
543N/A ret = rep.getHistoryGet(parent, basename, rev);
0N/A }
543N/A return ret;
0N/A }
1190N/A
0N/A /**
0N/A * Does this directory contain files with source control information?
1466N/A * In contrast to {@link #isDirectory(String)} this usually doesn't hit the
1466N/A * history DB but solely ask the repository.
1466N/A *
1473N/A * @param file The name of the directory (canonical path incl. source root).
1466N/A * @return {@code true} if the files in this directory have associated
1466N/A * revision history
0N/A */
256N/A public boolean hasHistory(File file) {
290N/A Repository repos = getRepository(file);
703N/A
1183N/A return repos == null
1183N/A ? false
1183N/A : repos.isWorking() && repos.fileHasHistory(file)
1470N/A && (RuntimeEnvironment.getConfig().isRemoteScmSupported()
1183N/A || !repos.isRemote());
0N/A }
93N/A
93N/A /**
1466N/A * Check if we can annotate the specified file. Doesn't hit the history DB
1466N/A * but asks the repository.
93N/A *
1473N/A * @param file the file to check (canonical path incl. source root).
1466N/A * @return {@code true} if the file is under version control and the
1466N/A * version control system supports annotation.
93N/A */
93N/A public boolean hasAnnotation(File file) {
176N/A if (!file.isDirectory()) {
290N/A Repository repos = getRepository(file);
435N/A if (repos != null && repos.isWorking()) {
298N/A return repos.fileHasAnnotation(file);
176N/A }
93N/A }
1190N/A
176N/A return false;
93N/A }
93N/A
1460N/A /**
1463N/A * Get the last modified times for all files and subdirectories excluding
1463N/A * '.' and '..' in the specified directory the history cache has information
1463N/A * about. The returned map is keyed by the name of the file or subdirectory
1463N/A * and does neither contain a {@code null} key nor a {@code null} value.
1460N/A *
1473N/A * @param directory the directory whose files to check (canonical path incl.
1473N/A * source root).
1474N/A * @param path2rev If not {@code null}, pathes including their latest
1474N/A * revisions get stored into this map.
1463N/A * @return a possible empty map.
1460N/A */
1474N/A public Map<String, Date> getLastModifiedTimes(File directory,
1474N/A Map<String, String> path2rev)
1474N/A {
1475N/A if (!useCache()) {
1475N/A return Collections.emptyMap();
1475N/A }
1475N/A // if source root
1475N/A if (directory.equals(RuntimeEnvironment.getConfig().getSourceRootFile())) {
1463N/A try {
1475N/A Map<String, Date> result =
1475N/A historyCache.getLastModifiedTimes(path2rev);
1475N/A return result;
1475N/A } catch (HistoryException e) {
1475N/A logger.warning(e.getLocalizedMessage());
1475N/A if (logger.isLoggable(Level.FINE)) {
1475N/A logger.log(Level.FINE, "getLastModifiedTimes()", e);
1475N/A }
1475N/A }
1475N/A return Collections.emptyMap();
1475N/A }
1475N/A // else try repo
1475N/A Repository repository = getRepository(directory);
1475N/A if (repository != null) {
1475N/A try {
1475N/A Map<String, Date> result = historyCache
1475N/A .getLastModifiedTimes(directory, repository, path2rev);
1475N/A return result;
1463N/A } catch (HistoryException e) {
1463N/A logger.warning(e.getLocalizedMessage());
1463N/A if (logger.isLoggable(Level.FINE)) {
1463N/A logger.log(Level.FINE, "getLastModifiedTimes()", e);
1463N/A }
1463N/A }
1460N/A }
1460N/A return Collections.emptyMap();
1460N/A }
1460N/A
1183N/A private void addRepositories(File[] files, Collection<RepositoryInfo> repos,
1190N/A IgnoredNames ignoredNames, int depth)
1183N/A {
993N/A addRepositories(files, repos, ignoredNames, true, depth);
317N/A }
317N/A
993N/A /**
993N/A * recursivelly search for repositories with a depth limit
993N/A * @param files list of files to check if they contain a repo
993N/A * @param repos list of found repos
993N/A * @param ignoredNames what files to ignore
993N/A * @param recursiveSearch whether to use recursive search
1190N/A * @param depth current depth - using global scanningDepth - one can limit
1183N/A * this to improve scanning performance
993N/A */
1183N/A private void addRepositories(File[] files, Collection<RepositoryInfo> repos,
1183N/A IgnoredNames ignoredNames, boolean recursiveSearch, int depth) {
314N/A for (File file : files) {
314N/A Repository repository = null;
314N/A try {
314N/A repository = RepositoryFactory.getRepository(file);
314N/A } catch (InstantiationException ie) {
1327N/A logger.warning("Could not create repoitory for '"
1327N/A + file + "', could not instantiate the repository: "
1327N/A + ie.getMessage());
1327N/A logger.log(Level.FINE, "addRepositories", ie);
314N/A } catch (IllegalAccessException iae) {
1327N/A logger.warning("Could not create repoitory for '"
1327N/A + file + "', missing access rights: " + iae.getMessage());
314N/A }
1250N/A if (repository == null) {
1250N/A // Not a repository, search it's sub-dirs
1474N/A if (file.isDirectory() && !ignoredNames.match(file)) {
1250N/A File subFiles[] = file.listFiles();
1250N/A if (subFiles == null) {
1327N/A logger.warning("Failed to get sub directories for '"
1250N/A + file.getAbsolutePath()
1250N/A + "', check access permissions.");
1250N/A } else if (depth<=scanningDepth) {
1250N/A addRepositories(subFiles, repos, ignoredNames, depth+1);
1250N/A }
1250N/A }
1250N/A } else {
314N/A try {
314N/A String path = file.getCanonicalPath();
314N/A repository.setDirectoryName(path);
1470N/A if (RuntimeEnvironment.getConfig().isVerbose()) {
1327N/A logger.log(Level.FINE, "Adding <{0}> repository: <{1}>",
1183N/A new Object[]{repository.getClass().getName(), path});
314N/A }
1190N/A
664N/A repos.add(new RepositoryInfo(repository));
191N/A
460N/A // @TODO: Search only for one type of repository - the one found here
317N/A if (recursiveSearch && repository.supportsSubRepositories()) {
314N/A File subFiles[] = file.listFiles();
460N/A if (subFiles == null) {
1327N/A logger.warning("Failed to get sub directories for '"
1190N/A + file.getAbsolutePath()
1182N/A + "', check access permissions.");
1182N/A } else if (depth<=scanningDepth) {
1190N/A // Search only one level down - if not: too much
1182N/A // stat'ing for huge Mercurial repositories
1190N/A addRepositories(subFiles, repos, ignoredNames,
1182N/A false, depth+1);
314N/A }
314N/A }
1190N/A
314N/A } catch (IOException exp) {
1327N/A logger.warning("Failed to get canonical path for '"
1327N/A + file.getAbsolutePath()
1327N/A + "'. Repository will be ignored: " + exp.getMessage());
1327N/A logger.log(Level.FINE, "addRepositories", exp);
314N/A }
191N/A }
1190N/A }
11N/A }
1190N/A
99N/A /**
99N/A * Search through the all of the directories and add all of the source
99N/A * repositories found.
1190N/A *
99N/A * @param dir the root directory to start the search in.
99N/A */
290N/A public void addRepositories(String dir) {
664N/A List<RepositoryInfo> repos = new ArrayList<RepositoryInfo>();
1470N/A addRepositories(new File[] { new File(dir) }, repos,
1473N/A RuntimeEnvironment.getConfig().getIgnoredNames(), 0);
1470N/A RuntimeEnvironment.getConfig().setRepositories(repos);
664N/A invalidateRepositories(repos);
58N/A }
58N/A
203N/A /**
203N/A * Update the source the contents in the source repositories.
203N/A */
203N/A public void updateRepositories() {
1470N/A boolean verbose = RuntimeEnvironment.getConfig().isVerbose();
590N/A
664N/A for (Map.Entry<String, Repository> entry : repositories.entrySet()) {
290N/A Repository repository = entry.getValue();
1190N/A
203N/A String path = entry.getKey();
203N/A String type = repository.getClass().getSimpleName();
517N/A
578N/A if (repository.isWorking()) {
517N/A if (verbose) {
1327N/A logger.log(Level.INFO, "Update {0} repository in ''{1}''",
1327N/A new String[] { type, path });
517N/A }
517N/A
517N/A try {
517N/A repository.update();
519N/A } catch (UnsupportedOperationException e) {
1327N/A logger.warning("Skipping update of " + type + " repository"
1327N/A + " in '" + path + "': Not implemented");
517N/A } catch (Exception e) {
1327N/A logger.warning("An error occured while updating '"
1327N/A + path + "' (" + type + "): " + e.getMessage());
1327N/A logger.log(Level.FINE, "updateRepositories", e);
517N/A }
578N/A } else {
1327N/A logger.warning("Skipping update of " + type + " repository in '"
1327N/A + path + "': Missing SCM dependencies?");
203N/A }
203N/A }
203N/A }
1190N/A
668N/A /**
668N/A * Update the source the contents in the source repositories.
668N/A * @param paths A list of files/directories to update
668N/A */
1182N/A public void updateRepositories(Collection<String> paths) {
1470N/A boolean verbose = RuntimeEnvironment.getConfig().isVerbose();
668N/A
1182N/A List<Repository> repos = getReposFromString(paths);
668N/A
668N/A for (Repository repository : repos) {
668N/A String type = repository.getClass().getSimpleName();
668N/A
668N/A if (repository.isWorking()) {
668N/A if (verbose) {
1327N/A logger.log(Level.INFO, "Update {0} repository in ''{1}''",
1327N/A new String[] { type, repository.getDirectoryName()});
668N/A }
668N/A
668N/A try {
668N/A repository.update();
668N/A } catch (UnsupportedOperationException e) {
1327N/A logger.warning("Skipping update of " + type + " repository"
1327N/A + " in '" + repository.getDirectoryName()
1327N/A + "': Not implemented");
668N/A } catch (Exception e) {
1327N/A logger.warning("An error occured while updating '"
1327N/A + repository.getDirectoryName() + "' (" + type + "): "
1327N/A + e.getMessage());
1327N/A logger.log(Level.FINE, "updateRepositories", e);
668N/A }
668N/A } else {
1327N/A logger.warning("Skipping update of " + type + " repository in '"
1327N/A + repository.getDirectoryName() + "': Missing SCM dependencies?");
668N/A }
668N/A }
668N/A }
668N/A
771N/A private void createCache(Repository repository, String sinceRevision) {
772N/A if (!useCache()) {
772N/A return;
772N/A }
772N/A
240N/A String path = repository.getDirectoryName();
240N/A String type = repository.getClass().getSimpleName();
543N/A
578N/A if (repository.isWorking()) {
1470N/A boolean verbose = RuntimeEnvironment.getConfig().isVerbose();
543N/A long start = System.currentTimeMillis();
543N/A
543N/A if (verbose) {
1327N/A logger.log(Level.INFO, "Create historycache for ''{0}'' ({1})",
1327N/A new String[]{path, type});
543N/A }
240N/A
543N/A try {
771N/A repository.createCache(historyCache, sinceRevision);
543N/A } catch (Exception e) {
1327N/A logger.warning("An error occured while creating cache for '"
1327N/A + path + "' (" + type + "): " + e.getMessage());
1327N/A logger.log(Level.FINE, "createCache", e);
543N/A }
543N/A
543N/A if (verbose) {
543N/A long stop = System.currentTimeMillis();
1327N/A logger.log(Level.INFO, "Creating historycache for ''{0}'' took {1}ms",
1327N/A new String[]{path, String.valueOf(stop - start)});
543N/A }
578N/A } else {
1327N/A logger.warning("Skipping creation of historycache of "
1327N/A + type + " repository in '" + path + "': Missing SCM dependencies?");
240N/A }
240N/A }
543N/A
1474N/A /**
1474N/A * Create the history cache for the given repositories, if caching is
1474N/A * enabled and the repository in question supports history for directories.
1474N/A * @param allRepos repositories in question. If {@code null} all registered
1474N/A * repos are used instead.
1474N/A */
1474N/A public void createCache(Collection<String> allRepos) {
1474N/A if (!useCache()) {
1474N/A return;
1474N/A }
1474N/A Collection<Repository> repositories = allRepos == null
1474N/A ? this.repositories.values()
1474N/A : getReposFromString(allRepos);
1375N/A int num = Runtime.getRuntime().availableProcessors() * 2;
1462N/A String total = System.getProperty(Configuration.PROPERTY_KEY_PREFIX
1462N/A + "history.NumCacheThreads");
1375N/A if (total != null) {
1375N/A try {
1382N/A num = Integer.parseInt(total, 10);
1375N/A } catch (Throwable t) {
1382N/A logger.warning("Failed to parse the number of cache threads to use for cache creation: "
1382N/A + t.getMessage());
1382N/A logger.log(Level.FINE, "createCacheReal", t);
1375N/A }
1375N/A }
1375N/A ExecutorService executor = Executors.newFixedThreadPool(num);
701N/A
701N/A for (final Repository repos : repositories) {
771N/A final String latestRev;
771N/A try {
771N/A latestRev = historyCache.getLatestCachedRevision(repos);
771N/A } catch (HistoryException he) {
1327N/A logger.warning("Failed to retrieve latest cached revision for '"
1327N/A + repos.getDirectoryName() + "': " + he.getMessage());
1327N/A logger.log(Level.FINE, "createCacheReal", he);
771N/A continue;
771N/A }
701N/A executor.submit(new Runnable() {
1461N/A @SuppressWarnings("synthetic-access")
815N/A @Override
701N/A public void run() {
771N/A createCache(repos, latestRev);
701N/A }
701N/A });
701N/A }
701N/A executor.shutdown();
701N/A while (!executor.isTerminated()) {
701N/A try {
1190N/A // Wait forever
1461N/A executor.awaitTermination(999, TimeUnit.DAYS);
701N/A } catch (InterruptedException exp) {
1327N/A logger.warning("Received interrupt while waiting for executor to finish: "
1327N/A + exp.getMessage());
1327N/A logger.log(Level.FINE, "createCacheReal", exp);
701N/A }
543N/A }
795N/A
795N/A // The cache has been populated. Now, optimize how it is stored on
795N/A // disk to enhance performance and save space.
795N/A try {
795N/A historyCache.optimize();
795N/A } catch (HistoryException he) {
1327N/A logger.warning("Failed optimizing the history cache database: "
1327N/A + he.getMessage());
1327N/A logger.log(Level.FINE, "createCacheReal", he);
795N/A }
543N/A }
543N/A
1461N/A /**
1461N/A * Remove the history cache for the given repositories.
1461N/A * @param repositories repositories in question.
1461N/A * @throws HistoryException
1461N/A */
1182N/A public void removeCache(Collection<String> repositories) throws HistoryException {
1182N/A List<Repository> repos = getReposFromString(repositories);
1182N/A HistoryCache cache = historyCache;
1182N/A if (cache == null) {
1470N/A if (RuntimeEnvironment.getConfig().isHistoryCacheInDB()) {
1182N/A cache = new JDBCHistoryCache();
1182N/A cache.initialize();
1182N/A } else {
1182N/A cache = new FileHistoryCache();
1182N/A }
1182N/A }
1182N/A for (Repository r : repos) {
1182N/A try {
1182N/A cache.clear(r);
1327N/A logger.info("History cache for '" + r.getDirectoryName() + "' cleared.");
1182N/A } catch (HistoryException e) {
1327N/A logger.warning("Clearing history cache for repository '" +
1327N/A r.getDirectoryName() + "' failed: " + e.getLocalizedMessage());
1182N/A }
1182N/A }
1182N/A invalidateRepositories(repos);
1182N/A }
1182N/A
203N/A /**
1474N/A * Create the history cache for all of the repositories.
203N/A */
203N/A public void createCache() {
1474N/A createCache(null);
240N/A }
240N/A
1182N/A private List<Repository> getReposFromString(Collection<String> repositories) {
543N/A ArrayList<Repository> repos = new ArrayList<Repository>();
1470N/A File root = RuntimeEnvironment.getConfig().getSourceRootFile();
240N/A for (String file : repositories) {
543N/A File f = new File(root, file);
543N/A Repository r = getRepository(f);
578N/A if (r == null) {
1327N/A logger.warning("Could not locate a repository for '"
1327N/A + f.getAbsolutePath() + "'");
1182N/A } else if (!repos.contains(r)){
543N/A repos.add(r);
203N/A }
240N/A }
1182N/A return repos;
203N/A }
1190N/A
651N/A /**
1466N/A * Check, whether the given path denotes a directory in the history cache.
1466N/A * This really hits the DB, so caching is advised.
1466N/A *
1466N/A * @param path The <em>canonical</em> path to the file in question,
1466N/A * e.g. sourceRoot + '/' + projectdir + '/' + subdir with '/'
1474N/A * path separators, only! One trailing slash gets automatically removed
1474N/A * if it exists.
1466N/A * @return {@code null} if an error occured, {@code Boolean#TRUE} if there
1466N/A * is a directory with the given name in the history cache,
1466N/A * {@link Boolean#FALSE} otherwise.
1466N/A */
1466N/A public Boolean isDirectory(String path) {
1474N/A if (!useCache() || path == null) {
1474N/A return null;
1474N/A }
1466N/A File dir = new File(path);
1466N/A Repository repo = getRepository(dir);
1466N/A try {
1474N/A return Boolean.valueOf(historyCache.hasCacheForDirectory(dir, repo));
1466N/A } catch (HistoryException e) {
1466N/A logger.warning(e.getLocalizedMessage());
1466N/A logger.log(Level.FINE, "isDirectory", e);
1466N/A return null;
1466N/A }
1466N/A }
1466N/A
1466N/A /**
771N/A * Ensure that we have a directory in the cache. If it's not there, fetch
771N/A * its history and populate the cache. If it's already there, and the
771N/A * cache is able to tell how recent it is, attempt to update it to the
771N/A * most recent revision.
651N/A *
1473N/A * @param file the root path to test (canonical path incl. source root).
678N/A * @throws HistoryException if an error occurs while accessing the
678N/A * history cache
651N/A */
764N/A public void ensureHistoryCacheExists(File file) throws HistoryException {
772N/A if (!useCache()) {
772N/A return;
772N/A }
772N/A
715N/A Repository repository = getRepository(file);
771N/A
771N/A if (repository == null) {
771N/A // no repository -> no history :(
771N/A return;
687N/A }
771N/A
771N/A String sinceRevision = null;
771N/A
771N/A if (historyCache.hasCacheForDirectory(file, repository)) {
771N/A sinceRevision = historyCache.getLatestCachedRevision(repository);
771N/A if (sinceRevision == null) {
771N/A // Cache already exists, but we don't know how recent it is,
771N/A // so don't do anything.
771N/A return;
771N/A }
771N/A }
771N/A
771N/A // Create cache from the beginning if it doesn't exist, or update it
771N/A // incrementally otherwise.
771N/A createCache(getRepository(file), sinceRevision);
687N/A }
651N/A
1461N/A /**
1461N/A * Get the with this instance registered repository for the given file.
1473N/A * @param path canonical path incl. source root to the source file in
1473N/A * question.
1461N/A * @return {@code null} if unknown, the related repository otherwise.
1461N/A */
687N/A protected Repository getRepository(File path) {
664N/A Map<String, Repository> repos = repositories;
439N/A
543N/A File file = path;
212N/A try {
543N/A file = path.getCanonicalFile();
212N/A } catch (IOException e) {
1327N/A logger.warning("Failed to get canonical path for '" + path + "': "
1327N/A + e.getLocalizedMessage());
212N/A return null;
212N/A }
543N/A while (file != null) {
543N/A Repository r = repos.get(file.getAbsolutePath());
543N/A if (r != null) {
543N/A return r;
8N/A }
543N/A file = file.getParentFile();
8N/A }
96N/A
96N/A return null;
664N/A }
664N/A
664N/A /**
664N/A * Invalidate the current list of known repositories!
1190N/A *
664N/A * @param repos The new repositories
664N/A */
1190N/A public void invalidateRepositories(Collection<? extends RepositoryInfo> repos)
1182N/A {
693N/A if (repos == null || repos.isEmpty()) {
693N/A repositories.clear();
693N/A } else {
1190N/A Map<String, Repository> nrep =
1183N/A new HashMap<String, Repository>(repos.size());
693N/A for (RepositoryInfo i : repos) {
693N/A try {
693N/A Repository r = RepositoryFactory.getRepository(i);
693N/A if (r == null) {
1327N/A logger.warning("Failed to instanciate internal repository data for "
1327N/A + i.getType() + " in '" + i.getDirectoryName() + "'");
693N/A } else {
693N/A nrep.put(r.getDirectoryName(), r);
693N/A }
693N/A } catch (InstantiationException ex) {
1327N/A logger.warning("Could not create " + i.getType()
1190N/A + " for '" + i.getDirectoryName()
1327N/A + "', could not instantiate the repository: "
1327N/A + ex.getMessage());
1327N/A logger.log(Level.FINE, "invalidateRepositories", ex);
693N/A } catch (IllegalAccessException iae) {
1327N/A logger.warning("Could not create " + i.getType()
1190N/A + " for '" + i.getDirectoryName()
1327N/A + "', missing access rights: " + iae.getMessage());
1327N/A logger.log(Level.FINE, "invalidateRepositories", iae);
664N/A }
664N/A }
693N/A repositories = nrep;
664N/A }
664N/A }
0N/A}