From fb9813874c811ae06604c30d875e9dce57df9874 Mon Sep 17 00:00:00 2001
From: SHaselbauer <sarah.haselbauer@akquinet.de>
Date: Mon, 21 Jan 2013 16:28:11 -0500
Subject: [PATCH] Merge multiple owners feature (pull request #63, #66)

---
 src/com/gitblit/client/RepositoriesPanel.java                 |    5 
 src/com/gitblit/wicket/GitBlitWebApp_nl.properties            |    8 
 src/com/gitblit/client/RegistrantPermissionsPanel.java        |    4 
 src/com/gitblit/wicket/GitBlitWebApp_pl.properties            |    6 
 src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties         |    8 
 src/com/gitblit/client/RepositoriesTableModel.java            |    4 
 src/com/gitblit/client/EditUserDialog.java                    |    4 
 tests/com/gitblit/tests/PermissionsTest.java                  |    6 
 src/com/gitblit/wicket/panels/RepositoriesPanel.java          |   78 ++++--
 src/com/gitblit/models/UserModel.java                         |    9 
 src/com/gitblit/wicket/pages/RepositoryPage.java              |   10 
 tests/com/gitblit/tests/GitServletTest.java                   |    7 
 src/com/gitblit/wicket/GitBlitWebApp_ko.properties            |    8 
 src/com/gitblit/GitFilter.java                                |    2 
 src/com/gitblit/GitBlit.java                                  |   48 ++-
 src/com/gitblit/wicket/GitBlitWebApp.properties               |    8 
 src/com/gitblit/wicket/GitBlitWebApp_ja.properties            |    6 
 src/com/gitblit/wicket/panels/RepositoriesPanel.html          |    6 
 src/com/gitblit/client/EditRepositoryDialog.java              |    6 
 src/com/gitblit/wicket/pages/EditRepositoryPage.html          |    2 
 src/com/gitblit/wicket/pages/RootSubPage.java                 |    2 
 src/com/gitblit/wicket/pages/SummaryPage.html                 |    2 
 src/com/gitblit/wicket/pages/EditRepositoryPage.java          |   26 +
 src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java |    4 
 src/com/gitblit/client/GitblitClient.java                     |    4 
 src/com/gitblit/utils/MultiConfigUtil.java                    |   89 ++++++++
 src/com/gitblit/utils/StringComparator.java                   |   24 ++
 src/com/gitblit/wicket/GitBlitWebApp_es.properties            |    6 
 tests/com/gitblit/tests/RpcTests.java                         |    2 
 src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html     |    2 
 src/com/gitblit/models/RepositoryModel.java                   |   99 ++++++--
 tests/com/gitblit/tests/FederationTests.java                  |    2 
 src/com/gitblit/wicket/pages/SummaryPage.java                 |   19 
 src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java     |  144 ++++++++----
 34 files changed, 454 insertions(+), 206 deletions(-)

diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 3eb246b..a607bd8 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -109,6 +109,7 @@
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.JsonUtils;
 import com.gitblit.utils.MetricUtils;
+import com.gitblit.utils.MultiConfigUtil;
 import com.gitblit.utils.ObjectCache;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.TimeUtils;
@@ -180,6 +181,8 @@
 	private TimeZone timezone;
 	
 	private FileBasedConfig projectConfigs;
+	
+	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();
 
 	public GitBlit() {
 		if (gitblit == null) {
@@ -822,7 +825,7 @@
 		// TODO reconsider ownership as a user property
 		// manually specify personal repository ownerships
 		for (RepositoryModel rm : repositoryListCache.values()) {
-			if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(user.username)) {
+			if (rm.isUsersPersonalRepository(user.username) || rm.isRepoAdministrator(user.username)) {
 				RegistrantAccessPermission rp = new RegistrantAccessPermission(rm.name, AccessPermission.REWIND,
 						PermissionType.OWNER, RegistrantType.REPOSITORY, null, false);
 				// user may be owner of a repository to which they've inherited
@@ -936,14 +939,14 @@
 			for (RepositoryModel model : getRepositoryModels(user)) {
 				if (model.isUsersPersonalRepository(username)) {
 					// personal repository
-					model.owner = user.username;
+					model.addRepoAdministrator(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)) {
+				} else if (model.isRepoAdministrator(username)) {
 					// common/shared repo
-					model.owner = user.username;
+					model.addRepoAdministrator(user.username);
 					updateRepositoryModel(model.name, model, false);
 				}
 			}
@@ -1662,7 +1665,7 @@
 		
 		if (config != null) {
 			model.description = getConfig(config, "description", "");
-			model.owner = getConfig(config, "owner", "");
+			model.addRepoAdministrators(multiConfigUtil.convertStringToSet(getConfig(config, "owner", "")));
 			model.useTickets = getConfig(config, "useTickets", false);
 			model.useDocs = getConfig(config, "useDocs", false);
 			model.allowForks = getConfig(config, "allowForks", true);
@@ -2169,7 +2172,7 @@
 	public void updateConfiguration(Repository r, RepositoryModel repository) {
 		StoredConfig config = r.getConfig();
 		config.setString(Constants.CONFIG_GITBLIT, null, "description", repository.description);
-		config.setString(Constants.CONFIG_GITBLIT, null, "owner", repository.owner);
+		config.setString(Constants.CONFIG_GITBLIT, null, "owner", multiConfigUtil.convertCollectionToSingleLineString(repository.getRepoAdministrators()));
 		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);
@@ -3079,9 +3082,15 @@
 		}
 		
 		// schedule lucene engine
-		logger.info("Lucene executor is scheduled to process indexed branches every 2 minutes.");
-		scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, 2, TimeUnit.MINUTES);
-		
+		boolean branchIndexingActivated = settings.getBoolean(
+				Keys.git.branchIndexingActivated, true);
+		logger.info("Branch indexing is "
+				+ (branchIndexingActivated ? "" : "not") + " activated");
+		if (branchIndexingActivated) {
+			logger.info("Lucene executor is scheduled to process indexed branches every 2 minutes.");
+			scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, 2,
+					TimeUnit.MINUTES);
+		}
 		// schedule gc engine
 		if (gcExecutor.isReady()) {
 			logger.info("GC executor is scheduled to scan repositories every 24 hours.");
@@ -3249,18 +3258,23 @@
 
 		// create a Gitblit repository model for the clone
 		RepositoryModel cloneModel = repository.cloneAs(cloneName);
-		// owner has REWIND/RW+ permissions
-		cloneModel.owner = user.username;
+		// owner has REWIND/RW+ permissions		
+		cloneModel.addRepoAdministrator(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);
+		Set<String> repoAdministrators = repository.getRepoAdministrators();
+		if (repoAdministrators != null) {
+			for (String repoAdministrator : repoAdministrators) {
+				if (!StringUtils.isEmpty(repoAdministrator)) {
+					UserModel originOwner = getUserModel(repoAdministrator);
+					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);
diff --git a/src/com/gitblit/GitFilter.java b/src/com/gitblit/GitFilter.java
index 2b769d4..82b37dc 100644
--- a/src/com/gitblit/GitFilter.java
+++ b/src/com/gitblit/GitFilter.java
@@ -222,7 +222,7 @@
 				// create repository
 				RepositoryModel model = new RepositoryModel();
 				model.name = repository;
-				model.owner = user.username;
+				model.addRepoAdministrator(user.username);
 				model.projectPath = StringUtils.getFirstPathElement(repository);
 				if (model.isUsersPersonalRepository(user.username)) {
 					// personal repository, default to private for user
diff --git a/src/com/gitblit/client/EditRepositoryDialog.java b/src/com/gitblit/client/EditRepositoryDialog.java
index 6f9ed52..5c03ff8 100644
--- a/src/com/gitblit/client/EditRepositoryDialog.java
+++ b/src/com/gitblit/client/EditRepositoryDialog.java
@@ -335,7 +335,7 @@
 		usersPalette = new RegistrantPermissionsPanel(RegistrantType.USER);
 
 		JPanel northFieldsPanel = new JPanel(new GridLayout(0, 1, 0, 5));
-		northFieldsPanel.add(newFieldPanel(Translation.get("gb.owner"), ownerField));
+		northFieldsPanel.add(newFieldPanel(Translation.get("gb.repoAdministrators"), ownerField));
 		northFieldsPanel.add(newFieldPanel(Translation.get("gb.accessRestriction"),
 				accessRestriction), BorderLayout.NORTH);
 
@@ -556,8 +556,8 @@
 
 		repository.name = rname;
 		repository.description = descriptionField.getText();
-		repository.owner = ownerField.getSelectedItem() == null ? null
-				: ownerField.getSelectedItem().toString();
+		repository.addRepoAdministrator(ownerField.getSelectedItem() == null ? null
+				: ownerField.getSelectedItem().toString());
 		repository.HEAD = headRefField.getSelectedItem() == null ? null
 				: headRefField.getSelectedItem().toString();
 		repository.gcPeriod = (Integer) gcPeriod.getSelectedItem();
diff --git a/src/com/gitblit/client/EditUserDialog.java b/src/com/gitblit/client/EditUserDialog.java
index 0400f5c..cb04b31 100644
--- a/src/com/gitblit/client/EditUserDialog.java
+++ b/src/com/gitblit/client/EditUserDialog.java
@@ -389,7 +389,7 @@
 		List<String> restricted = new ArrayList<String>();
 		for (RepositoryModel repo : repositories) {
 			// exclude Owner or personal repositories
-			if (!repo.isOwner(username) && !repo.isUsersPersonalRepository(username)) {
+			if (!repo.isRepoAdministrator(username) && !repo.isUsersPersonalRepository(username)) {
 				if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)
 						&& repo.authorizationControl.equals(AuthorizationControl.NAMED)) {
 					restricted.add(repo.name);
@@ -438,7 +438,7 @@
 					permission.mutable = false;
 					continue;
 				}
-				boolean isOwner = rm.isOwner(username);
+				boolean isOwner = rm.isRepoAdministrator(username);
 				if (isOwner) {
 					permission.permissionType = PermissionType.OWNER;
 					permission.mutable = false;
diff --git a/src/com/gitblit/client/GitblitClient.java b/src/com/gitblit/client/GitblitClient.java
index 1101cd6..01db46e 100644
--- a/src/com/gitblit/client/GitblitClient.java
+++ b/src/com/gitblit/client/GitblitClient.java
@@ -162,7 +162,7 @@
 	}
 
 	public boolean isOwner(RepositoryModel model) {
-		return account != null && account.equalsIgnoreCase(model.owner);
+		return model.isRepoAdministrator(account);
 	}
 
 	public String getURL(String action, String repository, String objectId) {
@@ -532,7 +532,7 @@
 		// TODO reconsider ownership as a user property
 		// manually specify personal repository ownerships
 		for (RepositoryModel rm : allRepositories) {
-			if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(user.username)) {
+			if (rm.isUsersPersonalRepository(user.username) || rm.isRepoAdministrator(user.username)) {
 				RegistrantAccessPermission rp = new RegistrantAccessPermission(rm.name, AccessPermission.REWIND,
 						PermissionType.OWNER, RegistrantType.REPOSITORY, null, false);
 				// user may be owner of a repository to which they've inherited
diff --git a/src/com/gitblit/client/RegistrantPermissionsPanel.java b/src/com/gitblit/client/RegistrantPermissionsPanel.java
index 98dbfb7..46ba689 100644
--- a/src/com/gitblit/client/RegistrantPermissionsPanel.java
+++ b/src/com/gitblit/client/RegistrantPermissionsPanel.java
@@ -209,8 +209,8 @@
 				setToolTipText(Translation.get("gb.administratorPermission"));
 				break;
 			case OWNER:
-				setText(Translation.get("gb.owner"));
-				setToolTipText(Translation.get("gb.ownerPermission"));
+				setText(Translation.get("gb.repoAdministrators"));
+				setToolTipText(Translation.get("gb.repoAdministratorPermission"));
 				break;
 			case TEAM:
 				setText(ap.source == null ? Translation.get("gb.team") : ap.source);
diff --git a/src/com/gitblit/client/RepositoriesPanel.java b/src/com/gitblit/client/RepositoriesPanel.java
index 769d33b..6700b21 100644
--- a/src/com/gitblit/client/RepositoriesPanel.java
+++ b/src/com/gitblit/client/RepositoriesPanel.java
@@ -52,6 +52,7 @@
 import com.gitblit.models.RegistrantAccessPermission;
 import com.gitblit.models.FeedModel;
 import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.MultiConfigUtil;
 import com.gitblit.utils.StringUtils;
 
 /**
@@ -84,6 +85,8 @@
 	private JTextField filterTextfield;
 
 	private JButton clearCache;
+	
+	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();
 
 	public RepositoriesPanel(GitblitClient gitblit) {
 		super();
@@ -453,7 +456,7 @@
 		dialog.setLocationRelativeTo(RepositoriesPanel.this);
 		List<String> usernames = gitblit.getUsernames();
 		List<RegistrantAccessPermission> members = gitblit.getUserAccessPermissions(repository);
-		dialog.setUsers(repository.owner, usernames, members);
+		dialog.setUsers(multiConfigUtil.convertCollectionToSingleLineString(repository.getRepoAdministrators()), usernames, members);
 		dialog.setTeams(gitblit.getTeamnames(), gitblit.getTeamAccessPermissions(repository));
 		dialog.setRepositories(gitblit.getRepositories());
 		dialog.setFederationSets(gitblit.getFederationSets(), repository.federationSets);
diff --git a/src/com/gitblit/client/RepositoriesTableModel.java b/src/com/gitblit/client/RepositoriesTableModel.java
index c3eaf6e..65f49ca 100644
--- a/src/com/gitblit/client/RepositoriesTableModel.java
+++ b/src/com/gitblit/client/RepositoriesTableModel.java
@@ -73,7 +73,7 @@
 		case Description:
 			return Translation.get("gb.description");
 		case Owner:
-			return Translation.get("gb.owner");
+			return Translation.get("gb.repoAdministrators");
 		case Last_Change:
 			return Translation.get("gb.lastChange");
 		case Size:
@@ -111,7 +111,7 @@
 		case Description:
 			return model.description;
 		case Owner:
-			return model.owner;
+			return model.getRepoAdministrators();
 		case Indicators:
 			return model;
 		case Last_Change:
diff --git a/src/com/gitblit/models/RepositoryModel.java b/src/com/gitblit/models/RepositoryModel.java
index 5be33a2..a27e9fd 100644
--- a/src/com/gitblit/models/RepositoryModel.java
+++ b/src/com/gitblit/models/RepositoryModel.java
@@ -23,10 +23,14 @@
 import java.util.Set;
 import java.util.TreeSet;
 
+import org.apache.wicket.markup.html.basic.MultiLineLabel;
+
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.Constants.AuthorizationControl;
 import com.gitblit.Constants.FederationStrategy;
 import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.MultiConfigUtil;
+import com.gitblit.utils.StringComparator;
 import com.gitblit.utils.StringUtils;
 
 /**
@@ -36,14 +40,15 @@
  * @author James Moger
  * 
  */
-public class RepositoryModel implements Serializable, Comparable<RepositoryModel> {
+public class RepositoryModel implements Serializable,
+		Comparable<RepositoryModel> {
 
 	private static final long serialVersionUID = 1L;
 
 	// field names are reflectively mapped in EditRepository page
 	public String name;
 	public String description;
-	public String owner;
+	private Set<String> repoAdministrators = new TreeSet<String>(new StringComparator());
 	public Date lastChange;
 	public boolean hasCommits;
 	public boolean showRemoteBranches;
@@ -79,26 +84,29 @@
 	public String gcThreshold;
 	public int gcPeriod;
 	public int maxActivityCommits;
-	
+
 	public transient boolean isCollectingGarbage;
 	public Date lastGC;
-	
+
+	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();
+
 	public RepositoryModel() {
 		this("", "", "", new Date(0));
 	}
 
-	public RepositoryModel(String name, String description, String owner, Date lastchange) {
+	public RepositoryModel(String name, String description, String owner,
+			Date lastchange) {
 		this.name = name;
-		this.description = description;
-		this.owner = owner;
+		this.description = description;		
+		this.addRepoAdministrator(owner);
 		this.lastChange = lastchange;
 		this.accessRestriction = AccessRestrictionType.NONE;
 		this.authorizationControl = AuthorizationControl.NAMED;
 		this.federationSets = new ArrayList<String>();
-		this.federationStrategy = FederationStrategy.FEDERATE_THIS;	
+		this.federationStrategy = FederationStrategy.FEDERATE_THIS;
 		this.projectPath = StringUtils.getFirstPathElement(name);
 	}
-	
+
 	public List<String> getLocalBranches() {
 		if (ArrayUtils.isEmpty(availableRefs)) {
 			return new ArrayList<String>();
@@ -111,30 +119,30 @@
 		}
 		return localBranches;
 	}
-	
+
 	public void addFork(String repository) {
 		if (forks == null) {
 			forks = new TreeSet<String>();
 		}
 		forks.add(repository);
 	}
-	
+
 	public void removeFork(String repository) {
 		if (forks == null) {
 			return;
 		}
 		forks.remove(repository);
 	}
-	
+
 	public void resetDisplayName() {
 		displayName = null;
 	}
-	
+
 	@Override
 	public int hashCode() {
 		return name.hashCode();
 	}
-	
+
 	@Override
 	public boolean equals(Object o) {
 		if (o instanceof RepositoryModel) {
@@ -155,27 +163,25 @@
 	public int compareTo(RepositoryModel o) {
 		return StringUtils.compareRepositoryNames(name, o.name);
 	}
-	
+
 	public boolean isFork() {
 		return !StringUtils.isEmpty(originRepository);
 	}
-	
-	public boolean isOwner(String username) {
-		return owner != null && username != null && owner.equalsIgnoreCase(username);
-	}
-	
+
 	public boolean isPersonalRepository() {
-		return !StringUtils.isEmpty(projectPath) && projectPath.charAt(0) == '~';
+		return !StringUtils.isEmpty(projectPath)
+				&& projectPath.charAt(0) == '~';
 	}
-	
+
 	public boolean isUsersPersonalRepository(String username) {
-		return !StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase("~" + username);
+		return !StringUtils.isEmpty(projectPath)
+				&& projectPath.equalsIgnoreCase("~" + username);
 	}
-	
+
 	public boolean allowAnonymousView() {
 		return !accessRestriction.atLeast(AccessRestrictionType.VIEW);
 	}
-	
+
 	public RepositoryModel cloneAs(String cloneName) {
 		RepositoryModel clone = new RepositoryModel();
 		clone.originRepository = name;
@@ -195,4 +201,47 @@
 		clone.skipSummaryMetrics = skipSummaryMetrics;
 		return clone;
 	}
+
+	public void addRepoAdministrator(String repoAdministrator) {
+		if (repoAdministrator != null && repoAdministrator.trim().length() > 0) {
+			this.repoAdministrators.add(repoAdministrator.toLowerCase());
+		}
+	}
+
+	public void removeRepoAdministrator(String repoAdministrator) {
+		if (repoAdministrator != null && repoAdministrator.trim().length() > 0) {
+			this.repoAdministrators.remove(repoAdministrator.toLowerCase());
+		}
+	}
+
+	public void addRepoAdministrators(Set<String> repoAdministrators) {
+		if (repoAdministrators != null) {
+			for (String admin : repoAdministrators) {
+				this.addRepoAdministrator(admin);
+			}
+		}
+	}
+
+	public void removeRepoAdministrators(Set<String> repoAdministrators) {
+		if (repoAdministrators != null) {
+			for (String admin : repoAdministrators) {
+				this.removeRepoAdministrator(admin);
+			}
+		}
+	}
+
+	public void removeAllRepoAdministrators() {
+		this.repoAdministrators.clear();
+	}
+	
+	public Set<String> getRepoAdministrators() {
+		return this.repoAdministrators;
+	}
+	
+	public boolean isRepoAdministrator(String username) {
+		if (username == null || username.trim().length() == 0) {
+			return false;
+		}
+		return this.repoAdministrators.contains(username.toLowerCase());
+	}
 }
\ No newline at end of file
diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java
index 54e81cb..c513feb 100644
--- a/src/com/gitblit/models/UserModel.java
+++ b/src/com/gitblit/models/UserModel.java
@@ -108,8 +108,7 @@
 	@Deprecated
 	@Unused
 	public boolean canAccessRepository(RepositoryModel repository) {
-		boolean isOwner = !StringUtils.isEmpty(repository.owner)
-				&& repository.owner.equals(username);
+		boolean isOwner = repository.isRepoAdministrator(username);
 		boolean allowAuthenticated = isAuthenticated && AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl);
 		return canAdmin() || isOwner || repositories.contains(repository.name.toLowerCase())
 				|| hasTeamAccess(repository.name) || allowAuthenticated;
@@ -304,7 +303,7 @@
 		}
 		
 		// repository owner - either specified owner or personal repository
-		if (repository.isOwner(username) || repository.isUsersPersonalRepository(username)) {
+		if (repository.isRepoAdministrator(username) || repository.isUsersPersonalRepository(username)) {
 			ap.permissionType = PermissionType.OWNER;
 			ap.permission = AccessPermission.REWIND;
 			return ap;
@@ -412,7 +411,7 @@
 			// can not fork your own repository
 			return false;
 		}
-		if (canAdmin() || repository.isOwner(username)) {
+		if (canAdmin() || repository.isRepoAdministrator(username)) {
 			return true;
 		}
 		if (!repository.allowForks) {
@@ -429,7 +428,7 @@
 	}
 	
 	public boolean canEdit(RepositoryModel model) {
-		return canAdmin() || model.isUsersPersonalRepository(username) || model.isOwner(username);
+		return canAdmin() || model.isUsersPersonalRepository(username) || model.isRepoAdministrator(username);
 	}
 	
 	/**
diff --git a/src/com/gitblit/utils/MultiConfigUtil.java b/src/com/gitblit/utils/MultiConfigUtil.java
new file mode 100644
index 0000000..1ed8122
--- /dev/null
+++ b/src/com/gitblit/utils/MultiConfigUtil.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2011 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.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utility class to convert Strings into Collections and vice versa.
+ * 
+ * @author saheba
+ *
+ */
+public class MultiConfigUtil implements Serializable {
+	private static final long serialVersionUID = 1324076956473037856L;
+
+	public static final String OPTION_SEPARATOR = ";";
+
+	/**
+	 * converts a collection of strings into a single line string by concatenating them and separating the different elements with the OPTION_SEPARATOR 
+	 * 
+	 * @param collection of strings
+	 * 
+	 * @return
+	 */
+	public String convertCollectionToSingleLineString(Collection<String> collection) {
+		String result = "";
+		for (String string : collection) {
+			if (!result.equals("")) {
+				result += OPTION_SEPARATOR;
+			}
+			result += string;
+		}
+		return result;
+	}
+	
+	/**
+	 * converts a collection of strings into a list of strings 
+	 * 
+	 * @param collection
+	 * 
+	 * @return
+	 */
+	public List<String> convertCollectionToList(Collection<String> collection) {
+		List<String> result = new ArrayList<String>();
+		for (String string : collection) {
+				result.add(string);
+		}
+		return result;
+	}
+
+	/**
+	 * converts a single line string into a set of strings by splitting the given string with the OPTION_SEPARATOR 
+	 * 
+	 * @param string which contains one or more options concatenated with the OPTION_SEPARATOR
+	 * 
+	 * @return
+	 */
+	public Set<String> convertStringToSet(String string) {
+		Set<String> result = new HashSet<String>();
+		if (string != null && string.trim().length() > 0) {
+			String[] splitted = string.split(OPTION_SEPARATOR);
+			for (int i = 0; i < splitted.length; i++) {
+				String possible = splitted[i].trim();
+				if (possible.length() > 0) {
+					result.add(possible);
+				}
+			}
+		}
+		return result;
+	}
+}
diff --git a/src/com/gitblit/utils/StringComparator.java b/src/com/gitblit/utils/StringComparator.java
new file mode 100644
index 0000000..da9b347
--- /dev/null
+++ b/src/com/gitblit/utils/StringComparator.java
@@ -0,0 +1,24 @@
+package com.gitblit.utils;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * A comparator for {@link java.util.TreeSet} that sorts strings ascending inside the {@link java.util.TreeSet}  
+ * 
+ * @author saheba
+ *
+ */
+public class StringComparator implements Comparator<String>, Serializable {
+	private static final long serialVersionUID = 7563266118711225424L;
+
+	/* (non-Javadoc)
+	 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
+	 */
+	@Override
+	public int compare(String o1, String o2) {
+		// TODO Auto-generated method stub
+		return o1.compareTo(o2);
+	}
+
+}
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index 16f7641..d601e1e 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -1,5 +1,5 @@
 gb.repository = repository
-gb.owner = owner
+gb.repoAdministrators = repository administrators
 gb.description = description
 gb.lastChange = last change
 gb.refs = refs
@@ -94,7 +94,7 @@
 gb.showReadme = show readme
 gb.showReadmeDescription = show a \"readme\" Markdown file on the summary page
 gb.nameDescription = use '/' to group repositories.  e.g. libraries/mycoollib.git
-gb.ownerDescription = the owner may edit repository settings
+gb.repoAdministratorsDescription = the repository administrators may edit repository settings
 gb.blob = blob
 gb.commitActivityTrend = commit activity trend
 gb.commitActivityDOW = commit activity by day of week
@@ -279,7 +279,7 @@
 gb.emailAddress = email address
 gb.errorAdminLoginRequired = Administration requires a login
 gb.errorOnlyAdminMayCreateRepository = Only an administrator may create a repository
-gb.errorOnlyAdminOrOwnerMayEditRepository = Only an administrator or the owner may edit a repository
+gb.errorOnlyAdminOrRepoAdminMayEditRepository = Only an administrator or a repository administrator may edit a repository
 gb.errorAdministrationDisabled = Administration is disabled
 gb.lastNDays = last {0} days
 gb.completeGravatarProfile = Complete profile on Gravatar.com
@@ -364,7 +364,7 @@
 gb.gcPeriodDescription = duration between garbage collections
 gb.gcThreshold = GC threshold
 gb.gcThresholdDescription = minimum total size of loose objects to trigger early garbage collection
-gb.ownerPermission = repository owner
+gb.repoAdministratorPermission = repository administrator
 gb.administrator = admin
 gb.administratorPermission = Gitblit administrator
 gb.team = team
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_es.properties b/src/com/gitblit/wicket/GitBlitWebApp_es.properties
index 64c9ca1..478a0ab 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp_es.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp_es.properties
@@ -1,5 +1,5 @@
 gb.repository = Repositorio
-gb.owner = Propietario
+gb.repoAdministrators = Administradores del repositorio
 gb.description = Descripci\u00F3n
 gb.lastChange = Actualizado
 gb.refs = Refs
@@ -94,7 +94,7 @@
 gb.showReadme = Ver l\u00E9eme
 gb.showReadmeDescription = Mostrar el archivo \"l\u00E9eme\" de Markdown en la p\u00E1gina resumen
 gb.nameDescription = Usa '/' para agrupar repositorios. ej. librerias/mylibreria.git
-gb.ownerDescription = El propietario puede editar la configuraci\u00F3n del repositorio
+gb.repoAdministratorsDescription = Administradores del repositorio puede editar la configuraci\u00F3n del repositorio
 gb.blob = Objeto
 gb.commitActivityTrend = Tendencia de actividad del repositorio
 gb.commitActivityDOW = Actividad de consignas por d\u00EDa de la semana
@@ -364,7 +364,7 @@
 gb.gcPeriodDescription = Duraci\u00F3n entre periodos de limpieza
 gb.gcThreshold = L\u00EDmites para GC
 gb.gcThresholdDescription = Tama\u00F1o m\u00EDnimo total de objetos sueltos para activar la recolecci\u00F3n inmediata de basura
-gb.ownerPermission = Propietario del repositorio
+gb.repoAdministratorPermission = Administrador del repositorio
 gb.administrator = Admin
 gb.administratorPermission = Administrador de Gitblit
 gb.team = Equipo
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_ja.properties b/src/com/gitblit/wicket/GitBlitWebApp_ja.properties
index 086df7b..d4e3511 100755
--- a/src/com/gitblit/wicket/GitBlitWebApp_ja.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp_ja.properties
@@ -1,5 +1,5 @@
 gb.repository = \u30ea\u30dd\u30b8\u30c8\u30ea
-gb.owner = \u6240\u6709\u8005
+gb.repoAdministrators = \u30EA\u30DD\u30B8\u30C8\u30EA\u7BA1\u7406\u8005
 gb.description = \u8aac\u660e
 gb.lastChange = \u6700\u5f8c\u306e\u5909\u66f4
 gb.refs = refs
@@ -94,7 +94,7 @@
 gb.showReadme = readme\u8868\u793a
 gb.showReadmeDescription = \"readme\" Markdown\u30d5\u30a1\u30a4\u30eb\u3092\u6982\u8981\u30da\u30fc\u30b8\u306b\u8868\u793a\u3059\u308b
 gb.nameDescription = \u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u30b0\u30eb\u30fc\u30d7\u5316\u3059\u308b\u306b\u306f '/' \u3092\u4f7f\u3046\u3002 e.g. libraries/mycoollib.git
-gb.ownerDescription = \u6240\u6709\u8005\u306f\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u8a2d\u5b9a\u3092\u5909\u66f4\u3067\u304d\u308b
+gb.repoAdministratorsDescription = \u30EA\u30DD\u30B8\u30C8\u30EA\u7BA1\u7406\u8005\u306f\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u8a2d\u5b9a\u3092\u5909\u66f4\u3067\u304d\u308b
 gb.blob = blob
 gb.commitActivityTrend = commit activity trend
 gb.commitActivityDOW = commit activity by day of week
@@ -279,7 +279,7 @@
 gb.emailAddress = email address
 gb.errorAdminLoginRequired = Administration requires a login
 gb.errorOnlyAdminMayCreateRepository = Only an administrator may create a repository
-gb.errorOnlyAdminOrOwnerMayEditRepository = Only an administrator or the owner may edit a repository
+gb.errorOnlyAdminOrRepoAdminMayEditRepository = Only an administrator or a repository administrator may edit a repository
 gb.errorAdministrationDisabled = Administration is disabled
 gb.lastNDays = last {0} days
 gb.completeGravatarProfile = Complete profile on Gravatar.com
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_ko.properties b/src/com/gitblit/wicket/GitBlitWebApp_ko.properties
index 18eda26..bff593d 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp_ko.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp_ko.properties
@@ -1,5 +1,5 @@
 gb.repository = \uC800\uC7A5\uC18C
-gb.owner = \uC18C\uC720\uC790
+gb.repoAdministrators = \uC800\uC7A5\uC18C \uAD00\uB9AC\uC790
 gb.description = \uC124\uBA85
 gb.lastChange = \uCD5C\uADFC \uBCC0\uACBD
 gb.refs = refs
@@ -94,7 +94,7 @@
 gb.showReadme = \uB9AC\uB4DC\uBBF8(readme) \uBCF4\uAE30
 gb.showReadmeDescription = \uC694\uC57D\uD398\uC774\uC9C0\uC5D0\uC11C \"readme\" \uB9C8\uD06C\uB2E4\uC6B4 \uD30C\uC77C \uBCF4\uAE30
 gb.nameDescription = \uC800\uC7A5\uC18C\uB97C \uADF8\uB8F9\uC73C\uB85C \uBB36\uC73C\uB824\uBA74 '/' \uB97C \uC0AC\uC6A9. \uC608) libraries/reponame.git
-gb.ownerDescription = \uC18C\uC720\uC790\uB294 \uC800\uC7A5\uC18C \uC124\uC815\uC744 \uBCC0\uACBD\uD560 \uC218 \uC788\uC74C
+gb.repoAdministratorsDescription = \uC800\uC7A5\uC18C \uAD00\uB9AC\uC790 \uC800\uC7A5\uC18C \uC124\uC815\uC744 \uBCC0\uACBD\uD560 \uC218 \uC788\uC74C
 gb.blob = blob
 gb.commitActivityTrend = \uCEE4\uBC0B \uD65C\uB3D9 \uD2B8\uB79C\uB4DC
 gb.commitActivityDOW = 1\uC8FC\uC77C\uC758 \uC77C\uB2E8\uC704 \uCEE4\uBC0B \uD65C\uB3D9
@@ -279,7 +279,7 @@
 gb.emailAddress = \uC774\uBA54\uC77C \uC8FC\uC18C
 gb.errorAdminLoginRequired = \uAD00\uB9AC\uB97C \uC704\uD574\uC11C\uB294 \uB85C\uADF8\uC778\uC774 \uD544\uC694
 gb.errorOnlyAdminMayCreateRepository = \uAD00\uB9AC\uC790\uB9CC \uC800\uC7A5\uC18C\uB97C \uB9CC\uB4E4\uC218 \uC788\uC74C
-gb.errorOnlyAdminOrOwnerMayEditRepository = \uAD00\uB9AC\uC790\uC640 \uC18C\uC720\uC790\uB9CC \uC800\uC7A5\uC18C\uB97C \uC218\uC815\uD560 \uC218 \uC788\uC74C
+gb.errorOnlyAdminOrRepoAdminMayEditRepository = \uAD00\uB9AC\uC790\uC640 \uC800\uC7A5\uC18C \uAD00\uB9AC\uC790\uB9CC \uC800\uC7A5\uC18C\uB97C \uC218\uC815\uD560 \uC218 \uC788\uC74C
 gb.errorAdministrationDisabled = \uAD00\uB9AC\uAE30\uB2A5 \uBE44\uD65C\uC131\uD654\uB428
 gb.lastNDays = {0} \uC77C\uC804
 gb.completeGravatarProfile = Gravatar.com \uC5D0 \uD504\uB85C\uD30C\uC77C \uC0DD\uC131\uB428
@@ -364,7 +364,7 @@
 gb.gcPeriodDescription = \uAC00\uBE44\uC9C0 \uD074\uB809\uC158\uAC04\uC758 \uC2DC\uAC04 \uAC04\uACA9
 gb.gcThreshold = GC \uAE30\uC900\uC810
 gb.gcThresholdDescription = \uC870\uAE30 \uAC00\uBE44\uC9C0 \uCEEC\uB809\uC158\uC744 \uBC1C\uC0DD\uC2DC\uD0A4\uAE30 \uC704\uD55C \uC624\uBE0C\uC81D\uD2B8\uB4E4\uC758 \uCD5C\uC18C \uC804\uCCB4 \uD06C\uAE30
-gb.ownerPermission = \uC800\uC7A5\uC18C \uC624\uB108
+gb.repoAdministratorPermission = \uC800\uC7A5\uC18C \uAD00\uB9AC\uC790
 gb.administrator = \uAD00\uB9AC\uC790
 gb.administratorPermission = Gitblit \uAD00\uB9AC\uC790
 gb.team = \uD300
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_nl.properties b/src/com/gitblit/wicket/GitBlitWebApp_nl.properties
index 5471ad8..75a0ac6 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp_nl.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp_nl.properties
@@ -1,5 +1,5 @@
 gb.repository = repositorie
-gb.owner = eigenaar
+gb.repoAdministrators = repository beheerders
 gb.description = omschrijving
 gb.lastChange = laatste wijziging
 gb.refs = refs
@@ -94,7 +94,7 @@
 gb.showReadme = toon readme
 gb.showReadmeDescription = toon een \"readme\" Markdown bestand in de samenvattingspagina
 gb.nameDescription = gebruik '/' voor het groeperen van repositories.  bijv. libraries/mycoollib.git
-gb.ownerDescription = de eigenaar mag repository instellingen wijzigen
+gb.repoAdministratorsDescription = repository beheerders mag repository instellingen wijzigen
 gb.blob = blob
 gb.commitActivityTrend = commit activiteit trend
 gb.commitActivityDOW = commit activiteit per dag van de week
@@ -279,7 +279,7 @@
 gb.emailAddress = emailadres
 gb.errorAdminLoginRequired = Aanmelden vereist voor beheerwerk
 gb.errorOnlyAdminMayCreateRepository = Alleen een beheerder kan een repositorie maken
-gb.errorOnlyAdminOrOwnerMayEditRepository = Alleen een beheerder of de eigenaar kan een repositorie wijzigen
+gb.errorOnlyAdminOrOwnerMayEditRepository = Alleen een beheerder of een repository beheerder kan een repositorie wijzigen
 gb.errorAdministrationDisabled = Beheer is uitgeschakeld
 gb.lastNDays = laatste {0} dagen
 gb.completeGravatarProfile = Completeer profiel op Gravatar.com
@@ -364,7 +364,7 @@
 gb.gcPeriodDescription = tijdsduur tussen opruimacties
 gb.gcThreshold = opruim drempel
 gb.gcThresholdDescription = minimum totaalomvang van losse objecten voor het starten van opruimactie
-gb.ownerPermission = repositorie eigenaar
+gb.repoAdministratorPermission = repository beheerder
 gb.administrator = beheer
 gb.administratorPermission = Gitblit beheerder
 gb.team = team
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_pl.properties b/src/com/gitblit/wicket/GitBlitWebApp_pl.properties
index 82ffa63..4760981 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp_pl.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp_pl.properties
@@ -1,5 +1,5 @@
 gb.repository = Repozytorium
-gb.owner = W\u0142a\u015Bciciel
+gb.repoAdministrators = Administratorzy repozytorium
 gb.description = Opis
 gb.lastChange = Ostatnia zmiana
 gb.refs = Refs
@@ -94,7 +94,7 @@
 gb.showReadme = Poka\u017C readme
 gb.showReadmeDescription = Poka\u017C sparsowany \"readme\" na stronie podsumowania
 gb.nameDescription = u\u017Cyj '/' do grupowania repozytori\u00F3w, np. libraries/server-lib.git
-gb.ownerDescription = W\u0142a\u015Bciciel mo\u017Ce edytowa\u0107 ustawienia repozytorium
+gb.repoAdministratorsDescription = Administrator repozytorium mo\u017Ce edytowa\u0107 ustawienia repozytorium
 gb.blob = blob
 gb.commitActivityTrend = Aktywno\u015B\u0107 zmian
 gb.commitActivityDOW = Aktywno\u015B\u0107 zmian wed\u0142ug dnia tygodnia
@@ -279,7 +279,7 @@
 gb.emailAddress = Adres email
 gb.errorAdminLoginRequired = Administracja wymaga zalogowania
 gb.errorOnlyAdminMayCreateRepository = Tylko administrator mo\u017Ce utworzy\u0107 repozytorium
-gb.errorOnlyAdminOrOwnerMayEditRepository = Tylko administrator lub w\u0142a\u015Bciciel mo\u017Ce edytowa\u0107 repozytorium.
+gb.errorOnlyAdminOrRepoAdminMayEditRepository = Tylko administrator lub administrator repozytorium mo\u017Ce edytowa\u0107 repozytorium.
 gb.errorAdministrationDisabled = Administracja jest wy\u0142\u0105czona
 gb.lastNDays = Ostatnich {0} dni
 gb.completeGravatarProfile = Pe\u0142ny profil na Gravatar.com
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties b/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties
index 469d205..0bbd119 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties
@@ -1,5 +1,5 @@
 gb.repository = reposit�rio
-gb.owner = propriet�rio
+gb.repoAdministrators = administradores do reposit�rio
 gb.description = descri��o
 gb.lastChange = �ltima altera��o
 gb.refs = refs
@@ -94,7 +94,7 @@
 gb.showReadme = mostrar readme
 gb.showReadmeDescription = mostrar um arquivo \"leia-me\" na p�gina de resumo
 gb.nameDescription = usar '/' para agrupar reposit�rios.  e.g. libraries/mycoollib.git
-gb.ownerDescription = o propriet�rio pode editar configura��es do reposit�rio
+gb.repoAdministratorsDescription = o administradores do reposit�rio pode editar configura��es do reposit�rio
 gb.blob = blob
 gb.commitActivityTrend = tend�ncia dos commits
 gb.commitActivityDOW = commits di�rios
@@ -279,7 +279,7 @@
 gb.emailAddress = e-mail
 gb.errorAdminLoginRequired = Administra��o requer um login
 gb.errorOnlyAdminMayCreateRepository = Somente umadministrador pode criar um reposit�rio
-gb.errorOnlyAdminOrOwnerMayEditRepository = Somente umadministrador pode editar um reposit�rio
+gb.errorOnlyAdminOrRepoAdminMayEditRepository = Somente umadministrador ou um administrador de reposit�rio pode editar um reposit�rio
 gb.errorAdministrationDisabled = Administra��o est� desabilitada
 gb.lastNDays = �ltimos {0} dias
 gb.completeGravatarProfile = Profile completo em Gravatar.com
@@ -364,7 +364,7 @@
 gb.gcPeriodDescription = dura��o entre as coletas de lixo
 gb.gcThreshold = limite do GC 
 gb.gcThresholdDescription = tamanho total m�nimo de objetos \"soltos\" que ativam a coleta de lixo
-gb.ownerPermission = propriet�rio do reposit�rio
+gb.repoAdministratorPermission = administrador do reposit�rio
 gb.administrator = administrador
 gb.administratorPermission = administrador do Gitblit
 gb.team = equipe
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/com/gitblit/wicket/pages/EditRepositoryPage.html
index 60893f4..be9616c 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.html
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -50,7 +50,7 @@
 		<div class="tab-pane" id="permissions">
 			<table class="plain">
 				<tbody class="settings">
-					<tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><select class="span2" wicket:id="owner" tabindex="15" /> &nbsp;<span class="help-inline"><wicket:message key="gb.ownerDescription"></wicket:message></span></td></tr>
+					<tr><th><wicket:message key="gb.repoAdministrators"></wicket:message></th><td class="edit"><span wicket:id="repoAdministrators" tabindex="15" /> &nbsp;<span class="help-inline"><wicket:message key="gb.repoAdministratorsDescription"></wicket:message></span></td></tr>
 					<tr><th colspan="2"><hr/></th></tr>
 					<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="16" /></td></tr>
 					<tr><th colspan="2"><hr/></th></tr>
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
index a071b69..9a81bde 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -61,6 +61,7 @@
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.MultiConfigUtil;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.StringChoiceRenderer;
@@ -70,6 +71,8 @@
 
 public class EditRepositoryPage extends RootSubPage {
 
+	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();
+	
 	private final boolean isCreate;
 
 	private boolean isAdmin;
@@ -94,7 +97,7 @@
 			// personal create permissions, inject personal repository path
 			model.name = user.getPersonalPath() + "/";
 			model.projectPath = user.getPersonalPath();
-			model.owner = user.username;
+			model.addRepoAdministrator(user.username);
 			// personal repositories are private by default
 			model.accessRestriction = AccessRestrictionType.VIEW;
 			model.authorizationControl = AuthorizationControl.NAMED;
@@ -164,6 +167,12 @@
 		final RegistrantPermissionsPanel teamsPalette = new RegistrantPermissionsPanel("teams", 
 				RegistrantType.TEAM, GitBlit.self().getAllTeamnames(), repositoryTeams, getAccessPermissions());
 
+		// repo administrators palette
+		List admins = multiConfigUtil.convertCollectionToList(repositoryModel.getRepoAdministrators());
+		List persons = GitBlit.self().getAllUsernames();
+		final Palette repoAdministratorsPalette = new Palette("repoAdministrators", new ListModel<String>(admins), new CollectionModel<String>(
+		      persons), new StringChoiceRenderer(), 10, true);
+		
 		// indexed local branches palette
 		List<String> allLocalBranches = new ArrayList<String>();
 		allLocalBranches.add(Constants.DEFAULT_BRANCH);
@@ -326,6 +335,12 @@
 					}
 					repositoryModel.indexedBranches = indexedBranches;
 
+					repositoryModel.removeAllRepoAdministrators();
+					Iterator<String> repoAdmins = repoAdministratorsPalette.getSelectedChoices();
+					while (repoAdmins.hasNext()) {
+						repositoryModel.addRepoAdministrator(repoAdmins.next());
+					}
+					
 					// pre-receive scripts
 					List<String> preReceiveScripts = new ArrayList<String>();
 					Iterator<String> pres = preReceivePalette.getSelectedChoices();
@@ -377,8 +392,7 @@
 		// field names reflective match RepositoryModel fields
 		form.add(new TextField<String>("name").setEnabled(allowEditName));
 		form.add(new TextField<String>("description"));
-		form.add(new DropDownChoice<String>("owner", GitBlit.self().getAllUsernames())
-				.setEnabled(GitBlitWebSession.get().canAdmin() && !repositoryModel.isPersonalRepository()));
+		form.add(repoAdministratorsPalette);
 		form.add(new CheckBox("allowForks").setEnabled(GitBlit.getBoolean(Keys.web.allowForking, true)));
 		DropDownChoice<AccessRestrictionType> accessRestriction = new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays
 				.asList(AccessRestrictionType.values()), new AccessRestrictionRenderer());
@@ -559,9 +573,9 @@
 						isAdmin = true;
 						return;
 					} else {
-						if (!model.owner.equalsIgnoreCase(user.username)) {
-							// User is not an Admin nor Owner
-							error(getString("gb.errorOnlyAdminOrOwnerMayEditRepository"), true);
+						if (!model.isRepoAdministrator(user.username)) {
+							// User is not an Admin nor RepoAdministrator
+							error(getString("gb.errorOnlyAdminOrRepoAdminMayEditRepository"), true);
 						}
 					}
 				}
diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.java b/src/com/gitblit/wicket/pages/RepositoryPage.java
index aac527d..b67aaa6 100644
--- a/src/com/gitblit/wicket/pages/RepositoryPage.java
+++ b/src/com/gitblit/wicket/pages/RepositoryPage.java
@@ -79,7 +79,7 @@
 	
 	private final Map<String, PageRegistration> registeredPages;
 	private boolean showAdmin;
-	private boolean isOwner;
+	private boolean isRepoAdministrator;
 	
 	public RepositoryPage(PageParameters params) {
 		super(params);
@@ -183,10 +183,10 @@
 		} else {
 			showAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, false);
 		}
-		isOwner = GitBlitWebSession.get().isLoggedIn()
-				&& (model.owner != null && model.owner.equalsIgnoreCase(GitBlitWebSession.get()
+		isRepoAdministrator = GitBlitWebSession.get().isLoggedIn()
+				&& (model.isRepoAdministrator(GitBlitWebSession.get()
 						.getUsername()));
-		if (showAdmin || isOwner) {
+		if (showAdmin || isRepoAdministrator) {
 			pages.put("edit", new PageRegistration("gb.edit", EditRepositoryPage.class, params));
 		}
 		return pages;
@@ -540,7 +540,7 @@
 	}
 	
 	public boolean isOwner() {
-		return isOwner;
+		return isRepoAdministrator;
 	}
 	
 	private class SearchForm extends SessionlessForm<Void> implements Serializable {
diff --git a/src/com/gitblit/wicket/pages/RootSubPage.java b/src/com/gitblit/wicket/pages/RootSubPage.java
index e7e12cc..ed1bdfb 100644
--- a/src/com/gitblit/wicket/pages/RootSubPage.java
+++ b/src/com/gitblit/wicket/pages/RootSubPage.java
@@ -88,7 +88,7 @@
 			if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)
 					&& repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED)) {
 				if (user != null &&
-						(repositoryModel.isOwner(user.username) || repositoryModel.isUsersPersonalRepository(user.username))) {
+						(repositoryModel.isRepoAdministrator(user.username) || repositoryModel.isUsersPersonalRepository(user.username))) {
 					// exclude Owner or personal repositories
 					continue;
 				}
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.html b/src/com/gitblit/wicket/pages/SummaryPage.html
index 45ffddf..788dbb5 100644
--- a/src/com/gitblit/wicket/pages/SummaryPage.html
+++ b/src/com/gitblit/wicket/pages/SummaryPage.html
@@ -16,7 +16,7 @@
 		<div class="hidden-phone" style="padding-bottom: 10px;"> 
 			<table class="plain">
 				<tr><th><wicket:message key="gb.description">[description]</wicket:message></th><td><span wicket:id="repositoryDescription">[repository description]</span></td></tr>
-				<tr><th><wicket:message key="gb.owner">[owner]</wicket:message></th><td><span wicket:id="repositoryOwner">[repository owner]</span></td></tr>
+				<tr><th><wicket:message key="gb.repoAdministrators">[owner]</wicket:message></th><td><span wicket:id="repositoryAdministrators">[repository owner]</span></td></tr>
 				<tr><th><wicket:message key="gb.lastChange">[last change]</wicket:message></th><td><span wicket:id="repositoryLastChange">[repository last change]</span></td></tr>
 				<tr><th><wicket:message key="gb.stats">[stats]</wicket:message></th><td><span wicket:id="branchStats">[branch stats]</span> <span class="link"><a wicket:id="metrics"><wicket:message key="gb.metrics">[metrics]</wicket:message></a></span></td></tr>
 				<tr><th style="vertical-align:top;"><wicket:message key="gb.repositoryUrl">[URL]</wicket:message>&nbsp;<img style="vertical-align: top;padding-left:3px;" wicket:id="accessRestrictionIcon" /></th><td><span wicket:id="repositoryCloneUrl">[repository clone url]</span><div wicket:id="otherUrls"></div></td></tr>
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.java b/src/com/gitblit/wicket/pages/SummaryPage.java
index 8df2ceb..3b2c92a 100644
--- a/src/com/gitblit/wicket/pages/SummaryPage.java
+++ b/src/com/gitblit/wicket/pages/SummaryPage.java
@@ -49,6 +49,7 @@
 import com.gitblit.utils.ArrayUtils;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.MultiConfigUtil;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.BranchesPanel;
@@ -59,6 +60,8 @@
 
 public class SummaryPage extends RepositoryPage {
 
+	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();
+	
 	public SummaryPage(PageParameters params) {
 		super(params);
 
@@ -82,16 +85,12 @@
 
 		// repository description
 		add(new Label("repositoryDescription", getRepositoryModel().description));
-		String owner = getRepositoryModel().owner;
-		if (StringUtils.isEmpty(owner)) {
-			add(new Label("repositoryOwner").setVisible(false));
-		} else {
-			UserModel ownerModel = GitBlit.self().getUserModel(owner);
-			if (ownerModel != null) {
-				add(new LinkPanel("repositoryOwner", null, ownerModel.getDisplayName(), UserPage.class, WicketUtils.newUsernameParameter(owner)));
-			} else {
-				add(new Label("repositoryOwner", owner));
-			}
+		String repoAdministrators = multiConfigUtil.convertCollectionToSingleLineString(getRepositoryModel().getRepoAdministrators());
+		if (StringUtils.isEmpty(repoAdministrators)) {
+			add(new Label("repositoryAdministrators").setVisible(false));
+		} else {			
+			//TODO reimplement link panel for each username
+			add(new Label("repositoryAdministrators", repoAdministrators));			
 		}
 
 		add(WicketUtils.createTimestampLabel("repositoryLastChange",
diff --git a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
index 4678153..e953235 100644
--- a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
+++ b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
@@ -51,7 +51,7 @@
 						<img style="border:0px;vertical-align:middle;" src="feed_16x16.png"></img>
 					</a>
 				</div>
-				<span style="color: #999;font-style:italic;font-size:0.8em;" wicket:id="repositoryOwner">[owner]</span>
+				<span style="color: #999;font-style:italic;font-size:0.8em;" wicket:id="repositoryAdministrators">[owner]</span>
 			</div>	
 			
 			<div class="pageTitle" style="border:0px;">
diff --git a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
index 50f0d52..d4f2a3a 100644
--- a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
+++ b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
@@ -36,6 +36,7 @@
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.MultiConfigUtil;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.WicketUtils;
@@ -51,86 +52,104 @@
 
 	private static final long serialVersionUID = 1L;
 
-	public ProjectRepositoryPanel(String wicketId, Localizer localizer, Component parent,
-			final boolean isAdmin, final RepositoryModel entry,
+	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();
+
+	public ProjectRepositoryPanel(String wicketId, Localizer localizer,
+			Component parent, final boolean isAdmin,
+			final RepositoryModel entry,
 			final Map<AccessRestrictionType, String> accessRestrictions) {
 		super(wicketId);
 
-		final boolean showSwatch = GitBlit.getBoolean(Keys.web.repositoryListSwatches, true);
-		final boolean gitServlet = GitBlit.getBoolean(Keys.git.enableGitServlet, true);
-		final boolean showSize = GitBlit.getBoolean(Keys.web.showRepositorySizes, true);
+		final boolean showSwatch = GitBlit.getBoolean(
+				Keys.web.repositoryListSwatches, true);
+		final boolean gitServlet = GitBlit.getBoolean(
+				Keys.git.enableGitServlet, true);
+		final boolean showSize = GitBlit.getBoolean(
+				Keys.web.showRepositorySizes, true);
 
 		// repository swatch
 		Component swatch;
 		if (entry.isBare) {
-			swatch = new Label("repositorySwatch", "&nbsp;").setEscapeModelStrings(false);
+			swatch = new Label("repositorySwatch", "&nbsp;")
+					.setEscapeModelStrings(false);
 		} else {
 			swatch = new Label("repositorySwatch", "!");
-			WicketUtils.setHtmlTooltip(swatch, localizer.getString("gb.workingCopyWarning", parent));
+			WicketUtils.setHtmlTooltip(swatch,
+					localizer.getString("gb.workingCopyWarning", parent));
 		}
 		WicketUtils.setCssBackground(swatch, entry.toString());
 		add(swatch);
 		swatch.setVisible(showSwatch);
 
 		PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
-		add(new LinkPanel("repositoryName", "list", StringUtils.getRelativePath(entry.projectPath,
-				StringUtils.stripDotGit(entry.name)), SummaryPage.class, pp));
-		add(new Label("repositoryDescription", entry.description).setVisible(!StringUtils
-				.isEmpty(entry.description)));
+		add(new LinkPanel("repositoryName", "list",
+				StringUtils.getRelativePath(entry.projectPath,
+						StringUtils.stripDotGit(entry.name)),
+				SummaryPage.class, pp));
+		add(new Label("repositoryDescription", entry.description)
+				.setVisible(!StringUtils.isEmpty(entry.description)));
 
 		if (StringUtils.isEmpty(entry.originRepository)) {
 			add(new Label("originRepository").setVisible(false));
 		} else {
-			Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
-			forkFrag.add(new LinkPanel("originRepository", null, StringUtils.stripDotGit(entry.originRepository), 
-					SummaryPage.class, WicketUtils.newRepositoryParameter(entry.originRepository)));
+			Fragment forkFrag = new Fragment("originRepository",
+					"originFragment", this);
+			forkFrag.add(new LinkPanel("originRepository", null, StringUtils
+					.stripDotGit(entry.originRepository), SummaryPage.class,
+					WicketUtils.newRepositoryParameter(entry.originRepository)));
 			add(forkFrag);
 		}
 
-		add(new BookmarkablePageLink<Void>("tickets", TicketsPage.class, pp).setVisible(entry.useTickets));
-		add(new BookmarkablePageLink<Void>("docs", DocsPage.class, pp).setVisible(entry.useDocs));
+		add(new BookmarkablePageLink<Void>("tickets", TicketsPage.class, pp)
+				.setVisible(entry.useTickets));
+		add(new BookmarkablePageLink<Void>("docs", DocsPage.class, pp)
+				.setVisible(entry.useDocs));
 
 		if (entry.isFrozen) {
-			add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", localizer.getString("gb.isFrozen", parent)));
+			add(WicketUtils.newImage("frozenIcon", "cold_16x16.png",
+					localizer.getString("gb.isFrozen", parent)));
 		} else {
 			add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
 		}
 
 		if (entry.isFederated) {
-			add(WicketUtils.newImage("federatedIcon", "federated_16x16.png", localizer.getString("gb.isFederated", parent)));
+			add(WicketUtils.newImage("federatedIcon", "federated_16x16.png",
+					localizer.getString("gb.isFederated", parent)));
 		} else {
 			add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));
 		}
 		switch (entry.accessRestriction) {
 		case NONE:
-			add(WicketUtils.newBlankImage("accessRestrictionIcon").setVisible(false));
+			add(WicketUtils.newBlankImage("accessRestrictionIcon").setVisible(
+					false));
 			break;
 		case PUSH:
-			add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
+			add(WicketUtils.newImage("accessRestrictionIcon",
+					"lock_go_16x16.png",
 					accessRestrictions.get(entry.accessRestriction)));
 			break;
 		case CLONE:
-			add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
+			add(WicketUtils.newImage("accessRestrictionIcon",
+					"lock_pull_16x16.png",
 					accessRestrictions.get(entry.accessRestriction)));
 			break;
 		case VIEW:
-			add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
+			add(WicketUtils.newImage("accessRestrictionIcon",
+					"shield_16x16.png",
 					accessRestrictions.get(entry.accessRestriction)));
 			break;
 		default:
 			add(WicketUtils.newBlankImage("accessRestrictionIcon"));
 		}
 
-		if (StringUtils.isEmpty(entry.owner)) {
-			add(new Label("repositoryOwner").setVisible(false));
+		if (entry.getRepoAdministrators().size() < 1) {
+			add(new Label("repositoryAdministrators").setVisible(false));
 		} else {
-			UserModel ownerModel = GitBlit.self().getUserModel(entry.owner);
-			String owner = entry.owner;
-			if (ownerModel != null) {
-				owner = ownerModel.getDisplayName();
-			}
-			add(new Label("repositoryOwner", owner + " (" +
-					localizer.getString("gb.owner", parent) + ")"));
+			add(new Label("repositoryAdministrators",
+					multiConfigUtil.convertCollectionToSingleLineString(entry
+							.getRepoAdministrators())
+							+ " ("
+							+ localizer.getString("gb.repoAdministrators", parent) + ")"));
 		}
 
 		UserModel user = GitBlitWebSession.get().getUser();
@@ -138,15 +157,19 @@
 			user = UserModel.ANONYMOUS;
 		}
 		Fragment repositoryLinks;
-		boolean showOwner = entry.isOwner(user.username);
+		boolean isRepoAdministrator = entry.isRepoAdministrator(user.username);
 		// owner of personal repository gets admin powers
-		boolean showAdmin = isAdmin || entry.isUsersPersonalRepository(user.username);
+		boolean showAdmin = isAdmin
+				|| entry.isUsersPersonalRepository(user.username);
 
-		if (showAdmin || showOwner) {
-			repositoryLinks = new Fragment("repositoryLinks", showAdmin ? "repositoryAdminLinks"
-					: "repositoryOwnerLinks", this);
-			repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository", EditRepositoryPage.class,
-					WicketUtils.newRepositoryParameter(entry.name)));
+		if (showAdmin || isRepoAdministrator) {
+			repositoryLinks = new Fragment(
+					"repositoryLinks",
+					showAdmin ? "repositoryAdminLinks" : "repositoryOwnerLinks",
+					this);
+			repositoryLinks.add(new BookmarkablePageLink<Void>(
+					"editRepository", EditRepositoryPage.class, WicketUtils
+							.newRepositoryParameter(entry.name)));
 			if (showAdmin) {
 				Link<Void> deleteLink = new Link<Void>("deleteRepository") {
 
@@ -157,28 +180,41 @@
 						if (GitBlit.self().deleteRepositoryModel(entry)) {
 							// redirect to the owning page
 							if (entry.isPersonalRepository()) {
-								setResponsePage(getPage().getClass(), WicketUtils.newUsernameParameter(entry.projectPath.substring(1)));
+								setResponsePage(
+										getPage().getClass(),
+										WicketUtils
+												.newUsernameParameter(entry.projectPath
+														.substring(1)));
 							} else {
-								setResponsePage(getPage().getClass(), WicketUtils.newProjectParameter(entry.projectPath));
+								setResponsePage(
+										getPage().getClass(),
+										WicketUtils
+												.newProjectParameter(entry.projectPath));
 							}
 						} else {
-							error(MessageFormat.format(getString("gb.repositoryDeleteFailed"), entry));
+							error(MessageFormat.format(
+									getString("gb.repositoryDeleteFailed"),
+									entry));
 						}
 					}
 				};
-				deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(
-						localizer.getString("gb.deleteRepository", parent), entry)));
+				deleteLink.add(new JavascriptEventConfirmation("onclick",
+						MessageFormat.format(localizer.getString(
+								"gb.deleteRepository", parent), entry)));
 				repositoryLinks.add(deleteLink);
 			}
 		} else {
-			repositoryLinks = new Fragment("repositoryLinks", "repositoryUserLinks", this);
+			repositoryLinks = new Fragment("repositoryLinks",
+					"repositoryUserLinks", this);
 		}
 
-		repositoryLinks.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils
-				.newRepositoryParameter(entry.name)).setEnabled(entry.hasCommits));
+		repositoryLinks.add(new BookmarkablePageLink<Void>("tree",
+				TreePage.class, WicketUtils.newRepositoryParameter(entry.name))
+				.setEnabled(entry.hasCommits));
 
-		repositoryLinks.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils
-				.newRepositoryParameter(entry.name)).setEnabled(entry.hasCommits));
+		repositoryLinks.add(new BookmarkablePageLink<Void>("log",
+				LogPage.class, WicketUtils.newRepositoryParameter(entry.name))
+				.setEnabled(entry.hasCommits));
 
 		add(repositoryLinks);
 
@@ -190,17 +226,20 @@
 		}
 		Label lastChangeLabel = new Label("repositoryLastChange", lastChange);
 		add(lastChangeLabel);
-		WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange));
+		WicketUtils.setCssClass(lastChangeLabel,
+				getTimeUtils().timeAgoCss(entry.lastChange));
 
 		if (entry.hasCommits) {
 			// Existing repository
 			add(new Label("repositorySize", entry.size).setVisible(showSize));
 		} else {
 			// New repository
-			add(new Label("repositorySize", localizer.getString("gb.empty", parent)).setEscapeModelStrings(false));
+			add(new Label("repositorySize", localizer.getString("gb.empty",
+					parent)).setEscapeModelStrings(false));
 		}
 
-		add(new ExternalLink("syndication", SyndicationServlet.asLink("", entry.name, null, 0)));
+		add(new ExternalLink("syndication", SyndicationServlet.asLink("",
+				entry.name, null, 0)));
 
 		List<String> repositoryUrls = new ArrayList<String>();
 		if (gitServlet) {
@@ -209,7 +248,8 @@
 		}
 		repositoryUrls.addAll(GitBlit.self().getOtherCloneUrls(entry.name));
 
-		String primaryUrl = ArrayUtils.isEmpty(repositoryUrls) ? "" : repositoryUrls.remove(0);
+		String primaryUrl = ArrayUtils.isEmpty(repositoryUrls) ? ""
+				: repositoryUrls.remove(0);
 		add(new RepositoryUrlPanel("repositoryCloneUrl", primaryUrl));
 	}
 }
diff --git a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
index 4156cd1..b3efdd8 100644
--- a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
+++ b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
@@ -162,8 +162,8 @@
 					item.add(administrator);
 					break;
 				case OWNER:
-					Label owner = new Label("pType", getString("gb.owner"));
-					WicketUtils.setHtmlTooltip(owner, getString("gb.ownerPermission"));
+					Label owner = new Label("pType", getString("gb.repoAdministrators"));
+					WicketUtils.setHtmlTooltip(owner, getString("gb.repoAdministratorPermission"));
 					WicketUtils.setCssClass(owner, "label label-info");
 					item.add(owner);
 					break;
diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.html b/src/com/gitblit/wicket/panels/RepositoriesPanel.html
index 42f9f1f..98ff430 100644
--- a/src/com/gitblit/wicket/panels/RepositoriesPanel.html
+++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.html
@@ -57,7 +57,7 @@
 				<wicket:message key="gb.repository">Repository</wicket:message>
 			</th>
 			<th class="hidden-phone" wicket:id="orderByDescription"><wicket:message key="gb.description">Description</wicket:message></th>
-			<th class="hidden-tablet hidden-phone" wicket:id="orderByOwner"><wicket:message key="gb.owner">Owner</wicket:message></th>
+			<th class="hidden-tablet hidden-phone" wicket:id="orderByOwner"><wicket:message key="gb.repoAdministrators">Owner</wicket:message></th>
 			<th class="hidden-phone"></th>
 			<th wicket:id="orderByDate"><wicket:message key="gb.lastChange">Last Change</wicket:message></th>
 			<th class="hidden-phone"></th>
@@ -72,7 +72,7 @@
 				<wicket:message key="gb.repository">Repository</wicket:message>
 			</th>
 			<th class="hidden-phone" ><span><wicket:message key="gb.description">Description</wicket:message></span></th>
-			<th class="hidden-tablet hidden-phone"><span><wicket:message key="gb.owner">Owner</wicket:message></span></th>
+			<th class="hidden-tablet hidden-phone"><span><wicket:message key="gb.repoAdministrators">Owner</wicket:message></span></th>
 			<th class="hidden-phone"></th>
 			<th><wicket:message key="gb.lastChange">Last Change</wicket:message></th>
 			<th class="hidden-phone"></th>
@@ -88,7 +88,7 @@
 	<wicket:fragment wicket:id="repositoryRow">
         <td class="left" style="padding-left:3px;" ><b><span class="repositorySwatch" wicket:id="repositorySwatch"></span></b> <span style="padding-left:3px;" wicket:id="repositoryName">[repository name]</span></td>
         <td class="hidden-phone"><span class="list" wicket:id="repositoryDescription">[repository description]</span></td>
-        <td class="hidden-tablet hidden-phone author"><span wicket:id="repositoryOwner">[repository owner]</span></td>
+        <td class="hidden-tablet hidden-phone author"><span wicket:id="repositoryAdministrators">[repository owner]</span></td>
         <td class="hidden-phone" style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="forkIcon" /><img class="inlineIcon" wicket:id="ticketsIcon" /><img class="inlineIcon" wicket:id="docsIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="federatedIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
         <td><span wicket:id="repositoryLastChange">[last change]</span></td>
         <td class="hidden-phone" style="text-align: right;padding-right:15px;"><span style="font-size:0.8em;" wicket:id="repositorySize">[repository size]</span></td>
diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/com/gitblit/wicket/panels/RepositoriesPanel.java
index 976c517..c6c57a7 100644
--- a/src/com/gitblit/wicket/panels/RepositoriesPanel.java
+++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.java
@@ -49,6 +49,7 @@
 import com.gitblit.models.ProjectModel;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
+import com.gitblit.utils.MultiConfigUtil;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.WicketUtils;
@@ -64,6 +65,8 @@
 
 	private static final long serialVersionUID = 1L;
 
+	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();
+	
 	public RepositoriesPanel(String wicketId, final boolean showAdmin, final boolean showManagement,
 			List<RepositoryModel> models, boolean enableLinks,
 			final Map<AccessRestrictionType, String> accessRestrictionTranslations) {
@@ -287,14 +290,7 @@
 					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
 				}
 
-				String owner = entry.owner;
-				if (!StringUtils.isEmpty(owner)) {
-					UserModel ownerModel = GitBlit.self().getUserModel(owner);
-					if (ownerModel != null) {
-						owner = ownerModel.getDisplayName();
-					}
-				}
-				row.add(new Label("repositoryOwner", owner));
+				row.add(new Label("repositoryAdministrators", multiConfigUtil.convertCollectionToSingleLineString(entry.getRepoAdministrators())));
 
 				String lastChange;
 				if (entry.lastChange.getTime() == 0) {
@@ -306,42 +302,29 @@
 				row.add(lastChangeLabel);
 				WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange));
 
-				boolean showOwner = user != null && entry.isOwner(user.username);
-				boolean myPersonalRepository = showOwner && entry.isUsersPersonalRepository(user.username);
+				boolean isRepoAdministrator = user != null && entry.isRepoAdministrator(user.username);
+				boolean myPersonalRepository = isRepoAdministrator && entry.isUsersPersonalRepository(user.username);
 				if (showAdmin || myPersonalRepository) {
 					Fragment repositoryLinks = new Fragment("repositoryLinks",
 							"repositoryAdminLinks", this);
 					repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository",
 							EditRepositoryPage.class, WicketUtils
 									.newRepositoryParameter(entry.name)));
-					Link<Void> deleteLink = new Link<Void>("deleteRepository") {
-
-						private static final long serialVersionUID = 1L;
-
-						@Override
-						public void onClick() {
-							if (GitBlit.self().deleteRepositoryModel(entry)) {
-								if (dp instanceof SortableRepositoriesProvider) {
-									info(MessageFormat.format(getString("gb.repositoryDeleted"), entry));
-									((SortableRepositoriesProvider) dp).remove(entry);
-								} else {
-									setResponsePage(getPage().getClass(), getPage().getPageParameters());
-								}
-							} else {
-								error(MessageFormat.format(getString("gb.repositoryDeleteFailed"), entry));
-							}
-						}
-					};
+					Link<Void> deleteLink = new DeleteLink(entry, dp);
 					deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(
 							getString("gb.deleteRepository"), entry)));
 					repositoryLinks.add(deleteLink);
 					row.add(repositoryLinks);
-				} else if (showOwner) {
+				} else if (isRepoAdministrator) {
 					Fragment repositoryLinks = new Fragment("repositoryLinks",
-							"repositoryOwnerLinks", this);
+							"repositoryAdminLinks", this);
 					repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository",
 							EditRepositoryPage.class, WicketUtils
 									.newRepositoryParameter(entry.name)));
