From 8f73a7cc630bb61d088c7cdad30a6708870184ee Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Wed, 14 Sep 2011 09:13:51 -0400
Subject: [PATCH] Implemented Federation Sets. Documentation.

---
 docs/01_setup.mkd                                        |    1 
 src/com/gitblit/FederationServlet.java                   |   18 ++++
 src/com/gitblit/wicket/panels/FederationTokensPanel.java |   29 +++++--
 src/com/gitblit/wicket/pages/EditRepositoryPage.java     |   24 +++++
 src/com/gitblit/build/BuildSite.java                     |    4 
 distrib/gitblit.properties                               |   26 +++++-
 src/com/gitblit/wicket/panels/FederationTokensPanel.html |    9 -
 src/com/gitblit/GitBlit.java                             |   29 +++++++
 src/com/gitblit/wicket/GitBlitWebApp.properties          |    9 +-
 docs/02_federation.mkd                                   |   11 +-
 docs/04_releases.mkd                                     |    1 
 src/com/gitblit/models/RepositoryModel.java              |    4 +
 src/com/gitblit/wicket/pages/EditRepositoryPage.html     |    9 +
 docs/00_index.mkd                                        |    1 
 resources/gitblit.css                                    |    6 +
 src/com/gitblit/wicket/pages/FederationProposalPage.java |    1 
 src/com/gitblit/Constants.java                           |   10 ++
 17 files changed, 155 insertions(+), 37 deletions(-)

diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties
index 5ea43cd..2ae6468 100644
--- a/distrib/gitblit.properties
+++ b/distrib/gitblit.properties
@@ -226,22 +226,26 @@
 
 # Registered extensions for google-code-prettify
 #
+# SPACE-DELIMITED
 # SINCE 0.5.0
 web.prettyPrintExtensions = c cpp cs css htm html java js php pl prefs properties py rb sh sql xml vb
 
 # Registered extensions for markdown transformation
 #
+# SPACE-DELIMITED
 # CASE-SENSITIVE
 # SINCE 0.5.0
 web.markdownExtensions = md mkd markdown MD MKD
 
 # Image extensions
 #
+# SPACE-DELIMITED
 # SINCE 0.5.0
 web.imageExtensions = bmp jpg gif png 
 
 # Registered extensions for binary blobs
 #
+# SPACE-DELIMITED
 # SINCE 0.5.0
 web.binaryExtensions = jar pdf tar.gz zip
 
@@ -304,8 +308,9 @@
 # SINCE 0.6.0
 mail.fromAddress = 
 
-# Space-separated list of email addresses for the Gitblit administrators
+# List of email addresses for the Gitblit administrators
 #
+# SPACE-DELIMITED
 # SINCE 0.6.0
 mail.adminAddresses = 
 
@@ -338,7 +343,7 @@
 #
 # CASE-SENSITIVE
 # SINCE 0.6.0
-# RESTART REQUIRED
+# RESTART REQUIRED *(only to enable or disable federation)*
 federation.passphrase =
 
 # Control whether or not this Gitblit instance can receive federation proposals
@@ -360,6 +365,17 @@
 # SINCE 0.6.0
 federation.defaultFrequency = 60 mins
 
+# Federation Sets are named groups of repositories.  The Federation Sets are 
+# available for selection in the repository settings page.  You can assign a
+# repository to one or more sets and then distribute the token for the set.
+# This allows you to grant federation pull access to a subset of your available
+# repositories.  Tokens for federation sets only grant repository pull access.
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 0.6.0
+federation.sets = 
+
 # Federation pull registrations
 # Registrations are read once, at startup.
 #
@@ -369,10 +385,10 @@
 #   The shortest frequency allowed is every 5 minutes
 #   Decimal frequency values are cast to integers
 #   Frequency values may be specified in mins, hours, or days
-#   Values that can not be parsed default to *federation.defaultFrequency*
+#   Values that can not be parsed or are unspecified default to *federation.defaultFrequency*
 #
 # folder:
