Revised multiple owners contribution
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index a607bd8..e4d81ce 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -109,7 +109,6 @@
 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;

@@ -181,8 +180,6 @@
 	private TimeZone timezone;

 	

 	private FileBasedConfig projectConfigs;

-	

-	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();

 

 	public GitBlit() {

 		if (gitblit == null) {

@@ -825,7 +822,7 @@
 		// TODO reconsider ownership as a user property

 		// manually specify personal repository ownerships

 		for (RepositoryModel rm : repositoryListCache.values()) {

-			if (rm.isUsersPersonalRepository(user.username) || rm.isRepoAdministrator(user.username)) {

+			if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(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

@@ -939,14 +936,14 @@
 			for (RepositoryModel model : getRepositoryModels(user)) {

 				if (model.isUsersPersonalRepository(username)) {

 					// personal repository

-					model.addRepoAdministrator(user.username);

+					model.addOwner(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.isRepoAdministrator(username)) {

+				} else if (model.isOwner(username)) {

 					// common/shared repo

-					model.addRepoAdministrator(user.username);

+					model.addOwner(user.username);

 					updateRepositoryModel(model.name, model, false);

 				}

 			}

@@ -1665,7 +1662,7 @@
 		

 		if (config != null) {

 			model.description = getConfig(config, "description", "");

-			model.addRepoAdministrators(multiConfigUtil.convertStringToSet(getConfig(config, "owner", "")));

+			model.addOwners(ArrayUtils.fromString(getConfig(config, "owner", "")));

 			model.useTickets = getConfig(config, "useTickets", false);

 			model.useDocs = getConfig(config, "useDocs", false);

 			model.allowForks = getConfig(config, "allowForks", true);

@@ -2172,7 +2169,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", multiConfigUtil.convertCollectionToSingleLineString(repository.getRepoAdministrators()));

+		config.setString(Constants.CONFIG_GITBLIT, null, "owner", ArrayUtils.toString(repository.owners));

 		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);

@@ -3082,15 +3079,9 @@
 		}

 		

 		// schedule lucene engine

-		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);

-		}

+		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.");

@@ -3258,23 +3249,20 @@
 

 		// create a Gitblit repository model for the clone

 		RepositoryModel cloneModel = repository.cloneAs(cloneName);

-		// owner has REWIND/RW+ permissions		

-		cloneModel.addRepoAdministrator(user.username);

+		// owner has REWIND/RW+ permissions

+		cloneModel.addOwner(user.username);

 		updateRepositoryModel(cloneName, cloneModel, false);

 

 		// add the owner of the source repository to the clone's access list

-		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);

-					}

+		if (!ArrayUtils.isEmpty(repository.owners)) {

+			for (String owner : repository.owners) {

+				UserModel originOwner = getUserModel(owner);

+				if (originOwner != null) {

+					originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);

+					updateUserModel(originOwner.username, originOwner, false);

 				}

 			}

-		}		

+		}

 

 		// grant origin's user list clone permission to fork

 		List<String> users = getRepositoryUsers(repository);

diff --git a/src/com/gitblit/GitFilter.java b/src/com/gitblit/GitFilter.java
index 82b37dc..a0d395b 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.addRepoAdministrator(user.username);

+				model.addOwner(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 5c03ff8..8851de4 100644
--- a/src/com/gitblit/client/EditRepositoryDialog.java
+++ b/src/com/gitblit/client/EditRepositoryDialog.java
@@ -38,7 +38,6 @@
 

 import javax.swing.BoxLayout;

 import javax.swing.ButtonGroup;

-import javax.swing.DefaultComboBoxModel;

 import javax.swing.DefaultListCellRenderer;

 import javax.swing.ImageIcon;

 import javax.swing.JButton;

@@ -117,7 +116,7 @@
 

 	private JComboBox federationStrategy;

 

-	private JComboBox ownerField;

+	private JPalette<String> ownersPalette;

 

 	private JComboBox headRefField;

 	

@@ -126,7 +125,7 @@
 	private JTextField gcThreshold;

 	

 	private JComboBox maxActivityCommits;

-

+	

 	private RegistrantPermissionsPanel usersPalette;

 

 	private JPalette<String> setsPalette;

@@ -207,7 +206,7 @@
 		gcThreshold = new JTextField(8);

 		gcThreshold.setText(anRepository.gcThreshold);

 

-		ownerField = new JComboBox();

+		ownersPalette = new JPalette<String>(true);

 

 		useTickets = new JCheckBox(Translation.get("gb.useTicketsDescription"),

 				anRepository.useTickets);

@@ -334,10 +333,10 @@
 

 		usersPalette = new RegistrantPermissionsPanel(RegistrantType.USER);

 

-		JPanel northFieldsPanel = new JPanel(new GridLayout(0, 1, 0, 5));

-		northFieldsPanel.add(newFieldPanel(Translation.get("gb.repoAdministrators"), ownerField));

+		JPanel northFieldsPanel = new JPanel(new BorderLayout(0, 5));

+		northFieldsPanel.add(newFieldPanel(Translation.get("gb.owners"), ownersPalette), BorderLayout.NORTH);

 		northFieldsPanel.add(newFieldPanel(Translation.get("gb.accessRestriction"),

-				accessRestriction), BorderLayout.NORTH);

+				accessRestriction), BorderLayout.CENTER);

 

 		JPanel northAccessPanel = new JPanel(new BorderLayout(5, 5));

 		northAccessPanel.add(northFieldsPanel, BorderLayout.NORTH);