+					Link<Void> deleteLink = new DeleteLink(entry, dp);
+					deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(
+							getString("gb.deleteRepository"), entry)));
+					repositoryLinks.add(deleteLink);
 					row.add(repositoryLinks);
 				} else {
 					row.add(new Label("repositoryLinks"));
@@ -370,6 +353,35 @@
 		}
 	}
 
+	private class DeleteLink extends Link<Void> {
+			private RepositoryModel entry;
+
+			private IDataProvider<RepositoryModel> dp;
+			
+			private static final long serialVersionUID = 1L;
+
+			public DeleteLink(RepositoryModel entry, IDataProvider<RepositoryModel> dp) {
+				super("deleteRepository");
+				this.entry=entry;
+				this.dp=dp;
+			}
+			
+			@Override
+			public void onClick() {
+				if (GitBlit.self().deleteRepositoryModel(entry)) {
+					if (dp instanceof SortableRepositoriesProvider) {
+						info(MessageFormat.format(getString("gb.repositoryDeleted"), entry));
+						((SortableRepositoriesProvider) dp).remove(entry);
+					} else {
+						setResponsePage(getPage().getClass(), getPage().getPageParameters());
+					}
+				} else {
+					error(MessageFormat.format(getString("gb.repositoryDeleteFailed"), entry));
+				}
+			}
+		
+	}
+	
 	private static class GroupRepositoryModel extends RepositoryModel {
 
 		private static final long serialVersionUID = 1L;
@@ -463,6 +475,8 @@
 
 		private List<RepositoryModel> list;
 
+		private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();
+		
 		protected SortableRepositoriesProvider(List<RepositoryModel> list) {
 			this.list = list;
 			setSort(SortBy.date.name(), false);
@@ -516,9 +530,9 @@
 					@Override
 					public int compare(RepositoryModel o1, RepositoryModel o2) {
 						if (asc) {
-							return o1.owner.compareTo(o2.owner);
+							return multiConfigUtil.convertCollectionToSingleLineString(o1.getRepoAdministrators()).compareTo(multiConfigUtil.convertCollectionToSingleLineString(o2.getRepoAdministrators()));
 						}
-						return o2.owner.compareTo(o1.owner);
+						return multiConfigUtil.convertCollectionToSingleLineString(o2.getRepoAdministrators()).compareTo(multiConfigUtil.convertCollectionToSingleLineString(o1.getRepoAdministrators()));
 					}
 				});
 			} else if (prop.equals(SortBy.description.name())) {
diff --git a/tests/com/gitblit/tests/FederationTests.java b/tests/com/gitblit/tests/FederationTests.java
index c8f686a..be9960a 100644
--- a/tests/com/gitblit/tests/FederationTests.java
+++ b/tests/com/gitblit/tests/FederationTests.java
@@ -72,7 +72,7 @@
 			model.accessRestriction = AccessRestrictionType.VIEW;
 			model.description = "cloneable repository " + i;
 			model.lastChange = new Date();
-			model.owner = "adminuser";
+			model.addRepoAdministrator("adminuser");
 			model.name = "repo" + i + ".git";
 			model.size = "5 MB";
 			model.hasCommits = true;
diff --git a/tests/com/gitblit/tests/GitServletTest.java b/tests/com/gitblit/tests/GitServletTest.java
index 771c4b9..284be4c 100644
--- a/tests/com/gitblit/tests/GitServletTest.java
+++ b/tests/com/gitblit/tests/GitServletTest.java
@@ -41,6 +41,7 @@
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.MultiConfigUtil;
 import com.gitblit.utils.PushLogUtils;
 
 public class GitServletTest {
@@ -58,6 +59,8 @@
 	String password = GitBlitSuite.password;
 
 	private static final AtomicBoolean started = new AtomicBoolean(false);
+	
+	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();
 
 	@BeforeClass
 	public static void startGitblit() throws Exception {
@@ -725,7 +728,7 @@
 			
 			// confirm default personal repository permissions
 			RepositoryModel model = GitBlit.self().getRepositoryModel(MessageFormat.format("~{0}/ticgit.git", user.username));
-			assertEquals("Unexpected owner", user.username, model.owner);
+			assertEquals("Unexpected owner", user.username, multiConfigUtil.convertCollectionToSingleLineString(model.getRepoAdministrators()));
 			assertEquals("Unexpected authorization control", AuthorizationControl.NAMED, model.authorizationControl);
 			assertEquals("Unexpected access restriction", AccessRestrictionType.VIEW, model.accessRestriction);
 			
@@ -749,7 +752,7 @@
 			
 			// confirm default project repository permissions
 			RepositoryModel model = GitBlit.self().getRepositoryModel("project/ticgit.git");
-			assertEquals("Unexpected owner", user.username, model.owner);
+			assertEquals("Unexpected owner", user.username, multiConfigUtil.convertCollectionToSingleLineString(model.getRepoAdministrators()));
 			assertEquals("Unexpected authorization control", AuthorizationControl.fromName(GitBlit.getString(Keys.git.defaultAuthorizationControl, "NAMED")), model.authorizationControl);
 			assertEquals("Unexpected access restriction", AccessRestrictionType.fromName(GitBlit.getString(Keys.git.defaultAccessRestriction, "NONE")), model.accessRestriction);
 
diff --git a/tests/com/gitblit/tests/PermissionsTest.java b/tests/com/gitblit/tests/PermissionsTest.java
index b6ffa62..6eca046 100644
--- a/tests/com/gitblit/tests/PermissionsTest.java
+++ b/tests/com/gitblit/tests/PermissionsTest.java
@@ -2327,7 +2327,7 @@
 		repository.accessRestriction = AccessRestrictionType.VIEW;
 
 		UserModel user = new UserModel("test");
-		repository.owner = user.username;
+		repository.addRepoAdministrator(user.username);
 
 		assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name));
 		assertTrue("owner CAN NOT view!", user.canView(repository));
@@ -2351,7 +2351,7 @@
 		repository.accessRestriction = AccessRestrictionType.VIEW;
 
 		UserModel user = new UserModel("test");
-		repository.owner = user.username;
+		repository.addRepoAdministrator(user.username);
 
 		assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name));
 		assertTrue("user CAN NOT view!", user.canView(repository));
@@ -2375,7 +2375,7 @@
 		repository.accessRestriction = AccessRestrictionType.VIEW;
 
 		UserModel user = new UserModel("visitor");
-		repository.owner = "test";
+		repository.addRepoAdministrator("test");
 
 		assertFalse("user HAS a repository permission!", user.hasRepositoryPermission(repository.name));
 		assertFalse("user CAN view!", user.canView(repository));
diff --git a/tests/com/gitblit/tests/RpcTests.java b/tests/com/gitblit/tests/RpcTests.java
index 62d87bf..7228441 100644
--- a/tests/com/gitblit/tests/RpcTests.java
+++ b/tests/com/gitblit/tests/RpcTests.java
@@ -167,7 +167,7 @@
 		RepositoryModel model = new RepositoryModel();
 		model.name = "garbagerepo.git";
 		model.description = "created by RpcUtils";
-		model.owner = "garbage";
+		model.addRepoAdministrator("garbage");
 		model.accessRestriction = AccessRestrictionType.VIEW;
 		model.authorizationControl = AuthorizationControl.AUTHENTICATED;
 

--
Gitblit v1.9.1