-#   if blank, the folder is *git.repositoriesFolder*
+#   if unspecified, the folder is *git.repositoriesFolder*
 #   if specified, the folder is relative to *git.repositoriesFolder*
 #
 # mergeAccounts:
@@ -384,7 +400,7 @@
 #   notified by email of pull failures
 #
 # include and exclude:
-#   space-separated list of repositories to include or exclude from pull
+#   Space-delimited list of repositories to include or exclude from pull
 #   may be * wildcard to include or exclude all
 #   may use fuzzy match (e.g. org.eclipse.*)
 
diff --git a/docs/00_index.mkd b/docs/00_index.mkd
index cd2755a..2f24298 100644
--- a/docs/00_index.mkd
+++ b/docs/00_index.mkd
@@ -30,6 +30,7 @@
 <br/>**New:** *federation.allowProposals = false*
 <br/>**New:** *federation.proposalsFolder = proposals*
 <br/>**New:** *federation.defaultFrequency = 60 mins*
+<br/>**New:** *federation.sets =*
 <br/>**New:** *mail.* settings for sending emails
 <br/>**New:** user role *#notfederated* to prevent a user account from being pulled by a federated Gitblit instance
 - added: google-gson dependency
diff --git a/docs/01_setup.mkd b/docs/01_setup.mkd
index 25c7d86..37605c9 100644
--- a/docs/01_setup.mkd
+++ b/docs/01_setup.mkd
@@ -132,6 +132,7 @@
 	    showReadme = false
 	    excludeFromFederation = false
 	    isFederated = false
+	    federationSets = 
 	    
 #### Repository Names
 Repository names must be unique and are CASE-SENSITIVE ON CASE-SENSITIVE FILESYSTEMS.  The name must be composed of letters, digits, or `/ _ - .`<br/>
diff --git a/docs/02_federation.mkd b/docs/02_federation.mkd
index 1eb1bbb..b2df22e 100644
--- a/docs/02_federation.mkd
+++ b/docs/02_federation.mkd
@@ -49,6 +49,9 @@
 
 If *federation.passphrase* has a non-empty value, the federation tokens are displayed in the log file and are visible, to administrators, in the web ui.
 
+#### Federation Sets
+
+Federation Sets (*federation.sets*) are named groups of repositories.  The Federation Sets are available for selection in the repository settings page.  You can assign a repository to one or more sets and then distribute the token for the set.  This allows you to grant federation pull access to a subset of your available repositories.  Tokens for federation sets only grant pull access for the member repositories.
 
 ### Federation Proposals (Source Gitblit Instance)
 
@@ -177,7 +180,7 @@
 </tr>
 
 <tr><th>federation.N.frequency</th>
-<td>N [mins/hours/days]</td>
+<td>x [mins/hours/days]</td>
 <td>the period to wait between pull executions</td>
 </tr>
 
@@ -198,16 +201,16 @@
 
 <tr><th>federation.N.freeze</th>
 <td>boolean</td>
-<td>freeze the repository at the end of each pull</td>
+<td>freeze the repository after the first pull, subsequent pulls respect the local *isFrozen* setting</td>
 </tr>
 
 <tr><th>federation.N.include</th>
-<td>string array<br/>(space separated)</td>
+<td>string array<br/>(space-delimited)</td>
 <td>list of included repositories *(wildcard and fuzzy matching supported)*</td>
 </tr>
 
 <tr><th>federation.N.exclude</th>
-<td>string array<br/>(space separated)</td>
+<td>string array<br/>(space-delimited)</td>
 <td>list of excluded repositories *(wildcard and fuzzy matching supported)*</td>
 </tr>
 
diff --git a/docs/04_releases.mkd b/docs/04_releases.mkd
index e02be89..6eff75f 100644
--- a/docs/04_releases.mkd
+++ b/docs/04_releases.mkd
@@ -10,6 +10,7 @@
 <br/>**New:** *federation.allowProposals = false*
 <br/>**New:** *federation.proposalsFolder = proposals*
 <br/>**New:** *federation.defaultFrequency = 60 mins*
+<br/>**New:** *federation.sets =*
 <br/>**New:** *mail.* settings for sending emails
 <br/>**New:** user role *#notfederated* to prevent a user account from being pulled by a federated Gitblit instance
 - added: google-gson dependency
