/*
|
* Copyright 2013 gitblit.com.
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
package com.gitblit.utils;
|
|
import java.text.MessageFormat;
|
import java.util.ArrayList;
|
import java.util.Calendar;
|
import java.util.Date;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.TimeUnit;
|
|
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
|
import com.gitblit.models.RefModel;
|
import com.gitblit.models.RepositoryCommit;
|
|
/**
|
* Caches repository commits for re-use in the dashboard and activity pages.
|
*
|
* @author James Moger
|
*
|
*/
|
public class CommitCache {
|
|
private static final CommitCache instance;
|
|
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
|
protected final Map<String, ObjectCache<List<RepositoryCommit>>> cache;
|
|
protected int cacheDays = -1;
|
|
public static CommitCache instance() {
|
return instance;
|
}
|
|
static {
|
instance = new CommitCache();
|
}
|
|
protected CommitCache() {
|
cache = new ConcurrentHashMap<String, ObjectCache<List<RepositoryCommit>>>();
|
}
|
|
/**
|
* Returns the cutoff date for the cache. Commits after this date are cached.
|
* Commits before this date are not cached.
|
*
|
* @return
|
*/
|
protected Date getCacheCutoffDate() {
|
final Calendar cal = Calendar.getInstance();
|
cal.setTimeInMillis(System.currentTimeMillis());
|
cal.set(Calendar.HOUR_OF_DAY, 0);
|
cal.set(Calendar.MINUTE, 0);
|
cal.set(Calendar.SECOND, 0);
|
cal.set(Calendar.MILLISECOND, 0);
|
cal.add(Calendar.DATE, -1*cacheDays);
|
return cal.getTime();
|
}
|
|
/**
|
* Sets the number of days to cache.
|
*
|
* @param days
|
*/
|
public synchronized void setCacheDays(int days) {
|
this.cacheDays = days;
|
clear();
|
}
|
|
/**
|
* Clears the entire commit cache.
|
*
|
*/
|
public void clear() {
|
cache.clear();
|
}
|
|
/**
|
* Clears the commit cache for a specific repository.
|
*
|
* @param repositoryName
|
*/
|
public void clear(String repositoryName) {
|
String repoKey = repositoryName.toLowerCase();
|
ObjectCache<List<RepositoryCommit>> repoCache = cache.remove(repoKey);
|
if (repoCache != null) {
|
logger.info(MessageFormat.format("{0} commit cache cleared", repositoryName));
|
}
|
}
|
|
/**
|
* Get all commits for the specified repository:branch that are in the cache.
|
*
|
* @param repositoryName
|
* @param repository
|
* @param branch
|
* @return a list of commits
|
*/
|
public List<RepositoryCommit> getCommits(String repositoryName, Repository repository, String branch) {
|
return getCommits(repositoryName, repository, branch, getCacheCutoffDate());
|
}
|
|
/**
|
* Get all commits for the specified repository:branch since a specific date.
|
* These commits may be retrieved from the cache if the sinceDate is after
|
* the cacheCutoffDate.
|
*
|
* @param repositoryName
|
* @param repository
|
* @param branch
|
* @param sinceDate
|
* @return a list of commits
|
*/
|
public List<RepositoryCommit> getCommits(String repositoryName, Repository repository, String branch, Date sinceDate) {
|
long start = System.nanoTime();
|
Date cacheCutoffDate = getCacheCutoffDate();
|
List<RepositoryCommit> list;
|
if (cacheDays > 0 && (sinceDate.getTime() >= cacheCutoffDate.getTime())) {
|
// request fits within the cache window
|
String repoKey = repositoryName.toLowerCase();
|
if (!cache.containsKey(repoKey)) {
|
cache.put(repoKey, new ObjectCache<List<RepositoryCommit>>());
|
}
|
|
ObjectCache<List<RepositoryCommit>> repoCache = cache.get(repoKey);
|
String branchKey = branch.toLowerCase();
|
|
RevCommit tip = JGitUtils.getCommit(repository, branch);
|
Date tipDate = JGitUtils.getCommitDate(tip);
|
|
List<RepositoryCommit> commits;
|
if (!repoCache.hasCurrent(branchKey, tipDate)) {
|
commits = repoCache.getObject(branchKey);
|
if (ArrayUtils.isEmpty(commits)) {
|
// we don't have any cached commits for this branch, reload
|
commits = get(repositoryName, repository, branch, cacheCutoffDate);
|
repoCache.updateObject(branchKey, tipDate, commits);
|
logger.debug(MessageFormat.format("parsed {0} commits from {1}:{2} since {3,date,yyyy-MM-dd} in {4} msecs",
|
commits.size(), repositoryName, branch, cacheCutoffDate, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
|
} else {
|
// incrementally update cache since the last cached commit
|
ObjectId sinceCommit = commits.get(0).getId();
|
List<RepositoryCommit> incremental = get(repositoryName, repository, branch, sinceCommit);
|
logger.info(MessageFormat.format("incrementally added {0} commits to cache for {1}:{2} in {3} msecs",
|
incremental.size(), repositoryName, branch, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
|
incremental.addAll(commits);
|
repoCache.updateObject(branchKey, tipDate, incremental);
|
commits = incremental;
|
}
|
} else {
|
// cache is current
|
commits = repoCache.getObject(branchKey);
|
// evict older commits outside the cache window
|
commits = reduce(commits, cacheCutoffDate);
|
// update cache
|
repoCache.updateObject(branchKey, tipDate, commits);
|
}
|
|
if (sinceDate.equals(cacheCutoffDate)) {
|
list = commits;
|
} else {
|
// reduce the commits to those since the specified date
|
list = reduce(commits, sinceDate);
|
}
|
logger.debug(MessageFormat.format("retrieved {0} commits from cache of {1}:{2} since {3,date,yyyy-MM-dd} in {4} msecs",
|
list.size(), repositoryName, branch, sinceDate, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
|
} else {
|
// not caching or request outside cache window
|
list = get(repositoryName, repository, branch, sinceDate);
|
logger.debug(MessageFormat.format("parsed {0} commits from {1}:{2} since {3,date,yyyy-MM-dd} in {4} msecs",
|
list.size(), repositoryName, branch, sinceDate, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
|
}
|
return list;
|
}
|
|
/**
|
* Returns a list of commits for the specified repository branch.
|
*
|
* @param repositoryName
|
* @param repository
|
* @param branch
|
* @param sinceDate
|
* @return a list of commits
|
*/
|
protected List<RepositoryCommit> get(String repositoryName, Repository repository, String branch, Date sinceDate) {
|
Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository, false);
|
List<RepositoryCommit> commits = new ArrayList<RepositoryCommit>();
|
for (RevCommit commit : JGitUtils.getRevLog(repository, branch, sinceDate)) {
|
RepositoryCommit commitModel = new RepositoryCommit(repositoryName, branch, commit);
|
commitModel.setRefs(allRefs.get(commitModel.getName()));
|
commits.add(commitModel);
|
}
|
return commits;
|
}
|
|
/**
|
* Returns a list of commits for the specified repository branch since the specified commit.
|
*
|
* @param repositoryName
|
* @param repository
|
* @param branch
|
* @param sinceCommit
|
* @return a list of commits
|
*/
|
protected List<RepositoryCommit> get(String repositoryName, Repository repository, String branch, ObjectId sinceCommit) {
|
Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository, false);
|
List<RepositoryCommit> commits = new ArrayList<RepositoryCommit>();
|
for (RevCommit commit : JGitUtils.getRevLog(repository, sinceCommit.getName(), branch)) {
|
RepositoryCommit commitModel = new RepositoryCommit(repositoryName, branch, commit);
|
commitModel.setRefs(allRefs.get(commitModel.getName()));
|
commits.add(commitModel);
|
}
|
return commits;
|
}
|
|
/**
|
* Reduces the list of commits to those since the specified date.
|
*
|
* @param commits
|
* @param sinceDate
|
* @return a list of commits
|
*/
|
protected List<RepositoryCommit> reduce(List<RepositoryCommit> commits, Date sinceDate) {
|
List<RepositoryCommit> filtered = new ArrayList<RepositoryCommit>();
|
for (RepositoryCommit commit : commits) {
|
if (commit.getCommitDate().compareTo(sinceDate) >= 0) {
|
filtered.add(commit);
|
}
|
}
|
return filtered;
|
}
|
}
|