From a1f27e2fac7b38b87645bd53b7e023484c796f1c Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 29 Nov 2013 11:05:51 -0500
Subject: [PATCH] Extract ProjectManager from the GitBlit singleton

---
 src/main/java/com/gitblit/manager/ProjectManager.java  |  327 ++++++++++++++++++++++++++++++++
 src/main/java/com/gitblit/DaggerModule.java            |   12 +
 src/main/java/com/gitblit/manager/IProjectManager.java |    2 
 src/main/java/com/gitblit/GitBlit.java                 |  256 -------------------------
 4 files changed, 341 insertions(+), 256 deletions(-)

diff --git a/src/main/java/com/gitblit/DaggerModule.java b/src/main/java/com/gitblit/DaggerModule.java
index 240fe7f..8cd9c8b 100644
--- a/src/main/java/com/gitblit/DaggerModule.java
+++ b/src/main/java/com/gitblit/DaggerModule.java
@@ -29,6 +29,7 @@
 import com.gitblit.manager.ISessionManager;
 import com.gitblit.manager.IUserManager;
 import com.gitblit.manager.NotificationManager;
+import com.gitblit.manager.ProjectManager;
 import com.gitblit.manager.RepositoryManager;
 import com.gitblit.manager.RuntimeManager;
 import com.gitblit.manager.SessionManager;
@@ -125,8 +126,15 @@
 				userManager);
 	}
 