diff --git a/resources/gitblit.css b/resources/gitblit.css
index 2257286..0586b61 100644
--- a/resources/gitblit.css
+++ b/resources/gitblit.css
@@ -22,6 +22,12 @@
     background: url(background.png) repeat-x scroll 0 0 #FFFFFF;
 }
 
+hr {	
+	color: #ffffff;
+	background-color: #ffffff;
+	height: 1px; !important
+}
+
 pre, code, pre.prettyprint, pre.plainprint {
 	color: black;
 	font-family: monospace;
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index a58242b..0446f2d 100644
--- a/src/com/gitblit/Constants.java
+++ b/src/com/gitblit/Constants.java
@@ -151,7 +151,7 @@
 			return name();
 		}
 	}
-	
+
 	/**
 	 * Enumeration representing the federation types.
 	 */
@@ -167,6 +167,14 @@
 			return FEDERATE_THIS;
 		}
 
+		public boolean exceeds(FederationStrategy type) {
+			return this.ordinal() > type.ordinal();
+		}
+
+		public boolean atLeast(FederationStrategy type) {
+			return this.ordinal() >= type.ordinal();
+		}
+
 		@Override
 		public String toString() {
 			return name();
diff --git a/src/com/gitblit/FederationServlet.java b/src/com/gitblit/FederationServlet.java
index 86aadb0..a84e90c 100644
--- a/src/com/gitblit/FederationServlet.java
+++ b/src/com/gitblit/FederationServlet.java
@@ -225,6 +225,12 @@
 
 		Object result = null;
 		if (FederationRequest.PULL_REPOSITORIES.equals(reqType)) {
+			// build a reverse-lookup for token->federation set name
+			Map<String, String> federationSets = new HashMap<String, String>();
+			for (String set : GitBlit.getStrings(Keys.federation.sets)) {
+				federationSets.put(GitBlit.self().getFederationToken(set), set);
+			}
+
 			// Determine the Gitblit clone url
 			StringBuilder sb = new StringBuilder();
 			sb.append(HttpUtils.getHostURL(request));
@@ -253,7 +259,17 @@
 					}
 					break;
 				}
-				repositories.put(url, model);
+
+				if (federationSets.containsKey(token)) {
+					// include repositories only for federation set
+					String set = federationSets.get(token);
+					if (model.federationSets.contains(set)) {
+						repositories.put(url, model);
+					}
+				} else {
+					// standard federation token for ALL
+					repositories.put(url, model);
+				}
 			}
 			result = repositories;
 		} else {
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index b32440b..33eaee9 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -21,6 +21,7 @@
 import java.lang.reflect.Field;
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -521,6 +522,8 @@
 			model.showReadme = getConfig(config, "showReadme", false);
 			model.federationStrategy = FederationStrategy.fromName(getConfig(config,
 					"federationStrategy", null));
+			model.federationSets = new ArrayList<String>(Arrays.asList(config.getStringList(
+					"gitblit", null, "federationSets")));
 			model.isFederated = getConfig(config, "isFederated", false);
 			model.origin = config.getString("remote", "origin", "url");
 		}
@@ -697,6 +700,7 @@
 		config.setBoolean("gitblit", null, "showRemoteBranches", repository.showRemoteBranches);
 		config.setBoolean("gitblit", null, "isFrozen", repository.isFrozen);
 		config.setBoolean("gitblit", null, "showReadme", repository.showReadme);
+		config.setStringList("gitblit", null, "federationSets", repository.federationSets);
 		config.setString("gitblit", null, "federationStrategy",
 				repository.federationStrategy.name());
 		config.setBoolean("gitblit", null, "isFederated", repository.isFederated);
@@ -810,9 +814,16 @@
 			validPassphrase = false;
 		}
 		if (validPassphrase) {
+			// standard tokens
 			for (FederationToken tokenType : FederationToken.values()) {
 				logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(),
 						getFederationToken(tokenType)));