@@ -556,8 +555,8 @@
 

 		repository.name = rname;

 		repository.description = descriptionField.getText();

-		repository.addRepoAdministrator(ownerField.getSelectedItem() == null ? null

-				: ownerField.getSelectedItem().toString());

+		repository.owners.clear();

+		repository.owners.addAll(ownersPalette.getSelections());

 		repository.HEAD = headRefField.getSelectedItem() == null ? null

 				: headRefField.getSelectedItem().toString();

 		repository.gcPeriod = (Integer) gcPeriod.getSelectedItem();

@@ -629,11 +628,8 @@
 		this.allowNamed.setSelected(!authenticated);

 	}

 

-	public void setUsers(String owner, List<String> all, List<RegistrantAccessPermission> permissions) {

-		ownerField.setModel(new DefaultComboBoxModel(all.toArray()));

-		if (!StringUtils.isEmpty(owner)) {

-			ownerField.setSelectedItem(owner);

-		}

+	public void setUsers(List<String> owners, List<String> all, List<RegistrantAccessPermission> permissions) {

+		ownersPalette.setObjects(all, owners);

 		usersPalette.setObjects(all, permissions);

 	}

 

diff --git a/src/com/gitblit/client/EditUserDialog.java b/src/com/gitblit/client/EditUserDialog.java
index cb04b31..0400f5c 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.isRepoAdministrator(username) && !repo.isUsersPersonalRepository(username)) {

+			if (!repo.isOwner(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.isRepoAdministrator(username);

+				boolean isOwner = rm.isOwner(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 01db46e..cc7d58a 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 model.isRepoAdministrator(account);

+		return model.isOwner(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.isRepoAdministrator(user.username)) {

+			if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(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/JPalette.java b/src/com/gitblit/client/JPalette.java
index 4ead099..a0c2b25 100644
--- a/src/com/gitblit/client/JPalette.java
+++ b/src/com/gitblit/client/JPalette.java
@@ -144,7 +144,7 @@
 		table.getColumn(table.getColumnName(0)).setCellRenderer(nameRenderer);

 

 		JScrollPane jsp = new JScrollPane(table);

-		jsp.setPreferredSize(new Dimension(225, 175));

+		jsp.setPreferredSize(new Dimension(225, 160));

 		JPanel panel = new JPanel(new BorderLayout());

 		JLabel jlabel = new JLabel(label);

 		jlabel.setFont(jlabel.getFont().deriveFont(Font.BOLD));

diff --git a/src/com/gitblit/client/RegistrantPermissionsPanel.java b/src/com/gitblit/client/RegistrantPermissionsPanel.java
index 46ba689..98dbfb7 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.repoAdministrators"));

-				setToolTipText(Translation.get("gb.repoAdministratorPermission"));

+				setText(Translation.get("gb.owner"));

+				setToolTipText(Translation.get("gb.ownerPermission"));

 				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 6700b21..64bde9b 100644
--- a/src/com/gitblit/client/RepositoriesPanel.java
+++ b/src/com/gitblit/client/RepositoriesPanel.java
@@ -49,10 +49,9 @@
 import com.gitblit.Constants;

 import com.gitblit.Constants.RpcRequest;

 import com.gitblit.Keys;

-import com.gitblit.models.RegistrantAccessPermission;

 import com.gitblit.models.FeedModel;

+import com.gitblit.models.RegistrantAccessPermission;

 import com.gitblit.models.RepositoryModel;

-import com.gitblit.utils.MultiConfigUtil;

 import com.gitblit.utils.StringUtils;

 

 /**

@@ -85,8 +84,6 @@
 	private JTextField filterTextfield;

 

 	private JButton clearCache;

-	

-	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();

 

 	public RepositoriesPanel(GitblitClient gitblit) {

 		super();

@@ -456,7 +453,7 @@
 		dialog.setLocationRelativeTo(RepositoriesPanel.this);

 		List<String> usernames = gitblit.getUsernames();

 		List<RegistrantAccessPermission> members = gitblit.getUserAccessPermissions(repository);

-		dialog.setUsers(multiConfigUtil.convertCollectionToSingleLineString(repository.getRepoAdministrators()), usernames, members);

+		dialog.setUsers(new ArrayList<String>(repository.owners), 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 65f49ca..6b295a4 100644
--- a/src/com/gitblit/client/RepositoriesTableModel.java
+++ b/src/com/gitblit/client/RepositoriesTableModel.java
@@ -23,6 +23,7 @@
 import javax.swing.table.AbstractTableModel;

 

 import com.gitblit.models.RepositoryModel;

+import com.gitblit.utils.ArrayUtils;

 

 /**

  * Table model of a list of repositories.

@@ -73,7 +74,7 @@
 		case Description:

 			return Translation.get("gb.description");

 		case Owner:

-			return Translation.get("gb.repoAdministrators");

+			return Translation.get("gb.owner");

 		case Last_Change:

 			return Translation.get("gb.lastChange");

 		case Size:

@@ -111,7 +112,7 @@
 		case Description:

 			return model.description;

 		case Owner:

-			return model.getRepoAdministrators();

+			return ArrayUtils.toString(model.owners);

 		case Indicators:

 			return model;

 		case Last_Change:

diff --git a/src/com/gitblit/models/RepositoryModel.java b/src/com/gitblit/models/RepositoryModel.java
index a27e9fd..320f16b 100644
--- a/src/com/gitblit/models/RepositoryModel.java
+++ b/src/com/gitblit/models/RepositoryModel.java
@@ -17,20 +17,17 @@
 

 import java.io.Serializable;

 import java.util.ArrayList;

+import java.util.Collection;

 import java.util.Date;

 import java.util.List;

 import java.util.Map;

 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;

 

 /**

@@ -40,15 +37,14 @@
  * @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;

-	private Set<String> repoAdministrators = new TreeSet<String>(new StringComparator());

+	public List<String> owners;

 	public Date lastChange;

 	public boolean hasCommits;

 	public boolean showRemoteBranches;

@@ -84,29 +80,28 @@
 	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.addRepoAdministrator(owner);

+		this.description = description;

 		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);

+		this.owners = new ArrayList<String>();

+		

+		addOwner(owner);

 	}

-

+	

 	public List<String> getLocalBranches() {

 		if (ArrayUtils.isEmpty(availableRefs)) {

 			return new ArrayList<String>();

@@ -119,30 +114,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) {

@@ -163,25 +158,30 @@
 	public int compareTo(RepositoryModel o) {

 		return StringUtils.compareRepositoryNames(name, o.name);

 	}

-

+	

 	public boolean isFork() {

 		return !StringUtils.isEmpty(originRepository);

 	}

-

+	

+	public boolean isOwner(String username) {

+		if (StringUtils.isEmpty(username) || ArrayUtils.isEmpty(owners)) {

+			return false;

+		}

+		return owners.contains(username.toLowerCase());

+	}

+	

 	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;

@@ -202,46 +202,36 @@
 		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 addOwner(String username) {

+		if (!StringUtils.isEmpty(username)) {

+			String name = username.toLowerCase();

+			// a set would be more efficient, but this complicates JSON

+			// deserialization so we enforce uniqueness with an arraylist

+			if (!owners.contains(name)) {

+				owners.add(name);

 			}

 		}

 	}

 

-	public void removeRepoAdministrators(Set<String> repoAdministrators) {

-		if (repoAdministrators != null) {

-			for (String admin : repoAdministrators) {

-				this.removeRepoAdministrator(admin);

+	public void removeOwner(String username) {

+		if (!StringUtils.isEmpty(username)) {

+			owners.remove(username.toLowerCase());

+		}

+	}

+

+	public void addOwners(Collection<String> usernames) {

+		if (!ArrayUtils.isEmpty(usernames)) {

+			for (String username : usernames) {

+				addOwner(username);

 			}

 		}

 	}

 

-	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;

+	public void removeOwners(Collection<String> usernames) {

+		if (!ArrayUtils.isEmpty(owners)) {

+			for (String username : usernames) {

+				removeOwner(username);

+			}

 		}

-		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 c513feb..bec011d 100644
--- a/src/com/gitblit/models/UserModel.java
+++ b/src/com/gitblit/models/UserModel.java
@@ -108,7 +108,7 @@
 	@Deprecated

 	@Unused

 	public boolean canAccessRepository(RepositoryModel repository) {

-		boolean isOwner = repository.isRepoAdministrator(username);

+		boolean isOwner = repository.isOwner(username);

 		boolean allowAuthenticated = isAuthenticated && AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl);

 		return canAdmin() || isOwner || repositories.contains(repository.name.toLowerCase())

 				|| hasTeamAccess(repository.name) || allowAuthenticated;

@@ -303,7 +303,7 @@
 		}

 		

 		// repository owner - either specified owner or personal repository

-		if (repository.isRepoAdministrator(username) || repository.isUsersPersonalRepository(username)) {

+		if (repository.isOwner(username) || repository.isUsersPersonalRepository(username)) {

 			ap.permissionType = PermissionType.OWNER;

 			ap.permission = AccessPermission.REWIND;

 			return ap;

@@ -411,7 +411,7 @@
 			// can not fork your own repository

 			return false;

 		}

-		if (canAdmin() || repository.isRepoAdministrator(username)) {

+		if (canAdmin() || repository.isOwner(username)) {

 			return true;

 		}

 		if (!repository.allowForks) {

@@ -428,7 +428,7 @@
 	}

 	

 	public boolean canEdit(RepositoryModel model) {

-		return canAdmin() || model.isUsersPersonalRepository(username) || model.isRepoAdministrator(username);

+		return canAdmin() || model.isUsersPersonalRepository(username) || model.isOwner(username);

 	}

 	

 	/**

diff --git a/src/com/gitblit/utils/ArrayUtils.java b/src/com/gitblit/utils/ArrayUtils.java
index 41d110a..6583467 100644
--- a/src/com/gitblit/utils/ArrayUtils.java
+++ b/src/com/gitblit/utils/ArrayUtils.java
@@ -15,7 +15,9 @@
  */

 package com.gitblit.utils;

 

+import java.util.ArrayList;

 import java.util.Collection;

+import java.util.List;

 

 

 /**

@@ -41,4 +43,32 @@
 	public static boolean isEmpty(Collection<?> collection) {

 		return collection == null || collection.size() == 0;

 	}

+	

+	public static String toString(Collection<?> collection) {

+		if (isEmpty(collection)) {

+			return "";

+		}

+		StringBuilder sb = new StringBuilder();

+		for (Object o : collection) {

+			sb.append(o.toString()).append(", ");

+		}

+		// trim trailing comma-space

+		sb.setLength(sb.length() - 2);

+		return sb.toString();

+	}

+	

+	public static Collection<String> fromString(String value) {

+		if (StringUtils.isEmpty(value)) {

+			value = "";

+		}

+		List<String> list = new ArrayList<String>();

+		String [] values = value.split(",|;");

+		for (String v : values) {

+			String string = v.trim();

+			if (!StringUtils.isEmpty(string)) {

+				list.add(string);

+			}

+		}

+		return list;

+	}

 }

diff --git a/src/com/gitblit/utils/MultiConfigUtil.java b/src/com/gitblit/utils/MultiConfigUtil.java
deleted file mode 100644
index 1ed8122..0000000
--- a/src/com/gitblit/utils/MultiConfigUtil.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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
deleted file mode 100644
index da9b347..0000000
--- a/src/com/gitblit/utils/StringComparator.java
+++ /dev/null
@@ -1,24 +0,0 @@
-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 d601e1e..e775976 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -1,5 +1,5 @@
 gb.repository = repository

-gb.repoAdministrators = repository administrators

+gb.owner = owner

 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.repoAdministratorsDescription = the repository administrators may edit repository settings

+gb.ownerDescription = the owner 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.errorOnlyAdminOrRepoAdminMayEditRepository = Only an administrator or a repository administrator may edit a repository

+gb.errorOnlyAdminOrOwnerMayEditRepository = Only an administrator or the owner 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.repoAdministratorPermission = repository administrator

+gb.ownerPermission = repository owner

 gb.administrator = admin

 gb.administratorPermission = Gitblit administrator

 gb.team = team

@@ -440,4 +440,5 @@
 gb.validity = validity

 gb.siteName = site name

 gb.siteNameDescription = short, descriptive name of your server 

-gb.excludeFromActivity = exclude from activity page
\ No newline at end of file
+gb.excludeFromActivity = exclude from activity page

+gb.owners = owners
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_es.properties b/src/com/gitblit/wicket/GitBlitWebApp_es.properties
index 478a0ab..64c9ca1 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.repoAdministrators = Administradores del repositorio

+gb.owner = Propietario

 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.repoAdministratorsDescription = Administradores del repositorio puede editar la configuraci\u00F3n del repositorio

+gb.ownerDescription = El propietario 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.repoAdministratorPermission = Administrador del repositorio

+gb.ownerPermission = Propietario 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 d4e3511..086df7b 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.repoAdministrators = \u30EA\u30DD\u30B8\u30C8\u30EA\u7BA1\u7406\u8005

+gb.owner = \u6240\u6709\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.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.ownerDescription = \u6240\u6709\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.errorOnlyAdminOrRepoAdminMayEditRepository = Only an administrator or a repository administrator may edit a repository

+gb.errorOnlyAdminOrOwnerMayEditRepository = Only an administrator or the owner 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 bff593d..18eda26 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.repoAdministrators = \uC800\uC7A5\uC18C \uAD00\uB9AC\uC790
+gb.owner = \uC18C\uC720\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.repoAdministratorsDescription = \uC800\uC7A5\uC18C \uAD00\uB9AC\uC790 \uC800\uC7A5\uC18C \uC124\uC815\uC744 \uBCC0\uACBD\uD560 \uC218 \uC788\uC74C
+gb.ownerDescription = \uC18C\uC720\uC790\uB294 \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.errorOnlyAdminOrRepoAdminMayEditRepository = \uAD00\uB9AC\uC790\uC640 \uC800\uC7A5\uC18C \uAD00\uB9AC\uC790\uB9CC \uC800\uC7A5\uC18C\uB97C \uC218\uC815\uD560 \uC218 \uC788\uC74C
+gb.errorOnlyAdminOrOwnerMayEditRepository = \uAD00\uB9AC\uC790\uC640 \uC18C\uC720\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.repoAdministratorPermission = \uC800\uC7A5\uC18C \uAD00\uB9AC\uC790
+gb.ownerPermission = \uC800\uC7A5\uC18C \uC624\uB108
 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 75a0ac6..5471ad8 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.repoAdministrators = repository beheerders

+gb.owner = eigenaar

 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.repoAdministratorsDescription = repository beheerders mag repository instellingen wijzigen

+gb.ownerDescription = de eigenaar 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 een repository beheerder kan een repositorie wijzigen

+gb.errorOnlyAdminOrOwnerMayEditRepository = Alleen een beheerder of de eigenaar 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.repoAdministratorPermission = repository beheerder

+gb.ownerPermission = repositorie eigenaar

 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 4760981..82ffa63 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.repoAdministrators = Administratorzy repozytorium
+gb.owner = W\u0142a\u015Bciciel
 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.repoAdministratorsDescription = Administrator repozytorium mo\u017Ce edytowa\u0107 ustawienia repozytorium
+gb.ownerDescription = W\u0142a\u015Bciciel 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.errorOnlyAdminOrRepoAdminMayEditRepository = Tylko administrator lub administrator repozytorium mo\u017Ce edytowa\u0107 repozytorium.
+gb.errorOnlyAdminOrOwnerMayEditRepository = Tylko administrator lub w\u0142a\u015Bciciel 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 0bbd119..469d205 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.repoAdministrators = administradores do repositório

+gb.owner = proprietá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.repoAdministratorsDescription = o administradores do repositório pode editar configurações do repositório

+gb.ownerDescription = o proprietá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.errorOnlyAdminOrRepoAdminMayEditRepository = Somente umadministrador ou um administrador de repositório pode editar um repositório

+gb.errorOnlyAdminOrOwnerMayEditRepository = Somente umadministrador 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.repoAdministratorPermission = administrador do repositório

+gb.ownerPermission = proprietário 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 be9616c..7fc0de2 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.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><wicket:message key="gb.owners"></wicket:message></th><td class="edit"><span wicket:id="owners" tabindex="15" /> </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 9a81bde..d68d655 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -61,7 +61,6 @@
 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;

@@ -71,8 +70,6 @@
 

 public class EditRepositoryPage extends RootSubPage {

 

-	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();

-	

 	private final boolean isCreate;

 

 	private boolean isAdmin;

@@ -97,7 +94,7 @@
 			// personal create permissions, inject personal repository path

 			model.name = user.getPersonalPath() + "/";

 			model.projectPath = user.getPersonalPath();

-			model.addRepoAdministrator(user.username);

+			model.addOwner(user.username);

 			// personal repositories are private by default

 			model.accessRestriction = AccessRestrictionType.VIEW;

 			model.authorizationControl = AuthorizationControl.NAMED;

@@ -167,11 +164,11 @@
 		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);

+		// owners palette

+		List<String> owners = new ArrayList<String>(repositoryModel.owners);

+		List<String> persons = GitBlit.self().getAllUsernames();

+		final Palette<String> ownersPalette = new Palette<String>("owners", new ListModel<String>(owners), new CollectionModel<String>(

+		      persons), new StringChoiceRenderer(), 12, true);

 		

 		// indexed local branches palette

 		List<String> allLocalBranches = new ArrayList<String>();

@@ -335,10 +332,11 @@
 					}

 					repositoryModel.indexedBranches = indexedBranches;

 

-					repositoryModel.removeAllRepoAdministrators();

-					Iterator<String> repoAdmins = repoAdministratorsPalette.getSelectedChoices();

-					while (repoAdmins.hasNext()) {

-						repositoryModel.addRepoAdministrator(repoAdmins.next());

+					// owners

+					repositoryModel.owners.clear();

+					Iterator<String> owners = ownersPalette.getSelectedChoices();

+					while (owners.hasNext()) {

+						repositoryModel.addOwner(owners.next());

 					}

 					

 					// pre-receive scripts

@@ -392,7 +390,7 @@
 		// field names reflective match RepositoryModel fields

 		form.add(new TextField<String>("name").setEnabled(allowEditName));

 		form.add(new TextField<String>("description"));

-		form.add(repoAdministratorsPalette);

+		form.add(ownersPalette);

 		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());

@@ -573,9 +571,9 @@
 						isAdmin = true;

 						return;

 					} else {

-						if (!model.isRepoAdministrator(user.username)) {

-							// User is not an Admin nor RepoAdministrator

-							error(getString("gb.errorOnlyAdminOrRepoAdminMayEditRepository"), true);

+						if (!model.isOwner(user.username)) {

+							// User is not an Admin nor Owner

+							error(getString("gb.errorOnlyAdminOrOwnerMayEditRepository"), true);

 						}

 					}

 				}

diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.java b/src/com/gitblit/wicket/pages/RepositoryPage.java
index b67aaa6..094144b 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 isRepoAdministrator;

+	private boolean isOwner;

 	

 	public RepositoryPage(PageParameters params) {

 		super(params);

@@ -183,10 +183,10 @@
 		} else {

 			showAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, false);

 		}

-		isRepoAdministrator = GitBlitWebSession.get().isLoggedIn()

-				&& (model.isRepoAdministrator(GitBlitWebSession.get()

+		isOwner = GitBlitWebSession.get().isLoggedIn()

+				&& (model.isOwner(GitBlitWebSession.get()

 						.getUsername()));

-		if (showAdmin || isRepoAdministrator) {

+		if (showAdmin || isOwner) {

 			pages.put("edit", new PageRegistration("gb.edit", EditRepositoryPage.class, params));

 		}

 		return pages;

@@ -540,7 +540,7 @@
 	}

 	

 	public boolean isOwner() {

-		return isRepoAdministrator;

+		return isOwner;

 	}

 	

 	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 ed1bdfb..e7e12cc 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.isRepoAdministrator(user.username) || repositoryModel.isUsersPersonalRepository(user.username))) {

+						(repositoryModel.isOwner(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 788dbb5..3e85df9 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.repoAdministrators">[owner]</wicket:message></th><td><span wicket:id="repositoryAdministrators">[repository owner]</span></td></tr>

+				<tr><th><wicket:message key="gb.owners">[owner]</wicket:message></th><td><span wicket:id="repositoryOwners"><span wicket:id="owner"></span><span wicket:id="comma"></span></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>

@@ -44,7 +44,11 @@
 		<div style="border:1px solid #ddd;border-radius: 0 0 3px 3px;padding: 20px;">

 			<div wicket:id="readmeContent" class="markdown"></div>

 		</div>

-	</wicket:fragment>	

+	</wicket:fragment>

+	

+	<wicket:fragment wicket:id="ownersFragment">

+		

+	</wicket:fragment>

 </wicket:extend>	

 </body>

 </html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.java b/src/com/gitblit/wicket/pages/SummaryPage.java
index 3b2c92a..bd40a1b 100644
--- a/src/com/gitblit/wicket/pages/SummaryPage.java
+++ b/src/com/gitblit/wicket/pages/SummaryPage.java
@@ -27,6 +27,9 @@
 import org.apache.wicket.markup.html.basic.Label;

 import org.apache.wicket.markup.html.link.BookmarkablePageLink;

 import org.apache.wicket.markup.html.panel.Fragment;

+import org.apache.wicket.markup.repeater.Item;

+import org.apache.wicket.markup.repeater.data.DataView;

+import org.apache.wicket.markup.repeater.data.ListDataProvider;

 import org.eclipse.jgit.lib.Repository;

 import org.eclipse.jgit.revwalk.RevCommit;

 import org.wicketstuff.googlecharts.Chart;

@@ -49,7 +52,6 @@
 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;

@@ -60,8 +62,6 @@
 

 public class SummaryPage extends RepositoryPage {

 

-	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();

-	

 	public SummaryPage(PageParameters params) {

 		super(params);

 

@@ -85,14 +85,29 @@
 

 		// repository description

 		add(new Label("repositoryDescription", getRepositoryModel().description));

-		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));			

-		}

-

+		

+		// owner links

+		final List<String> owners = new ArrayList<String>(getRepositoryModel().owners);

+		ListDataProvider<String> ownersDp = new ListDataProvider<String>(owners);

+		DataView<String> ownersView = new DataView<String>("repositoryOwners", ownersDp) {

+			private static final long serialVersionUID = 1L;

+			int counter = 0;

+			public void populateItem(final Item<String> item) {

+				UserModel ownerModel = GitBlit.self().getUserModel(item.getModelObject());

+				if (ownerModel != null) {

+					item.add(new LinkPanel("owner", null, ownerModel.getDisplayName(), UserPage.class,

+							WicketUtils.newUsernameParameter(ownerModel.username)).setRenderBodyOnly(true));

+				} else {

+					item.add(new Label("owner").setVisible(false));

+				}

+				counter++;

+				item.add(new Label("comma", ",").setVisible(counter < owners.size()));

+				item.setRenderBodyOnly(true);

+			}

+		};

+		ownersView.setRenderBodyOnly(true);

+		add(ownersView);

+		

 		add(WicketUtils.createTimestampLabel("repositoryLastChange",

 				JGitUtils.getLastChange(r), getTimeZone(), getTimeUtils()));

 		if (metricsTotal == null) {

diff --git a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
index e953235..4678153 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="repositoryAdministrators">[owner]</span>

+				<span style="color: #999;font-style:italic;font-size:0.8em;" wicket:id="repositoryOwner">[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 d4f2a3a..0bc7add 100644
--- a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
+++ b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
@@ -36,7 +36,6 @@
 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;

@@ -52,104 +51,94 @@
 

 	private static final long serialVersionUID = 1L;

 

-	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();

-

-	public ProjectRepositoryPanel(String wicketId, Localizer localizer,

-			Component parent, final boolean isAdmin,

-			final RepositoryModel entry,

+	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 (entry.getRepoAdministrators().size() < 1) {

-			add(new Label("repositoryAdministrators").setVisible(false));

+		if (ArrayUtils.isEmpty(entry.owners)) {

+			add(new Label("repositoryOwner").setVisible(false));

 		} else {

-			add(new Label("repositoryAdministrators",

-					multiConfigUtil.convertCollectionToSingleLineString(entry

-							.getRepoAdministrators())

-							+ " ("

-							+ localizer.getString("gb.repoAdministrators", parent) + ")"));

+			String owner = "";

+			for (String username : entry.owners) {

+				UserModel ownerModel = GitBlit.self().getUserModel(username);

+			

+				if (ownerModel != null) {

+					owner = ownerModel.getDisplayName();

+				}				

+			}

+			if (entry.owners.size() > 1) {

+				owner += ", ...";

+			}

+			Label ownerLabel = (new Label("repositoryOwner", owner + " (" +

+					localizer.getString("gb.owner", parent) + ")"));

+			WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners));

+			add(ownerLabel);

 		}

 

 		UserModel user = GitBlitWebSession.get().getUser();

@@ -157,19 +146,15 @@
 			user = UserModel.ANONYMOUS;

 		}

 		Fragment repositoryLinks;

-		boolean isRepoAdministrator = entry.isRepoAdministrator(user.username);

+		boolean showOwner = entry.isOwner(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 || isRepoAdministrator) {

-			repositoryLinks = new Fragment(

-					"repositoryLinks",

-					showAdmin ? "repositoryAdminLinks" : "repositoryOwnerLinks",

-					this);

-			repositoryLinks.add(new BookmarkablePageLink<Void>(

-					"editRepository", EditRepositoryPage.class, WicketUtils

-							.newRepositoryParameter(entry.name)));

+		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) {

 				Link<Void> deleteLink = new Link<Void>("deleteRepository") {

 

@@ -180,41 +165,28 @@
 						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);

 

@@ -226,20 +198,17 @@
 		}

 		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) {

@@ -248,8 +217,7 @@
 		}

 		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 b3efdd8..4156cd1 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.repoAdministrators"));
-					WicketUtils.setHtmlTooltip(owner, getString("gb.repoAdministratorPermission"));
+					Label owner = new Label("pType", getString("gb.owner"));
+					WicketUtils.setHtmlTooltip(owner, getString("gb.ownerPermission"));
 					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 98ff430..42f9f1f 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.repoAdministrators">Owner</wicket:message></th>

+			<th class="hidden-tablet hidden-phone" wicket:id="orderByOwner"><wicket:message key="gb.owner">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.repoAdministrators">Owner</wicket:message></span></th>

+			<th class="hidden-tablet hidden-phone"><span><wicket:message key="gb.owner">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="repositoryAdministrators">[repository owner]</span></td>

+        <td class="hidden-tablet hidden-phone author"><span wicket:id="repositoryOwner">[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 c6c57a7..f4fcaac 100644
--- a/src/com/gitblit/wicket/panels/RepositoriesPanel.java
+++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.java
@@ -49,7 +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.ArrayUtils;

 import com.gitblit.utils.StringUtils;

 import com.gitblit.wicket.GitBlitWebSession;

 import com.gitblit.wicket.WicketUtils;

@@ -65,8 +65,6 @@
 

 	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) {

@@ -290,7 +288,23 @@
 					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));

 				}

 

-				row.add(new Label("repositoryAdministrators", multiConfigUtil.convertCollectionToSingleLineString(entry.getRepoAdministrators())));

+				String owner = "";

+				if (!ArrayUtils.isEmpty(entry.owners)) {

+					// display first owner

+					for (String username : entry.owners) {

+						UserModel ownerModel = GitBlit.self().getUserModel(username);

+						if (ownerModel != null) {

+							owner = ownerModel.getDisplayName();

+							break;

+						}

+					}

+					if (entry.owners.size() > 1) {

+						owner += ", ...";

+					}

+				}

+				Label ownerLabel = new Label("repositoryOwner", owner);

+				WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners));

+				row.add(ownerLabel);

 

 				String lastChange;

 				if (entry.lastChange.getTime() == 0) {

@@ -302,29 +316,42 @@
 				row.add(lastChangeLabel);

 				WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange));

 

-				boolean isRepoAdministrator = user != null && entry.isRepoAdministrator(user.username);

-				boolean myPersonalRepository = isRepoAdministrator && entry.isUsersPersonalRepository(user.username);

+				boolean showOwner = user != null && entry.isOwner(user.username);

+				boolean myPersonalRepository = showOwner && 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 DeleteLink(entry, dp);

+					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));

+							}

+						}

+					};

 					deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(

 							getString("gb.deleteRepository"), entry)));

 					repositoryLinks.add(deleteLink);

 					row.add(repositoryLinks);

-				} else if (isRepoAdministrator) {

+				} else if (showOwner) {

 					Fragment repositoryLinks = new Fragment("repositoryLinks",

-							"repositoryAdminLinks", this);

+							"repositoryOwnerLinks", 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"));

@@ -353,35 +380,6 @@
 		}

 	}

 

-	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;

@@ -475,8 +473,6 @@
 

 		private List<RepositoryModel> list;

 

-		private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();

-		

 		protected SortableRepositoriesProvider(List<RepositoryModel> list) {

 			this.list = list;

 			setSort(SortBy.date.name(), false);

@@ -529,10 +525,12 @@
 				Collections.sort(list, new Comparator<RepositoryModel>() {

 					@Override

 					public int compare(RepositoryModel o1, RepositoryModel o2) {

+						String own1 = ArrayUtils.toString(o1.owners);

+						String own2 = ArrayUtils.toString(o2.owners);

 						if (asc) {

-							return multiConfigUtil.convertCollectionToSingleLineString(o1.getRepoAdministrators()).compareTo(multiConfigUtil.convertCollectionToSingleLineString(o2.getRepoAdministrators()));

+							return own1.compareTo(own2);

 						}

-						return multiConfigUtil.convertCollectionToSingleLineString(o2.getRepoAdministrators()).compareTo(multiConfigUtil.convertCollectionToSingleLineString(o1.getRepoAdministrators()));

+						return own2.compareTo(own1);

 					}

 				});

 			} else if (prop.equals(SortBy.description.name())) {

diff --git a/tests/com/gitblit/tests/FederationTests.java b/tests/com/gitblit/tests/FederationTests.java
index be9960a..ced500a 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.addRepoAdministrator("adminuser");

+			model.addOwner("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 284be4c..a05b365 100644
--- a/tests/com/gitblit/tests/GitServletTest.java
+++ b/tests/com/gitblit/tests/GitServletTest.java
@@ -40,8 +40,8 @@
 import com.gitblit.models.PushLogEntry;

 import com.gitblit.models.RepositoryModel;

 import com.gitblit.models.UserModel;

+import com.gitblit.utils.ArrayUtils;

 import com.gitblit.utils.JGitUtils;

-import com.gitblit.utils.MultiConfigUtil;

 import com.gitblit.utils.PushLogUtils;

 

 public class GitServletTest {

@@ -59,8 +59,6 @@
 	String password = GitBlitSuite.password;

 

 	private static final AtomicBoolean started = new AtomicBoolean(false);

-	

-	private MultiConfigUtil multiConfigUtil = new MultiConfigUtil();

 

 	@BeforeClass

 	public static void startGitblit() throws Exception {

@@ -728,7 +726,7 @@
 			

 			// confirm default personal repository permissions

 			RepositoryModel model = GitBlit.self().getRepositoryModel(MessageFormat.format("~{0}/ticgit.git", user.username));

-			assertEquals("Unexpected owner", user.username, multiConfigUtil.convertCollectionToSingleLineString(model.getRepoAdministrators()));

+			assertEquals("Unexpected owner", user.username, ArrayUtils.toString(model.owners));

 			assertEquals("Unexpected authorization control", AuthorizationControl.NAMED, model.authorizationControl);

 			assertEquals("Unexpected access restriction", AccessRestrictionType.VIEW, model.accessRestriction);

 			

@@ -752,7 +750,7 @@
 			

 			// confirm default project repository permissions

 			RepositoryModel model = GitBlit.self().getRepositoryModel("project/ticgit.git");

-			assertEquals("Unexpected owner", user.username, multiConfigUtil.convertCollectionToSingleLineString(model.getRepoAdministrators()));

+			assertEquals("Unexpected owner", user.username, ArrayUtils.toString(model.owners));

 			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 6eca046..5a95104 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.addRepoAdministrator(user.username);
+		repository.addOwner(user.username);
 
 		assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name));
 		assertTrue("owner CAN NOT view!", user.canView(repository));
@@ -2345,13 +2345,58 @@
 	}
 	
 	@Test
+	public void testMultipleOwners() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		UserModel user = new UserModel("test");
+		repository.addOwner(user.username);
+		UserModel user2 = new UserModel("test2");
+		repository.addOwner(user2.username);
+
+		// first owner
+		assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name));
+		assertTrue("owner CAN NOT view!", user.canView(repository));
+		assertTrue("owner CAN NOT clone!", user.canClone(repository));
+		assertTrue("owner CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("owner CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("owner CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("owner CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertTrue("owner CAN NOT fork!", user.canFork(repository));
+		
+		assertFalse("owner CAN NOT delete!", user.canDelete(repository));
+		assertTrue("owner CAN NOT edit!", user.canEdit(repository));
+		
+		// second owner
+		assertFalse("user SHOULD NOT HAVE a repository permission!", user2.hasRepositoryPermission(repository.name));
+		assertTrue("owner CAN NOT view!", user2.canView(repository));
+		assertTrue("owner CAN NOT clone!", user2.canClone(repository));
+		assertTrue("owner CAN NOT push!", user2.canPush(repository));
+		
+		assertTrue("owner CAN NOT create ref!", user2.canCreateRef(repository));
+		assertTrue("owner CAN NOT delete ref!", user2.canDeleteRef(repository));
+		assertTrue("owner CAN NOT rewind ref!", user2.canRewindRef(repository));
+
+		assertTrue("owner CAN NOT fork!", user2.canFork(repository));
+		
+		assertFalse("owner CAN NOT delete!", user2.canDelete(repository));
+		assertTrue("owner CAN NOT edit!", user2.canEdit(repository));
+		
+		assertTrue(repository.isOwner(user.username));
+		assertTrue(repository.isOwner(user2.username));	
+	}
+	
+	@Test
 	public void testOwnerPersonalRepository() throws Exception {
 		RepositoryModel repository = new RepositoryModel("~test/myrepo.git", null, null, new Date());
 		repository.authorizationControl = AuthorizationControl.NAMED;
 		repository.accessRestriction = AccessRestrictionType.VIEW;
 
 		UserModel user = new UserModel("test");
-		repository.addRepoAdministrator(user.username);
+		repository.addOwner(user.username);
 
 		assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name));
 		assertTrue("user CAN NOT view!", user.canView(repository));
@@ -2375,7 +2420,7 @@
 		repository.accessRestriction = AccessRestrictionType.VIEW;
 
 		UserModel user = new UserModel("visitor");
-		repository.addRepoAdministrator("test");
+		repository.addOwner("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 7228441..3241a8a 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.addRepoAdministrator("garbage");

+		model.addOwner("garbage");

 		model.accessRestriction = AccessRestrictionType.VIEW;

 		model.authorizationControl = AuthorizationControl.AUTHENTICATED;