From e23cd08c09ac25b671edd07895596b2ad274e5d7 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 12 Oct 2012 18:04:17 -0400
Subject: [PATCH] Added an IntelliJ module file for fun
---
src/com/gitblit/GitBlit.java | 664 +++++++++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 589 insertions(+), 75 deletions(-)
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 0940c76..36f5f35 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -22,6 +22,8 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -30,6 +32,7 @@
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -37,9 +40,9 @@
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
+import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -54,22 +57,19 @@
import javax.servlet.http.Cookie;
import org.apache.wicket.protocol.http.WebResponse;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache;
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.storage.file.WindowCache;
import org.eclipse.jgit.storage.file.WindowCacheConfig;
-import org.eclipse.jgit.transport.ServiceMayNotContinueException;
-import org.eclipse.jgit.transport.resolver.FileResolver;
-import org.eclipse.jgit.transport.resolver.RepositoryResolver;
-import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
-import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.FederationRequest;
@@ -78,7 +78,9 @@
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
+import com.gitblit.models.ForkModel;
import com.gitblit.models.Metric;
+import com.gitblit.models.ProjectModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.SearchResult;
import com.gitblit.models.ServerSettings;
@@ -88,6 +90,8 @@
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.ByteFormat;
+import com.gitblit.utils.ContainerUtils;
+import com.gitblit.utils.DeepCopier;
import com.gitblit.utils.FederationUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.JsonUtils;
@@ -128,11 +132,11 @@
private final ObjectCache<List<Metric>> repositoryMetricsCache = new ObjectCache<List<Metric>>();
- private final List<String> repositoryListCache = new CopyOnWriteArrayList<String>();
+ private final Map<String, RepositoryModel> repositoryListCache = new ConcurrentHashMap<String, RepositoryModel>();
+
+ private final Map<String, ProjectModel> projectCache = new ConcurrentHashMap<String, ProjectModel>();
private final AtomicReference<String> repositoryListSettingsChecksum = new AtomicReference<String>("");
-
- private RepositoryResolver<Void> repositoryResolver;
private ServletContext servletContext;
@@ -151,6 +155,8 @@
private LuceneExecutor luceneExecutor;
private TimeZone timezone;
+
+ private FileBasedConfig projectConfigs;
public GitBlit() {
if (gitblit == null) {
@@ -613,6 +619,7 @@
* @param usernames
* @return true if successful
*/
+ @Deprecated
public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) {
return userService.setUsernamesForRepositoryRole(repository.name, repositoryUsers);
}
@@ -634,6 +641,22 @@
throw new GitBlitException(MessageFormat.format(
"Failed to rename ''{0}'' because ''{1}'' already exists.", username,
user.username));
+ }
+
+ // rename repositories and owner fields for all repositories
+ for (RepositoryModel model : getRepositoryModels(user)) {
+ if (model.isUsersPersonalRepository(username)) {
+ // personal repository
+ model.owner = user.username;
+ String oldRepositoryName = model.name;
+ model.name = "~" + user.username + model.name.substring(model.projectPath.length());
+ model.projectPath = "~" + user.username;
+ updateRepositoryModel(oldRepositoryName, model, false);
+ } else if (model.isOwner(username)) {
+ // common/shared repo
+ model.owner = user.username;
+ updateRepositoryModel(model.name, model, false);
+ }
}
}
if (!userService.updateUserModel(username, user)) {
@@ -694,6 +717,7 @@
* @param teamnames
* @return true if successful
*/
+ @Deprecated
public boolean setRepositoryTeams(RepositoryModel repository, List<String> repositoryTeams) {
return userService.setTeamnamesForRepositoryRole(repository.name, repositoryTeams);
}
@@ -734,27 +758,43 @@
* Adds the repository to the list of cached repositories if Gitblit is
* configured to cache the repository list.
*
- * @param name
+ * @param model
*/
- private void addToCachedRepositoryList(String name) {
+ private void addToCachedRepositoryList(RepositoryModel model) {
if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
- repositoryListCache.add(name);
+ repositoryListCache.put(model.name, model);
+
+ // update the fork origin repository with this repository clone
+ if (!StringUtils.isEmpty(model.originRepository)) {
+ if (repositoryListCache.containsKey(model.originRepository)) {
+ RepositoryModel origin = repositoryListCache.get(model.originRepository);
+ origin.addFork(model.name);
+ }
+ }
}
+ }
+
+ /**
+ * Removes the repository from the list of cached repositories.
+ *
+ * @param name
+ * @return the model being removed
+ */
+ private RepositoryModel removeFromCachedRepositoryList(String name) {
+ if (StringUtils.isEmpty(name)) {
+ return null;
+ }
+ return repositoryListCache.remove(name);
}
/**
- * Clears all the cached data for the specified repository.
+ * Clears all the cached metadata for the specified repository.
*
* @param repositoryName
- * @param isDeleted
*/
- private void clearRepositoryCache(String repositoryName, boolean isDeleted) {
+ private void clearRepositoryMetadataCache(String repositoryName) {
repositorySizeCache.remove(repositoryName);
repositoryMetricsCache.remove(repositoryName);
-
- if (isDeleted) {
- repositoryListCache.remove(repositoryName);
- }
}
/**
@@ -831,10 +871,12 @@
calculateSize(model);
}
}
+ } else {
+ // update cache
+ for (String repository : repositories) {
+ getRepositoryModel(repository);
+ }
}
-
- // update cache
- repositoryListCache.addAll(repositories);
long duration = System.currentTimeMillis() - startTime;
logger.info(MessageFormat.format(msg, repositoryListCache.size(), duration));
@@ -842,7 +884,7 @@
}
// return sorted copy of cached list
- List<String> list = new ArrayList<String>(repositoryListCache);
+ List<String> list = new ArrayList<String>(repositoryListCache.keySet());
StringUtils.sortRepositorynames(list);
return list;
}
@@ -865,32 +907,18 @@
* @return repository or null
*/
public Repository getRepository(String repositoryName, boolean logError) {
+ File dir = FileKey.resolve(new File(repositoriesFolder, repositoryName), FS.DETECTED);
+ if (dir == null)
+ return null;
+
Repository r = null;
try {
- r = repositoryResolver.open(null, repositoryName);
- } catch (RepositoryNotFoundException e) {
- r = null;
+ FileKey key = FileKey.exact(dir, FS.DETECTED);
+ r = RepositoryCache.open(key, true);
+ } catch (IOException e) {
if (logError) {
logger.error("GitBlit.getRepository(String) failed to find "
+ new File(repositoriesFolder, repositoryName).getAbsolutePath());
- }
- } catch (ServiceNotAuthorizedException e) {
- r = null;
- if (logError) {
- logger.error("GitBlit.getRepository(String) failed to find "
- + new File(repositoriesFolder, repositoryName).getAbsolutePath(), e);
- }
- } catch (ServiceNotEnabledException e) {
- r = null;
- if (logError) {
- logger.error("GitBlit.getRepository(String) failed to find "
- + new File(repositoriesFolder, repositoryName).getAbsolutePath(), e);
- }
- } catch (ServiceMayNotContinueException e) {
- r = null;
- if (logError) {
- logger.error("GitBlit.getRepository(String) failed to find "
- + new File(repositoriesFolder, repositoryName).getAbsolutePath(), e);
}
}
return r;
@@ -948,14 +976,13 @@
if (model == null) {
return null;
}
- if (model.accessRestriction.atLeast(AccessRestrictionType.VIEW)) {
- if (user != null && user.canAccessRepository(model)) {
- return model;
- }
- return null;
- } else {
+ if (user == null) {
+ user = UserModel.ANONYMOUS;
+ }
+ if (user.canView(model)) {
return model;
}
+ return null;
}
/**
@@ -966,27 +993,272 @@
* @return repository model or null
*/
public RepositoryModel getRepositoryModel(String repositoryName) {
+ if (!repositoryListCache.containsKey(repositoryName)) {
+ RepositoryModel model = loadRepositoryModel(repositoryName);
+ if (model == null) {
+ return null;
+ }
+ addToCachedRepositoryList(model);
+ return model;
+ }
+
+ // cached model
+ RepositoryModel model = repositoryListCache.get(repositoryName);
+
+ // check for updates
+ Repository r = getRepository(repositoryName);
+ if (r == null) {
+ // repository is missing
+ removeFromCachedRepositoryList(repositoryName);
+ logger.error(MessageFormat.format("Repository \"{0}\" is missing! Removing from cache.", repositoryName));
+ return null;
+ }
+
+ FileBasedConfig config = (FileBasedConfig) getRepositoryConfig(r);
+ if (config.isOutdated()) {
+ // reload model
+ logger.info(MessageFormat.format("Config for \"{0}\" has changed. Reloading model and updating cache.", repositoryName));
+ model = loadRepositoryModel(repositoryName);
+ removeFromCachedRepositoryList(repositoryName);
+ addToCachedRepositoryList(model);
+ } else {
+ // update a few repository parameters
+ if (!model.hasCommits) {
+ // update hasCommits, assume a repository only gains commits :)
+ model.hasCommits = JGitUtils.hasCommits(r);
+ }
+
+ model.lastChange = JGitUtils.getLastChange(r);
+ }
+ r.close();
+
+ // return a copy of the cached model
+ return DeepCopier.copy(model);
+ }
+
+
+ /**
+ * Returns the map of project config. This map is cached and reloaded if
+ * the underlying projects.conf file changes.
+ *
+ * @return project config map
+ */
+ private Map<String, ProjectModel> getProjectConfigs() {
+ if (projectCache.isEmpty() || projectConfigs.isOutdated()) {
+
+ try {
+ projectConfigs.load();
+ } catch (Exception e) {
+ }
+
+ // project configs
+ String rootName = GitBlit.getString(Keys.web.repositoryRootGroupName, "main");
+ ProjectModel rootProject = new ProjectModel(rootName, true);
+
+ Map<String, ProjectModel> configs = new HashMap<String, ProjectModel>();
+ // cache the root project under its alias and an empty path
+ configs.put("", rootProject);
+ configs.put(rootProject.name.toLowerCase(), rootProject);
+
+ for (String name : projectConfigs.getSubsections("project")) {
+ ProjectModel project;
+ if (name.equalsIgnoreCase(rootName)) {
+ project = rootProject;
+ } else {
+ project = new ProjectModel(name);
+ }
+ project.title = projectConfigs.getString("project", name, "title");
+ project.description = projectConfigs.getString("project", name, "description");
+ // TODO add more interesting metadata
+ // project manager?
+ // commit message regex?
+ // RW+
+ // RW
+ // R
+ configs.put(name.toLowerCase(), project);
+ }
+ projectCache.clear();
+ projectCache.putAll(configs);
+ }
+ return projectCache;
+ }
+
+ /**
+ * Returns a list of project models for the user.
+ *
+ * @param user
+ * @param includeUsers
+ * @return list of projects that are accessible to the user
+ */
+ public List<ProjectModel> getProjectModels(UserModel user, boolean includeUsers) {
+ Map<String, ProjectModel> configs = getProjectConfigs();
+
+ // per-user project lists, this accounts for security and visibility
+ Map<String, ProjectModel> map = new TreeMap<String, ProjectModel>();
+ // root project
+ map.put("", configs.get(""));
+
+ for (RepositoryModel model : getRepositoryModels(user)) {
+ String rootPath = StringUtils.getRootPath(model.name).toLowerCase();
+ if (!map.containsKey(rootPath)) {
+ ProjectModel project;
+ if (configs.containsKey(rootPath)) {
+ // clone the project model because it's repository list will
+ // be tailored for the requesting user
+ project = DeepCopier.copy(configs.get(rootPath));
+ } else {
+ project = new ProjectModel(rootPath);
+ }
+ map.put(rootPath, project);
+ }
+ map.get(rootPath).addRepository(model);
+ }
+
+ // sort projects, root project first
+ List<ProjectModel> projects;
+ if (includeUsers) {
+ // all projects
+ projects = new ArrayList<ProjectModel>(map.values());
+ Collections.sort(projects);
+ projects.remove(map.get(""));
+ projects.add(0, map.get(""));
+ } else {
+ // all non-user projects
+ projects = new ArrayList<ProjectModel>();
+ ProjectModel root = map.remove("");
+ for (ProjectModel model : map.values()) {
+ if (!model.isUserProject()) {
+ projects.add(model);
+ }
+ }
+ Collections.sort(projects);
+ projects.add(0, root);
+ }
+ return projects;
+ }
+
+ /**
+ * Returns the project model for the specified user.
+ *
+ * @param name
+ * @param user
+ * @return a project model, or null if it does not exist
+ */
+ public ProjectModel getProjectModel(String name, UserModel user) {
+ for (ProjectModel project : getProjectModels(user, true)) {
+ if (project.name.equalsIgnoreCase(name)) {
+ return project;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a project model for the Gitblit/system user.
+ *
+ * @param name a project name
+ * @return a project model or null if the project does not exist
+ */
+ public ProjectModel getProjectModel(String name) {
+ Map<String, ProjectModel> configs = getProjectConfigs();
+ ProjectModel project = configs.get(name.toLowerCase());
+ if (project == null) {
+ project = new ProjectModel(name);
+ if (name.length() > 0 && name.charAt(0) == '~') {
+ UserModel user = getUserModel(name.substring(1));
+ if (user != null) {
+ project.title = user.getDisplayName();
+ project.description = "personal repositories";
+ }
+ }
+ } else {
+ // clone the object
+ project = DeepCopier.copy(project);
+ }
+ if (StringUtils.isEmpty(name)) {
+ // get root repositories
+ for (String repository : getRepositoryList()) {
+ if (repository.indexOf('/') == -1) {
+ project.addRepository(repository);
+ }
+ }
+ } else {
+ // get repositories in subfolder
+ String folder = name.toLowerCase() + "/";
+ for (String repository : getRepositoryList()) {
+ if (repository.toLowerCase().startsWith(folder)) {
+ project.addRepository(repository);
+ }
+ }
+ }
+ if (project.repositories.size() == 0) {
+ // no repositories == no project
+ return null;
+ }
+ return project;
+ }
+
+ /**
+ * Workaround JGit. I need to access the raw config object directly in order
+ * to see if the config is dirty so that I can reload a repository model.
+ * If I use the stock JGit method to get the config it already reloads the
+ * config. If the config changes are made within Gitblit this is fine as
+ * the returned config will still be flagged as dirty. BUT... if the config
+ * is manipulated outside Gitblit then it fails to recognize this as dirty.
+ *
+ * @param r
+ * @return a config
+ */
+ private StoredConfig getRepositoryConfig(Repository r) {
+ try {
+ Field f = r.getClass().getDeclaredField("repoConfig");
+ f.setAccessible(true);
+ StoredConfig config = (StoredConfig) f.get(r);
+ return config;
+ } catch (Exception e) {
+ logger.error("Failed to retrieve \"repoConfig\" via reflection", e);
+ }
+ return r.getConfig();
+ }
+
+ /**
+ * Create a repository model from the configuration and repository data.
+ *
+ * @param repositoryName
+ * @return a repositoryModel or null if the repository does not exist
+ */
+ private RepositoryModel loadRepositoryModel(String repositoryName) {
Repository r = getRepository(repositoryName);
if (r == null) {
return null;
}
RepositoryModel model = new RepositoryModel();
- model.name = repositoryName;
+ model.isBare = r.isBare();
+ File basePath = getFileOrFolder(Keys.git.repositoriesFolder, "git");
+ if (model.isBare) {
+ model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory());
+ } else {
+ model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory().getParentFile());
+ }
model.hasCommits = JGitUtils.hasCommits(r);
model.lastChange = JGitUtils.getLastChange(r);
- model.isBare = r.isBare();
+ model.projectPath = StringUtils.getFirstPathElement(repositoryName);
StoredConfig config = r.getConfig();
+ boolean hasOrigin = !StringUtils.isEmpty(config.getString("remote", "origin", "url"));
+
if (config != null) {
model.description = getConfig(config, "description", "");
model.owner = getConfig(config, "owner", "");
model.useTickets = getConfig(config, "useTickets", false);
model.useDocs = getConfig(config, "useDocs", false);
+ model.allowForks = getConfig(config, "allowForks", true);
model.accessRestriction = AccessRestrictionType.fromName(getConfig(config,
"accessRestriction", settings.getString(Keys.git.defaultAccessRestriction, null)));
model.authorizationControl = AuthorizationControl.fromName(getConfig(config,
"authorizationControl", settings.getString(Keys.git.defaultAuthorizationControl, null)));
- model.showRemoteBranches = getConfig(config, "showRemoteBranches", false);
+ model.verifyCommitter = getConfig(config, "verifyCommitter", false);
+ model.showRemoteBranches = getConfig(config, "showRemoteBranches", hasOrigin);
model.isFrozen = getConfig(config, "isFrozen", false);
model.showReadme = getConfig(config, "showReadme", false);
model.skipSizeCalculation = getConfig(config, "skipSizeCalculation", false);
@@ -997,6 +1269,9 @@
Constants.CONFIG_GITBLIT, null, "federationSets")));
model.isFederated = getConfig(config, "isFederated", false);
model.origin = config.getString("remote", "origin", "url");
+ if (model.origin != null) {
+ model.origin = model.origin.replace('\\', '/');
+ }
model.preReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList(
Constants.CONFIG_GITBLIT, null, "preReceiveScript")));
model.postReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList(
@@ -1015,6 +1290,23 @@
model.HEAD = JGitUtils.getHEADRef(r);
model.availableRefs = JGitUtils.getAvailableHeadTargets(r);
r.close();
+
+ if (model.origin != null && model.origin.startsWith("file://")) {
+ // repository was cloned locally... perhaps as a fork
+ try {
+ File folder = new File(new URI(model.origin));
+ String originRepo = com.gitblit.utils.FileUtils.getRelativePath(getRepositoriesFolder(), folder);
+ if (!StringUtils.isEmpty(originRepo)) {
+ // ensure origin still exists
+ File repoFolder = new File(getRepositoriesFolder(), originRepo);
+ if (repoFolder.exists()) {
+ model.originRepository = originRepo;
+ }
+ }
+ } catch (URISyntaxException e) {
+ logger.error("Failed to determine fork for " + model, e);
+ }
+ }
return model;
}
@@ -1025,12 +1317,124 @@
* @return true if the repository exists
*/
public boolean hasRepository(String repositoryName) {
+ if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
+ // if we are caching use the cache to determine availability
+ // otherwise we end up adding a phantom repository to the cache
+ return repositoryListCache.containsKey(repositoryName);
+ }
Repository r = getRepository(repositoryName, false);
if (r == null) {
return false;
}
r.close();
return true;
+ }
+
+ /**
+ * Determines if the specified user has a fork of the specified origin
+ * repository.
+ *
+ * @param username
+ * @param origin
+ * @return true the if the user has a fork
+ */
+ public boolean hasFork(String username, String origin) {
+ return getFork(username, origin) != null;
+ }
+
+ /**
+ * Gets the name of a user's fork of the specified origin
+ * repository.
+ *
+ * @param username
+ * @param origin
+ * @return the name of the user's fork, null otherwise
+ */
+ public String getFork(String username, String origin) {
+ String userProject = "~" + username.toLowerCase();
+ if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
+ String userPath = userProject + "/";
+
+ // collect all origin nodes in fork network
+ Set<String> roots = new HashSet<String>();
+ roots.add(origin);
+ RepositoryModel originModel = repositoryListCache.get(origin);
+ while (originModel != null) {
+ if (!ArrayUtils.isEmpty(originModel.forks)) {
+ for (String fork : originModel.forks) {
+ if (!fork.startsWith(userPath)) {
+ roots.add(fork);
+ }
+ }
+ }
+
+ if (originModel.originRepository != null) {
+ roots.add(originModel.originRepository);
+ originModel = repositoryListCache.get(originModel.originRepository);
+ } else {
+ // break
+ originModel = null;
+ }
+ }
+
+ for (String repository : repositoryListCache.keySet()) {
+ if (repository.toLowerCase().startsWith(userPath)) {
+ RepositoryModel model = repositoryListCache.get(repository);
+ if (!StringUtils.isEmpty(model.originRepository)) {
+ if (roots.contains(model.originRepository)) {
+ // user has a fork in this graph
+ return model.name;
+ }
+ }
+ }
+ }
+ } else {
+ // not caching
+ ProjectModel project = getProjectModel(userProject);
+ for (String repository : project.repositories) {
+ if (repository.toLowerCase().startsWith(userProject)) {
+ RepositoryModel model = repositoryListCache.get(repository);
+ if (model.originRepository.equalsIgnoreCase(origin)) {
+ // user has a fork
+ return model.name;
+ }
+ }
+ }
+ }
+ // user does not have a fork
+ return null;
+ }
+
+ /**
+ * Returns the fork network for a repository by traversing up the fork graph
+ * to discover the root and then down through all children of the root node.
+ *
+ * @param repository
+ * @return a ForkModel
+ */
+ public ForkModel getForkNetwork(String repository) {
+ if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
+ // find the root
+ RepositoryModel model = repositoryListCache.get(repository);
+ while (model.originRepository != null) {
+ model = repositoryListCache.get(model.originRepository);
+ }
+ ForkModel root = getForkModel(model.name);
+ return root;
+ }
+ return null;
+ }
+
+ private ForkModel getForkModel(String repository) {
+ RepositoryModel model = repositoryListCache.get(repository);
+ ForkModel fork = new ForkModel(model);
+ if (!ArrayUtils.isEmpty(model.forks)) {
+ for (String aFork : model.forks) {
+ ForkModel fm = getForkModel(aFork);
+ fork.forks.add(fm);
+ }
+ }
+ return fork;
}
/**
@@ -1060,6 +1464,11 @@
*/
private void closeRepository(String repositoryName) {
Repository repository = getRepository(repositoryName);
+ if (repository == null) {
+ return;
+ }
+ RepositoryCache.close(repository);
+
// assume 2 uses in case reflection fails
int uses = 2;
try {
@@ -1156,6 +1565,13 @@
public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
boolean isCreate) throws GitBlitException {
Repository r = null;
+ String projectPath = StringUtils.getFirstPathElement(repository.name);
+ if (!StringUtils.isEmpty(projectPath)) {
+ if (projectPath.equalsIgnoreCase(getString(Keys.web.repositoryRootGroupName, "main"))) {
+ // strip leading group name
+ repository.name = repository.name.substring(projectPath.length() + 1);
+ }
+ }
if (isCreate) {
// ensure created repository name ends with .git
if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
@@ -1169,9 +1585,6 @@
// create repository
logger.info("create repository " + repository.name);
r = JGitUtils.createRepository(repositoriesFolder, repository.name);
-
- // add name to cache
- addToCachedRepositoryList(repository.name);
} else {
// rename repository
if (!repositoryName.equalsIgnoreCase(repository.name)) {
@@ -1209,27 +1622,40 @@
"Failed to rename repository permissions ''{0}'' to ''{1}''.",
repositoryName, repository.name));
}
+
+ // rename fork origins in their configs
+ if (!ArrayUtils.isEmpty(repository.forks)) {
+ for (String fork : repository.forks) {
+ Repository rf = getRepository(fork);
+ try {
+ StoredConfig config = rf.getConfig();
+ String origin = config.getString("remote", "origin", "url");
+ origin = origin.replace(repositoryName, repository.name);
+ config.setString("remote", "origin", "url", origin);
+ config.save();
+ } catch (Exception e) {
+ logger.error("Failed to update repository fork config for " + fork, e);
+ }
+ rf.close();
+ }
+ }
+
+ // remove this repository from any origin model's fork list
+ if (!StringUtils.isEmpty(repository.originRepository)) {
+ RepositoryModel origin = repositoryListCache.get(repository.originRepository);
+ if (origin != null && !ArrayUtils.isEmpty(origin.forks)) {
+ origin.forks.remove(repositoryName);
+ }
+ }
// clear the cache
- clearRepositoryCache(repositoryName, true);
-
- // add new name to repository list cache
- addToCachedRepositoryList(repository.name);
+ clearRepositoryMetadataCache(repositoryName);
+ repository.resetDisplayName();
}
// load repository
logger.info("edit repository " + repository.name);
- try {
- r = repositoryResolver.open(null, repository.name);
- } catch (RepositoryNotFoundException e) {
- logger.error("Repository not found", e);
- } catch (ServiceNotAuthorizedException e) {
- logger.error("Service not authorized", e);
- } catch (ServiceNotEnabledException e) {
- logger.error("Service not enabled", e);
- } catch (ServiceMayNotContinueException e) {
- logger.error("Service may not continue", e);
- }
+ r = getRepository(repository.name);
}
// update settings
@@ -1242,13 +1668,18 @@
repository.name, currentRef, repository.HEAD));
if (JGitUtils.setHEADtoRef(r, repository.HEAD)) {
// clear the cache
- clearRepositoryCache(repository.name, false);
+ clearRepositoryMetadataCache(repository.name);
}
}
// close the repository object
r.close();
}
+
+ // update repository cache
+ removeFromCachedRepositoryList(repositoryName);
+ // model will actually be replaced on next load because config is stale
+ addToCachedRepositoryList(repository);
}
/**
@@ -1265,8 +1696,10 @@
config.setString(Constants.CONFIG_GITBLIT, null, "owner", repository.owner);
config.setBoolean(Constants.CONFIG_GITBLIT, null, "useTickets", repository.useTickets);
config.setBoolean(Constants.CONFIG_GITBLIT, null, "useDocs", repository.useDocs);
+ config.setBoolean(Constants.CONFIG_GITBLIT, null, "allowForks", repository.allowForks);
config.setString(Constants.CONFIG_GITBLIT, null, "accessRestriction", repository.accessRestriction.name());
config.setString(Constants.CONFIG_GITBLIT, null, "authorizationControl", repository.authorizationControl.name());
+ config.setBoolean(Constants.CONFIG_GITBLIT, null, "verifyCommitter", repository.verifyCommitter);
config.setBoolean(Constants.CONFIG_GITBLIT, null, "showRemoteBranches", repository.showRemoteBranches);
config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFrozen", repository.isFrozen);
config.setBoolean(Constants.CONFIG_GITBLIT, null, "showReadme", repository.showReadme);
@@ -1339,12 +1772,18 @@
try {
closeRepository(repositoryName);
// clear the repository cache
- clearRepositoryCache(repositoryName, true);
+ clearRepositoryMetadataCache(repositoryName);
+
+ RepositoryModel model = removeFromCachedRepositoryList(repositoryName);
+ if (model != null && !ArrayUtils.isEmpty(model.forks)) {
+ resetRepositoryListCache();
+ }
File folder = new File(repositoriesFolder, repositoryName);
if (folder.exists() && folder.isDirectory()) {
FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY);
if (userService.deleteRepositoryRole(repositoryName)) {
+ logger.info(MessageFormat.format("Repository \"{0}\" deleted", repositoryName));
return true;
}
}
@@ -2065,7 +2504,6 @@
this.settings = settings;
repositoriesFolder = getRepositoriesFolder();
logger.info("Git repositories folder " + repositoriesFolder.getAbsolutePath());
- repositoryResolver = new FileResolver<Void>(repositoriesFolder, true);
// calculate repository list settings checksum for future config changes
repositoryListSettingsChecksum.set(getRepositoryListSettingsChecksum());
@@ -2090,6 +2528,11 @@
loginService = new GitblitUserService();
}
setUserService(loginService);
+
+ // load and cache the project metadata
+ projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "projects.conf"), FS.detect());
+ getProjectConfigs();
+
mailExecutor = new MailExecutor(settings);
if (mailExecutor.isReady()) {
logger.info("Mail executor is scheduled to process the message queue every 2 minutes.");
@@ -2125,6 +2568,8 @@
} catch (IllegalArgumentException e) {
logger.error("Failed to configure JGit parameters!", e);
}
+
+ ContainerUtils.CVE_2007_0450.test();
}
private void logTimezone(String type, TimeZone zone) {
@@ -2196,4 +2641,73 @@
scheduledExecutor.shutdownNow();
luceneExecutor.close();
}
+
+ /**
+ * Creates a personal fork of the specified repository. The clone is view
+ * restricted by default and the owner of the source repository is given
+ * access to the clone.
+ *
+ * @param repository
+ * @param user
+ * @return the repository model of the fork, if successful
+ * @throws GitBlitException
+ */
+ public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
+ String cloneName = MessageFormat.format("~{0}/{1}.git", user.username, StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name)));
+ String fromUrl = MessageFormat.format("file://{0}/{1}", repositoriesFolder.getAbsolutePath(), repository.name);
+
+ // clone the repository
+ try {
+ JGitUtils.cloneRepository(repositoriesFolder, cloneName, fromUrl, true, null);
+ } catch (Exception e) {
+ throw new GitBlitException(e);
+ }
+
+ // create a Gitblit repository model for the clone
+ RepositoryModel cloneModel = repository.cloneAs(cloneName);
+ // owner has REWIND/RW+ permissions
+ cloneModel.owner = user.username;
+ updateRepositoryModel(cloneName, cloneModel, false);
+
+ // add the owner of the source repository to the clone's access list
+ if (!StringUtils.isEmpty(repository.owner)) {
+ UserModel originOwner = getUserModel(repository.owner);
+ if (originOwner != null) {
+ originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+ updateUserModel(originOwner.username, originOwner, false);
+ }
+ }
+
+ // grant origin's user list clone permission to fork
+ List<String> users = getRepositoryUsers(repository);
+ List<UserModel> cloneUsers = new ArrayList<UserModel>();
+ for (String name : users) {
+ if (!name.equalsIgnoreCase(user.username)) {
+ UserModel cloneUser = getUserModel(name);
+ if (cloneUser.canClone(repository)) {
+ // origin user can clone origin, grant clone access to fork
+ cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+ }
+ cloneUsers.add(cloneUser);
+ }
+ }
+ userService.updateUserModels(cloneUsers);
+
+ // grant origin's team list clone permission to fork
+ List<String> teams = getRepositoryTeams(repository);
+ List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
+ for (String name : teams) {
+ TeamModel cloneTeam = getTeamModel(name);
+ if (cloneTeam.canClone(repository)) {
+ // origin team can clone origin, grant clone access to fork
+ cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+ }
+ cloneTeams.add(cloneTeam);
+ }
+ userService.updateTeamModels(cloneTeams);
+
+ // add this clone to the cached model
+ addToCachedRepositoryList(cloneModel);
+ return cloneModel;
+ }
}
--
Gitblit v1.9.1