+			}
+
+			// federation set tokens
+			for (String set : settings.getStrings(Keys.federation.sets)) {
+				logger.info(MessageFormat.format("Federation Set {0} token = {1}", set,
+						getFederationToken(set)));
 			}
 		}
 
@@ -838,6 +849,7 @@
 			keys.remove(Keys.federation.allowProposals);
 			keys.remove(Keys.federation.proposalsFolder);
 			keys.remove(Keys.federation.defaultFrequency);
+			keys.remove(Keys.federation.sets);
 			Collections.sort(keys);
 			Map<String, FederationModel> federatedModels = new HashMap<String, FederationModel>();
 			for (String key : keys) {
@@ -936,8 +948,13 @@
 	 */
 	public List<String> getFederationTokens() {
 		List<String> tokens = new ArrayList<String>();
+		// generate standard tokens
 		for (FederationToken type : FederationToken.values()) {
 			tokens.add(getFederationToken(type));
+		}
+		// generate tokens for federation sets
+		for (String set : settings.getStrings(Keys.federation.sets)) {
+			tokens.add(getFederationToken(set));
 		}
 		return tokens;
 	}
@@ -949,8 +966,18 @@
 	 * @return a federation token
 	 */
 	public String getFederationToken(FederationToken type) {
+		return getFederationToken(type.name());
+	}
+
+	/**
+	 * Returns the specified federation token for this Gitblit instance.
+	 * 
+	 * @param value
+	 * @return a federation token
+	 */
+	public String getFederationToken(String value) {
 		String passphrase = settings.getString(Keys.federation.passphrase, "");
-		return StringUtils.getSHA1(passphrase + "-" + type.name());
+		return StringUtils.getSHA1(passphrase + "-" + value);
 	}
 
 	/**
diff --git a/src/com/gitblit/build/BuildSite.java b/src/com/gitblit/build/BuildSite.java
index dca5116..de4f071 100644
--- a/src/com/gitblit/build/BuildSite.java
+++ b/src/com/gitblit/build/BuildSite.java
@@ -55,6 +55,8 @@
  */
 public class BuildSite {
 
+	private static final String SPACE_DELIMITED = "SPACE-DELIMITED";
+	
 	private static final String CASE_SENSITIVE = "CASE-SENSITIVE";
 
 	private static final String RESTART_REQUIRED = "RESTART REQUIRED";
@@ -265,7 +267,7 @@
 		for (Setting setting : settings) {
 			for (String comment : setting.comments) {
 				if (comment.contains(SINCE) || comment.contains(RESTART_REQUIRED)
-						|| comment.contains(CASE_SENSITIVE)) {
+						|| comment.contains(CASE_SENSITIVE) || comment.contains(SPACE_DELIMITED)) {
 					sb.append(MessageFormat.format(
 							"<span style=\"color:#004000;\"># <i>{0}</i></span>",
 							transformMarkdown(comment)));
diff --git a/src/com/gitblit/models/RepositoryModel.java b/src/com/gitblit/models/RepositoryModel.java
index cd54ca5..0f1a676 100644
--- a/src/com/gitblit/models/RepositoryModel.java
+++ b/src/com/gitblit/models/RepositoryModel.java
@@ -16,7 +16,9 @@
 package com.gitblit.models;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.Constants.FederationStrategy;
@@ -45,6 +47,7 @@
 	public boolean isFrozen;
 	public boolean showReadme;
 	public FederationStrategy federationStrategy;
+	public List<String> federationSets;
 	public boolean isFederated;
 	public String frequency;
 	public String origin;
@@ -60,6 +63,7 @@
 		this.owner = owner;
 		this.lastChange = lastchange;
 		this.accessRestriction = AccessRestrictionType.NONE;
+		federationSets = new ArrayList<String>();
 	}
 
 	@Override
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index ab7ef4b..72d9d36 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -108,9 +108,9 @@
 gb.excludeFromFederation = exclude from federation
 gb.excludeFromFederationDescription = block federated Gitblit instances from pulling this object
 gb.tokens = federation tokens
-gb.tokenAllDescription = federate repositories, users, & settings
-gb.tokenUnrDescription = federate repositories & users
-gb.tokenJurDescription = federate repositories
+gb.tokenAllDescription = all repositories, users, & settings
+gb.tokenUnrDescription = all repositories & users
+gb.tokenJurDescription = all repositories
 gb.federatedRepositoryDefinitions = repository definitions
 gb.federatedUserDefinitions = user definitions
 gb.federatedSettingDefinitions = setting definitions
@@ -128,9 +128,10 @@
 gb.exclusions = exclusions
 gb.registration = registration
 gb.registrations = federation registrations
-gb.sendProposal send proposal
+gb.sendProposal propose
 gb.status = status
 gb.origin = origin
 gb.federationStrategy = federation strategy
 gb.federationRegistration = federation registration
 gb.federationResults = federation pull results
+gb.federationSets = federation sets
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/com/gitblit/wicket/pages/EditRepositoryPage.html
index 2dd031b..c33184d 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.html
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -23,10 +23,13 @@
 				<tr><th><wicket:message key="gb.enableDocs"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useDocs" tabindex="6" /> &nbsp;<i><wicket:message key="gb.useDocsDescription"></wicket:message></i></td></tr>
 				<tr><th><wicket:message key="gb.showRemoteBranches"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="showRemoteBranches" tabindex="7" /> &nbsp;<i><wicket:message key="gb.showRemoteBranchesDescription"></wicket:message></i></td></tr>
 				<tr><th><wicket:message key="gb.showReadme"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="showReadme" tabindex="8" /> &nbsp;<i><wicket:message key="gb.showReadmeDescription"></wicket:message></i></td></tr>
-				<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select wicket:id="accessRestriction" tabindex="9" /></td></tr>				
-				<tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="isFrozen" tabindex="10" /> &nbsp;<i><wicket:message key="gb.isFrozenDescription"></wicket:message></i></td></tr>
+				<tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="isFrozen" tabindex="9" /> &nbsp;<i><wicket:message key="gb.isFrozenDescription"></wicket:message></i></td></tr>
+				<tr><td style="padding-top:10px;" colspan="2"><hr></hr></td></tr>
+				<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select wicket:id="accessRestriction" tabindex="10" /></td></tr>				
+				<tr><th style="vertical-align: top;"><wicket:message key="gb.permittedUsers"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>
+				<tr><td style="padding-top:10px;" colspan="2"><hr></hr></td></tr>				
 				<tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select wicket:id="federationStrategy" tabindex="11" /></td></tr>
-				<tr><th style="vertical-align: top;"><wicket:message key="gb.permittedUsers"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>				
+				<tr><th style="vertical-align: top;"><wicket:message key="gb.federationSets"></wicket:message></th><td style="padding:2px;"><span wicket:id="federationSets"></span></td></tr>				
 				<tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="12" /> <input type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="13" /></td></tr>
 			</tbody>
 		</table>
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
index 3b6e6f3..7349ca5 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -73,6 +73,7 @@
 		// ensure this user can create or edit this repository
 		checkPermissions(repositoryModel);
 
+		List<String> federationSets = new ArrayList<String>();
 		List<String> repositoryUsers = new ArrayList<String>();
 		if (isCreate) {
 			super.setupPage("", getString("gb.newRepository"));
@@ -82,12 +83,21 @@
 				repositoryUsers.addAll(GitBlit.self().getRepositoryUsers(repositoryModel));
 				Collections.sort(repositoryUsers);
 			}
+			federationSets.addAll(repositoryModel.federationSets);
 		}
 
 		final String oldName = repositoryModel.name;
+		// users palette
 		final Palette<String> usersPalette = new Palette<String>("users", new ListModel<String>(
 				repositoryUsers), new CollectionModel<String>(GitBlit.self().getAllUsernames()),
 				new ChoiceRenderer<String>("", ""), 10, false);
+
+		// federation sets palette
+		List<String> sets = GitBlit.getStrings(Keys.federation.sets);
+		final Palette<String> federationSetsPalette = new Palette<String>("federationSets",
+				new ListModel<String>(federationSets), new CollectionModel<String>(sets),
+				new ChoiceRenderer<String>("", ""), 10, false);
+
 		CompoundPropertyModel<RepositoryModel> model = new CompoundPropertyModel<RepositoryModel>(
 				repositoryModel);
 		Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", model) {
@@ -136,6 +146,15 @@
 						return;
 					}
 
+					// save federation set preferences
+					if (repositoryModel.federationStrategy.exceeds(FederationStrategy.EXCLUDE)) {
+						repositoryModel.federationSets.clear();
+						Iterator<String> sets = federationSetsPalette.getSelectedChoices();
+						while (sets.hasNext()) {
+							repositoryModel.federationSets.add(sets.next());
+						}
+					}
+
 					// save the repository
 					GitBlit.self().updateRepositoryModel(oldName, repositoryModel, isCreate);
 
@@ -171,8 +190,8 @@
 				.asList(AccessRestrictionType.values()), new AccessRestrictionRenderer()));
 		form.add(new CheckBox("isFrozen"));
 		// TODO enable origin definition