-	@Provides @Singleton IProjectManager provideProjectManager() {
-		return gitblit;
+	@Provides @Singleton IProjectManager provideProjectManager(
+			IRuntimeManager runtimeManager,
+			IUserManager userManager,
+			IRepositoryManager repositoryManager) {
+
+		return new ProjectManager(
+				runtimeManager,
+				userManager,
+				repositoryManager);
 	}
 
 	@Provides @Singleton IGitblitManager provideGitblitManager() {
diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java
index 4e8822b..493f8fc 100644
--- a/src/main/java/com/gitblit/GitBlit.java
+++ b/src/main/java/com/gitblit/GitBlit.java
@@ -32,10 +32,8 @@
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
@@ -50,8 +48,6 @@
 
 import org.apache.wicket.resource.ContextRelativeResource;
 import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
 import org.slf4j.Logger;
 
 import com.gitblit.Constants.AccessPermission;
@@ -77,7 +73,6 @@
 import com.gitblit.models.FederationProposal;
 import com.gitblit.models.FederationSet;
 import com.gitblit.models.GitClientApplication;
-import com.gitblit.models.ProjectModel;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.RepositoryUrl;
 import com.gitblit.models.ServerSettings;
@@ -86,12 +81,10 @@
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.ArrayUtils;
 import com.gitblit.utils.ContainerUtils;
-import com.gitblit.utils.DeepCopier;
 import com.gitblit.utils.FederationUtils;
 import com.gitblit.utils.HttpUtils;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.JsonUtils;
-import com.gitblit.utils.ModelUtils;
 import com.gitblit.utils.ObjectCache;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.GitblitWicketFilter;
@@ -117,8 +110,7 @@
  */
 @WebListener
 public class GitBlit extends DaggerContextListener
-					 implements IProjectManager,
-								IFederationManager,
+					 implements IFederationManager,
 								IGitblitManager {
 
 	private static GitBlit gitblit;
@@ -138,15 +130,7 @@
 
 	private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
 
-	private final Map<String, ProjectModel> projectCache = new ConcurrentHashMap<String, ProjectModel>();
-
-	private final ObjectCache<String> projectMarkdownCache = new ObjectCache<String>();
-
-	private final ObjectCache<String> projectRepositoriesMarkdownCache = new ObjectCache<String>();
-
 	private IStoredSettings settings;
-
-	private FileBasedConfig projectConfigs;
 
 	private FanoutService fanoutService;
 
@@ -442,237 +426,6 @@
 		}
 	}
 
-	private void reloadProjectMarkdown(ProjectModel project) {
-		// project markdown
-		File pmkd = new File(getManager(IRepositoryManager.class).getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/project.mkd");
-		if (pmkd.exists()) {
-			Date lm = new Date(pmkd.lastModified());
-			if (!projectMarkdownCache.hasCurrent(project.name, lm)) {
-				String mkd = com.gitblit.utils.FileUtils.readContent(pmkd,  "\n");
-				projectMarkdownCache.updateObject(project.name, lm, mkd);
-			}
-			project.projectMarkdown = projectMarkdownCache.getObject(project.name);
-		}
-
-		// project repositories markdown
-		File rmkd = new File(getManager(IRepositoryManager.class).getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/repositories.mkd");
-		if (rmkd.exists()) {
-			Date lm = new Date(rmkd.lastModified());
-			if (!projectRepositoriesMarkdownCache.hasCurrent(project.name, lm)) {
-				String mkd = com.gitblit.utils.FileUtils.readContent(rmkd,  "\n");
-				projectRepositoriesMarkdownCache.updateObject(project.name, lm, mkd);
-			}
-			project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(project.name);
-		}
-	}
-
-
-	/**
-	 * 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 = settings.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");
-
-				reloadProjectMarkdown(project);
-
-				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
-	 */
-	@Override
-	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 : getManager(IRepositoryManager.class).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
-	 */
-	@Override
-	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
-	 */
-	@Override
-	public ProjectModel getProjectModel(String name) {
-		Map<String, ProjectModel> configs = getProjectConfigs();
-		ProjectModel project = configs.get(name.toLowerCase());
-		if (project == null) {
-			project = new ProjectModel(name);
-			if (ModelUtils.isPersonalRepository(name)) {
-				UserModel user = getManager(IUserManager.class).getUserModel(ModelUtils.getUserNameFromRepoPath(name));
-				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 : getManager(IRepositoryManager.class).getRepositoryList()) {
-				if (repository.indexOf('/') == -1) {
-					project.addRepository(repository);
-				}
-			}
-		} else {
-			// get repositories in subfolder
-			String folder = name.toLowerCase() + "/";
-			for (String repository : getManager(IRepositoryManager.class).getRepositoryList()) {
-				if (repository.toLowerCase().startsWith(folder)) {
-					project.addRepository(repository);
-				}
-			}
-		}
-		if (project.repositories.size() == 0) {
-			// no repositories == no project
-			return null;
-		}
-
-		reloadProjectMarkdown(project);
-		return project;
-	}
-
-	/**
-	 * Returns the list of project models that are referenced by the supplied
-	 * repository model	list.  This is an alternative method exists to ensure
-	 * Gitblit does not call getRepositoryModels(UserModel) twice in a request.
-	 *
-	 * @param repositoryModels
-	 * @param includeUsers
-	 * @return a list of project models
-	 */
-	@Override
-	public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) {
-		Map<String, ProjectModel> projects = new LinkedHashMap<String, ProjectModel>();
-		for (RepositoryModel repository : repositoryModels) {
-			if (!includeUsers && repository.isPersonalRepository()) {
-				// exclude personal repositories
-				continue;
-			}
-			if (!projects.containsKey(repository.projectPath)) {
-				ProjectModel project = getProjectModel(repository.projectPath);
-				if (project == null) {
-					logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!",
-							repository.projectPath));
-					continue;
-				}
-				projects.put(repository.projectPath, project);
-				// clear the repo list in the project because that is the system
-				// list, not the user-accessible list and start building the
-				// user-accessible list
-				project.repositories.clear();
-				project.repositories.add(repository.name);
-				project.lastChange = repository.lastChange;
-			} else {
-				// update the user-accessible list
-				// this is used for repository count
-				ProjectModel project = projects.get(repository.projectPath);
-				project.repositories.add(repository.name);
-				if (project.lastChange.before(repository.lastChange)) {
-					project.lastChange = repository.lastChange;
-				}
-			}
-		}
-		return new ArrayList<ProjectModel>(projects.values());
-	}
 
 	/**
 	 * Returns Gitblit's scheduled executor service for scheduling tasks.
@@ -1173,7 +926,7 @@
 						getManager(IUserManager.class),
 						getManager(ISessionManager.class),
 						getManager(IRepositoryManager.class),
-						this,
+						getManager(IProjectManager.class),
 						this,
 						this);
 				gitDaemon = new GitDaemon(gitblit);
@@ -1242,14 +995,11 @@
 		startManager(injector, IUserManager.class);
 		startManager(injector, ISessionManager.class);
 		startManager(injector, IRepositoryManager.class);
+		startManager(injector, IProjectManager.class);
 
 		logger.info("Gitblit base folder     = " + baseFolder.getAbsolutePath());
 
 		loadSettingModels(runtime.getSettingsModel());
-
-		// load and cache the project metadata
-		projectConfigs = new FileBasedConfig(runtime.getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
-		getProjectConfigs();
 
 		if (true/*startFederation*/) {
 			configureFederation();
diff --git a/src/main/java/com/gitblit/manager/IProjectManager.java b/src/main/java/com/gitblit/manager/IProjectManager.java
index b2577f5..c5b490e 100644
--- a/src/main/java/com/gitblit/manager/IProjectManager.java
+++ b/src/main/java/com/gitblit/manager/IProjectManager.java
@@ -21,7 +21,7 @@
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 
-public interface IProjectManager {
+public interface IProjectManager extends IManager {
 
 	/**
 	 * Returns a list of project models for the user.
diff --git a/src/main/java/com/gitblit/manager/ProjectManager.java b/src/main/java/com/gitblit/manager/ProjectManager.java
new file mode 100644
index 0000000..83a6310
--- /dev/null
+++ b/src/main/java/com/gitblit/manager/ProjectManager.java
@@ -0,0 +1,327 @@
+/*
+ * 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.manager;
+
+import java.io.File;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.DeepCopier;
+import com.gitblit.utils.ModelUtils;
+import com.gitblit.utils.ObjectCache;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Project manager handles project-related functions.
+ *
+ * @author James Moger
+ *
+ */
+public class ProjectManager implements IProjectManager {
+
+	private final Logger logger = LoggerFactory.getLogger(getClass());
+
+	private final Map<String, ProjectModel> projectCache = new ConcurrentHashMap<String, ProjectModel>();
+
+	private final ObjectCache<String> projectMarkdownCache = new ObjectCache<String>();
+
+	private final ObjectCache<String> projectRepositoriesMarkdownCache = new ObjectCache<String>();
+
+	private final IStoredSettings settings;
+
+	private final IRuntimeManager runtimeManager;
+
+	private final IUserManager userManager;
+
+	private final IRepositoryManager repositoryManager;
+
+	private FileBasedConfig projectConfigs;
+
+	public ProjectManager(
+			IRuntimeManager runtimeManager,
+			IUserManager userManager,
+			IRepositoryManager repositoryManager) {
+
+		this.settings = runtimeManager.getSettings();
+		this.runtimeManager = runtimeManager;
+		this.userManager = userManager;
+		this.repositoryManager = repositoryManager;
+	}
+
+	@Override
+	public IManager setup() {
+		// load and cache the project metadata
+		projectConfigs = new FileBasedConfig(runtimeManager.getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
+		getProjectConfigs();
+
+		return this;
+	}
+
+	@Override
+	public IManager stop() {
+		return this;
+	}
+
+	private void reloadProjectMarkdown(ProjectModel project) {
+		// project markdown
+		File pmkd = new File(repositoryManager.getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/project.mkd");
+		if (pmkd.exists()) {
+			Date lm = new Date(pmkd.lastModified());
+			if (!projectMarkdownCache.hasCurrent(project.name, lm)) {
+				String mkd = com.gitblit.utils.FileUtils.readContent(pmkd,  "\n");
+				projectMarkdownCache.updateObject(project.name, lm, mkd);
+			}
+			project.projectMarkdown = projectMarkdownCache.getObject(project.name);
+		}
+
+		// project repositories markdown
+		File rmkd = new File(repositoryManager.getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/repositories.mkd");
+		if (rmkd.exists()) {
+			Date lm = new Date(rmkd.lastModified());
+			if (!projectRepositoriesMarkdownCache.hasCurrent(project.name, lm)) {
+				String mkd = com.gitblit.utils.FileUtils.readContent(rmkd,  "\n");
+				projectRepositoriesMarkdownCache.updateObject(project.name, lm, mkd);
+			}
+			project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(project.name);
+		}
+	}
+
+
+	/**
+	 * 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 = settings.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");
+
+				reloadProjectMarkdown(project);
+
+				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
+	 */
+	@Override
+	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 : repositoryManager.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
+	 */
+	@Override
+	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
+	 */
+	@Override
+	public ProjectModel getProjectModel(String name) {
+		Map<String, ProjectModel> configs = getProjectConfigs();
+		ProjectModel project = configs.get(name.toLowerCase());
+		if (project == null) {
+			project = new ProjectModel(name);
+			if (ModelUtils.isPersonalRepository(name)) {
+				UserModel user = userManager.getUserModel(ModelUtils.getUserNameFromRepoPath(name));
+				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 : repositoryManager.getRepositoryList()) {
+				if (repository.indexOf('/') == -1) {
+					project.addRepository(repository);
+				}
+			}
+		} else {
+			// get repositories in subfolder
+			String folder = name.toLowerCase() + "/";
+			for (String repository : repositoryManager.getRepositoryList()) {
+				if (repository.toLowerCase().startsWith(folder)) {
+					project.addRepository(repository);
+				}
+			}
+		}
+		if (project.repositories.size() == 0) {
+			// no repositories == no project
+			return null;
+		}
+
+		reloadProjectMarkdown(project);
+		return project;
+	}
+
+	/**
+	 * Returns the list of project models that are referenced by the supplied
+	 * repository model	list.  This is an alternative method exists to ensure
+	 * Gitblit does not call getRepositoryModels(UserModel) twice in a request.
+	 *
+	 * @param repositoryModels
+	 * @param includeUsers
+	 * @return a list of project models
+	 */
+	@Override
+	public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) {
+		Map<String, ProjectModel> projects = new LinkedHashMap<String, ProjectModel>();
+		for (RepositoryModel repository : repositoryModels) {
+			if (!includeUsers && repository.isPersonalRepository()) {
+				// exclude personal repositories
+				continue;
+			}
+			if (!projects.containsKey(repository.projectPath)) {
+				ProjectModel project = getProjectModel(repository.projectPath);
+				if (project == null) {
+					logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!",
+							repository.projectPath));
+					continue;
+				}
+				projects.put(repository.projectPath, project);
+				// clear the repo list in the project because that is the system
+				// list, not the user-accessible list and start building the
+				// user-accessible list
+				project.repositories.clear();
+				project.repositories.add(repository.name);
+				project.lastChange = repository.lastChange;
+			} else {
+				// update the user-accessible list
+				// this is used for repository count
+				ProjectModel project = projects.get(repository.projectPath);
+				project.repositories.add(repository.name);
+				if (project.lastChange.before(repository.lastChange)) {
+					project.lastChange = repository.lastChange;
+				}
+			}
+		}
+		return new ArrayList<ProjectModel>(projects.values());
+	}
+}

--
Gitblit v1.9.1