import java.util.TimeZone;\r
import java.util.TreeSet;\r
import java.util.concurrent.ConcurrentHashMap;\r
-import java.util.concurrent.CopyOnWriteArrayList;\r
import java.util.concurrent.Executors;\r
import java.util.concurrent.ScheduledExecutorService;\r
import java.util.concurrent.TimeUnit;\r
import org.eclipse.jgit.lib.Repository;\r
import org.eclipse.jgit.lib.RepositoryCache.FileKey;\r
import org.eclipse.jgit.lib.StoredConfig;\r
+import org.eclipse.jgit.storage.file.FileBasedConfig;\r
import org.eclipse.jgit.storage.file.WindowCache;\r
import org.eclipse.jgit.storage.file.WindowCacheConfig;\r
import org.eclipse.jgit.transport.ServiceMayNotContinueException;\r
import com.gitblit.models.UserModel;\r
import com.gitblit.utils.ArrayUtils;\r
import com.gitblit.utils.ByteFormat;\r
+import com.gitblit.utils.DeepCopier;\r
import com.gitblit.utils.FederationUtils;\r
import com.gitblit.utils.JGitUtils;\r
import com.gitblit.utils.JsonUtils;\r
\r
private final ObjectCache<List<Metric>> repositoryMetricsCache = new ObjectCache<List<Metric>>();\r
\r
- private final List<String> repositoryListCache = new CopyOnWriteArrayList<String>();\r
+ private final Map<String, RepositoryModel> repositoryListCache = new ConcurrentHashMap<String, RepositoryModel>();\r
\r
private final AtomicReference<String> repositoryListSettingsChecksum = new AtomicReference<String>("");\r
\r
* \r
* @param name\r
*/\r
- private void addToCachedRepositoryList(String name) {\r
+ private void addToCachedRepositoryList(String name, RepositoryModel model) {\r
if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {\r
- repositoryListCache.add(name);\r
+ repositoryListCache.put(name, model);\r
}\r
}\r
+ \r
+ /**\r
+ * Removes the repository from the list of cached repositories.\r
+ * \r
+ * @param name\r
+ */\r
+ private void removeFromCachedRepositoryList(String name) {\r
+ if (StringUtils.isEmpty(name)) {\r
+ return;\r
+ }\r
+ repositoryListCache.remove(name);\r
+ }\r
\r
/**\r
- * Clears all the cached data for the specified repository.\r
+ * Clears all the cached metadata for the specified repository.\r
* \r
* @param repositoryName\r
- * @param isDeleted\r
*/\r
- private void clearRepositoryCache(String repositoryName, boolean isDeleted) {\r
+ private void clearRepositoryMetadataCache(String repositoryName) {\r
repositorySizeCache.remove(repositoryName);\r
repositoryMetricsCache.remove(repositoryName);\r
- \r
- if (isDeleted) {\r
- repositoryListCache.remove(repositoryName);\r
- }\r
}\r
\r
/**\r
calculateSize(model);\r
}\r
}\r
+ } else {\r
+ // update cache\r
+ for (String repository : repositories) {\r
+ getRepositoryModel(repository);\r
+ }\r
}\r
\r
- // update cache\r
- repositoryListCache.addAll(repositories);\r
- \r
long duration = System.currentTimeMillis() - startTime;\r
logger.info(MessageFormat.format(msg, repositoryListCache.size(), duration));\r
}\r
}\r
\r
// return sorted copy of cached list\r
- List<String> list = new ArrayList<String>(repositoryListCache); \r
+ List<String> list = new ArrayList<String>(repositoryListCache.keySet()); \r
StringUtils.sortRepositorynames(list);\r
return list;\r
}\r
* @return repository model or null\r
*/\r
public RepositoryModel getRepositoryModel(String repositoryName) {\r
+ if (!repositoryListCache.containsKey(repositoryName)) {\r
+ RepositoryModel model = loadRepositoryModel(repositoryName);\r
+ if (model == null) {\r
+ return null;\r
+ }\r
+ addToCachedRepositoryList(repositoryName, model); \r
+ return model;\r
+ }\r
+ \r
+ // cached model\r
+ RepositoryModel model = repositoryListCache.get(repositoryName);\r
+ \r
+ // check for updates\r
+ Repository r = getRepository(repositoryName);\r
+ if (r == null) {\r
+ // repository is missing\r
+ removeFromCachedRepositoryList(repositoryName);\r
+ logger.error(MessageFormat.format("Repository \"{0}\" is missing! Removing from cache.", repositoryName));\r
+ return null;\r
+ }\r
+ \r
+ FileBasedConfig config = (FileBasedConfig) getRepositoryConfig(r);\r
+ if (config.isOutdated()) {\r
+ // reload model\r
+ logger.info(MessageFormat.format("Config for \"{0}\" has changed. Reloading model and updating cache.", repositoryName));\r
+ model = loadRepositoryModel(repositoryName);\r
+ removeFromCachedRepositoryList(repositoryName);\r
+ addToCachedRepositoryList(repositoryName, model);\r
+ } else {\r
+ // update a few repository parameters \r
+ if (!model.hasCommits) {\r
+ // update hasCommits, assume a repository only gains commits :)\r
+ model.hasCommits = JGitUtils.hasCommits(r);\r
+ }\r
+\r
+ model.lastChange = JGitUtils.getLastChange(r);\r
+ }\r
+ r.close();\r
+ \r
+ // return a copy of the cached model\r
+ return DeepCopier.copy(model);\r
+ }\r
+ \r
+ /**\r
+ * Workaround JGit. I need to access the raw config object directly in order\r
+ * to see if the config is dirty so that I can reload a repository model.\r
+ * If I use the stock JGit method to get the config it already reloads the\r
+ * config. If the config changes are made within Gitblit this is fine as\r
+ * the returned config will still be flagged as dirty. BUT... if the config\r
+ * is manipulated outside Gitblit then it fails to recognize this as dirty.\r
+ * \r
+ * @param r\r
+ * @return a config\r
+ */\r
+ private StoredConfig getRepositoryConfig(Repository r) {\r
+ try {\r
+ Field f = r.getClass().getDeclaredField("repoConfig");\r
+ f.setAccessible(true);\r
+ StoredConfig config = (StoredConfig) f.get(r);\r
+ return config;\r
+ } catch (Exception e) {\r
+ logger.error("Failed to retrieve \"repoConfig\" via reflection", e);\r
+ }\r
+ return r.getConfig();\r
+ }\r
+ \r
+ /**\r
+ * Create a repository model from the configuration and repository data.\r
+ * \r
+ * @param repositoryName\r
+ * @return a repositoryModel or null if the repository does not exist\r
+ */\r
+ private RepositoryModel loadRepositoryModel(String repositoryName) {\r
Repository r = getRepository(repositoryName);\r
if (r == null) {\r
return null;\r
* @return true if the repository exists\r
*/\r
public boolean hasRepository(String repositoryName) {\r
+ if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {\r
+ // if we are caching use the cache to determine availability\r
+ // otherwise we end up adding a phantom repository to the cache\r
+ return repositoryListCache.containsKey(repositoryName);\r
+ } \r
Repository r = getRepository(repositoryName, false);\r
if (r == null) {\r
return false;\r
// create repository\r
logger.info("create repository " + repository.name);\r
r = JGitUtils.createRepository(repositoriesFolder, repository.name);\r
- \r
- // add name to cache\r
- addToCachedRepositoryList(repository.name);\r
} else {\r
// rename repository\r
if (!repositoryName.equalsIgnoreCase(repository.name)) {\r
}\r
\r
// clear the cache\r
- clearRepositoryCache(repositoryName, true);\r
- \r
- // add new name to repository list cache\r
- addToCachedRepositoryList(repository.name);\r
+ clearRepositoryMetadataCache(repositoryName);\r
}\r
\r
// load repository\r
repository.name, currentRef, repository.HEAD));\r
if (JGitUtils.setHEADtoRef(r, repository.HEAD)) {\r
// clear the cache\r
- clearRepositoryCache(repository.name, false);\r
+ clearRepositoryMetadataCache(repository.name);\r
}\r
}\r
\r
// close the repository object\r
r.close();\r
}\r
+ \r
+ // update repository cache\r
+ removeFromCachedRepositoryList(repositoryName);\r
+ // model will actually be replaced on next load because config is stale\r
+ addToCachedRepositoryList(repository.name, repository);\r
}\r
\r
/**\r
try {\r
closeRepository(repositoryName);\r
// clear the repository cache\r
- clearRepositoryCache(repositoryName, true); \r
+ clearRepositoryMetadataCache(repositoryName);\r
+ removeFromCachedRepositoryList(repositoryName);\r
\r
File folder = new File(repositoriesFolder, repositoryName);\r
if (folder.exists() && folder.isDirectory()) {\r
FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY);\r
if (userService.deleteRepositoryRole(repositoryName)) {\r
+ logger.info(MessageFormat.format("Repository \"{0}\" deleted", repositoryName));\r
return true;\r
}\r
}\r