-		form.add(new TextField<String>("origin").setEnabled(false/*isCreate*/));
-		
+		form.add(new TextField<String>("origin").setEnabled(false/* isCreate */));
+
 		// federation strategies - remove ORIGIN choice if this repository has
 		// no origin.
 		List<FederationStrategy> federationStrategies = new ArrayList<FederationStrategy>(
@@ -187,6 +206,7 @@
 		form.add(new CheckBox("showRemoteBranches"));
 		form.add(new CheckBox("showReadme"));
 		form.add(usersPalette);
+		form.add(federationSetsPalette);
 
 		form.add(new Button("save"));
 		Button cancel = new Button("cancel") {
diff --git a/src/com/gitblit/wicket/pages/FederationProposalPage.java b/src/com/gitblit/wicket/pages/FederationProposalPage.java
index 4685d2f..c2a2d24 100644
--- a/src/com/gitblit/wicket/pages/FederationProposalPage.java
+++ b/src/com/gitblit/wicket/pages/FederationProposalPage.java
@@ -79,6 +79,7 @@
 		sb.append(asParam(p, proposal.name, "frequency",
 				GitBlit.getString(Keys.federation.defaultFrequency, "60 mins")));
 		sb.append(asParam(p, proposal.name, "folder", proposal.name));
+		sb.append(asParam(p, proposal.name, "freeze", "true"));
 		sb.append(asParam(p, proposal.name, "sendStatus", "true"));
 		sb.append(asParam(p, proposal.name, "notifyOnError", "true"));
 		sb.append(asParam(p, proposal.name, "exclude", ""));
diff --git a/src/com/gitblit/wicket/panels/FederationTokensPanel.html b/src/com/gitblit/wicket/panels/FederationTokensPanel.html
index 6749e40..06c1869 100644
--- a/src/com/gitblit/wicket/panels/FederationTokensPanel.html
+++ b/src/com/gitblit/wicket/panels/FederationTokensPanel.html
@@ -8,8 +8,7 @@
 <wicket:panel>
 
 		<div class="admin_nav">
-			<a wicket:id="federatedRepositories"><wicket:message key="gb.federatedRepositoryDefinitions">[repositories]</wicket:message></a>
-			 | <a wicket:id="federatedUsers"><wicket:message key="gb.federatedUserDefinitions">[users]</wicket:message></a>
+			 <a wicket:id="federatedUsers"><wicket:message key="gb.federatedUserDefinitions">[users]</wicket:message></a>
 			 | <a wicket:id="federatedSettings"><wicket:message key="gb.federatedSettingDefinitions">[settings]</wicket:message></a>			
 		</div>
 
@@ -20,15 +19,13 @@
 				<wicket:message key="gb.tokens">[tokens]</wicket:message>
 			</th>
 			<th></th>
-			<th></th>
 			<th class="right"></th>
 		</tr>
 		<tbody>		
        		<tr wicket:id="row">
-       			<td class="left"><span class="list" wicket:id="field">[field]</span></td>
+       			<td class="left"><span wicket:id="description"></span></td>
        			<td><span class="sha1"" wicket:id="value">[value]</span></td>
-       			<td><span wicket:id="description"></span></td>
-       			<td class="rightAlign"><span class="link"><a wicket:id="send"><wicket:message key="gb.sendProposal">[send proposal]</wicket:message></a></span></td>
+       			<td class="rightAlign"><span class="link"><a wicket:id="repositoryDefinitions"><wicket:message key="gb.federatedRepositoryDefinitions">[repository definitions]</wicket:message></a> | <a wicket:id="send"><wicket:message key="gb.sendProposal">[send proposal]</wicket:message></a></span></td>
        		</tr>
     	</tbody>
 	</table>
diff --git a/src/com/gitblit/wicket/panels/FederationTokensPanel.java b/src/com/gitblit/wicket/panels/FederationTokensPanel.java
index 166f1bd..a9cbdcd 100644
--- a/src/com/gitblit/wicket/panels/FederationTokensPanel.java
+++ b/src/com/gitblit/wicket/panels/FederationTokensPanel.java
@@ -16,6 +16,7 @@
 package com.gitblit.wicket.panels;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.wicket.markup.html.basic.Label;
@@ -29,6 +30,7 @@
 import com.gitblit.Constants.FederationToken;
 import com.gitblit.FederationServlet;
 import com.gitblit.GitBlit;
+import com.gitblit.Keys;
 import com.gitblit.wicket.WicketUtils;
 
 public class FederationTokensPanel extends BasePanel {
@@ -38,11 +40,7 @@
 	public FederationTokensPanel(String wicketId, final boolean showFederation) {
 		super(wicketId);
 
-		String baseUrl = getRequest().getRelativePathPrefixToContextRoot();
-		add(new ExternalLink("federatedRepositories", FederationServlet.asPullLink(baseUrl, GitBlit
-				.self().getFederationToken(FederationToken.REPOSITORIES),
-				FederationRequest.PULL_REPOSITORIES)));
-
+		final String baseUrl = getRequest().getRelativePathPrefixToContextRoot();
 		add(new ExternalLink("federatedUsers", FederationServlet.asPullLink(baseUrl, GitBlit.self()
 				.getFederationToken(FederationToken.USERS_AND_REPOSITORIES),
 				FederationRequest.PULL_USERS)));
@@ -52,7 +50,13 @@
 
 		final List<String[]> data = new ArrayList<String[]>();
 		for (FederationToken token : FederationToken.values()) {
-			data.add(new String[] { token.name(), GitBlit.self().getFederationToken(token) });
+			data.add(new String[] { token.name(), GitBlit.self().getFederationToken(token), null });
+		}
+		List<String> sets = GitBlit.getStrings(Keys.federation.sets);
+		Collections.sort(sets);
+		for (String set : sets) {
+			data.add(new String[] { FederationToken.REPOSITORIES.name(),
+					GitBlit.self().getFederationToken(set), set });
 		}
 
 		DataView<String[]> dataView = new DataView<String[]>("row", new ListDataProvider<String[]>(
@@ -69,9 +73,17 @@
 			public void populateItem(final Item<String[]> item) {
 				final String[] entry = item.getModelObject();
 				final FederationToken token = FederationToken.fromName(entry[0]);
-
-				item.add(new Label("field", entry[0]));
+				if (entry[2] == null) {
+					// standard federation token
+					item.add(new Label("description", describeToken(token)));
+				} else {
+					// federation set token
+					item.add(new Label("description", entry[2]));
+				}
 				item.add(new Label("value", entry[1]));
+
+				item.add(new ExternalLink("repositoryDefinitions", FederationServlet.asPullLink(
+						baseUrl, entry[1], FederationRequest.PULL_REPOSITORIES)));
 
 				// TODO make this work
 				Link<Void> sendProposal = new Link<Void>("send") {
@@ -87,7 +99,6 @@
 						"Please enter URL for remote Gitblit instance:"));
 				item.add(sendProposal);
 
-				item.add(new Label("description", describeToken(token)));
 				WicketUtils.setAlternatingBackground(item, counter);
 				counter++;
 			}

--
Gitblit v1.9.1