Revisions to custom fields
diff --git a/docs/01_setup.mkd b/docs/01_setup.mkd
index 68c1025..c8cd09d 100644
--- a/docs/01_setup.mkd
+++ b/docs/01_setup.mkd
@@ -398,6 +398,16 @@
 

 Hook contributions and improvements are welcome.

 

+### Custom Fields

+

+*SINCE 1.0.0*

+

+Gitblit allows custom repository string fields to be defined in `gitblit.properties` or `web.xml`.  Entry textfields are automatically created for these fields in the Edit Repository page of Gitblit and the Edit Repository dialog of the Gitblit Manager.  These fields are accessible from your Groovy hook scripts as

+

+    repository.customFields.myField

+

+This feature allows you to customize the behavior of your hook scripts without hard-coding values in the hook scripts themselves.

+

 ### Pre-Receive

 

 Pre-Receive scripts execute after the pushed objects have all been written to the Git repository but before the refs have been updated to point to these new objects.

diff --git a/groovy/blockpush.groovy b/groovy/blockpush.groovy
index 186b242..d0e2c2e 100644
--- a/groovy/blockpush.groovy
+++ b/groovy/blockpush.groovy
@@ -58,6 +58,9 @@
  *	url				Base url for Gitblit		String

  *  logger			Logs messages to Gitblit 	org.slf4j.Logger

  *  clientLogger	Logs messages to Git client	com.gitblit.utils.ClientLogger

+ *

+ * Accessing Gitblit Custom Fields:

+ *   def myCustomField = repository.customFields.myCustomField

  *  

  */

 

diff --git a/groovy/jenkins.groovy b/groovy/jenkins.groovy
index 41083d2..7336058 100644
--- a/groovy/jenkins.groovy
+++ b/groovy/jenkins.groovy
@@ -53,6 +53,9 @@
  *	url				Base url for Gitblit		String

  *  logger			Logs messages to Gitblit 	org.slf4j.Logger

  *  clientLogger	Logs messages to Git client	com.gitblit.utils.ClientLogger

+ *

+ * Accessing Gitblit Custom Fields:

+ *   def myCustomField = repository.customFields.myCustomField

  *  

  */

 // Indicate we have started the script

diff --git a/groovy/localclone.groovy b/groovy/localclone.groovy
index e1d4493..11868d7 100644
--- a/groovy/localclone.groovy
+++ b/groovy/localclone.groovy
@@ -63,6 +63,9 @@
  *	url				Base url for Gitblit		String

  *  logger			Logs messages to Gitblit 	org.slf4j.Logger

  *  clientLogger	Logs messages to Git client	com.gitblit.utils.ClientLogger

+ *

+ * Accessing Gitblit Custom Fields:

+ *   def myCustomField = repository.customFields.myCustomField

  *  

  */

 

diff --git a/groovy/protect-refs.groovy b/groovy/protect-refs.groovy
index 48ac60f..f730bcb 100644
--- a/groovy/protect-refs.groovy
+++ b/groovy/protect-refs.groovy
@@ -64,6 +64,9 @@
  *	url				Base url for Gitblit		String

  *  logger			Logs messages to Gitblit 	org.slf4j.Logger

  *  clientLogger	Logs messages to Git client	com.gitblit.utils.ClientLogger

+ *

+ * Accessing Gitblit Custom Fields:

+ *   def myCustomField = repository.customFields.myCustomField

  *  

  */

 

diff --git a/groovy/sendmail.groovy b/groovy/sendmail.groovy
index 888f664..8d223ef 100644
--- a/groovy/sendmail.groovy
+++ b/groovy/sendmail.groovy
@@ -59,6 +59,9 @@
  *	url				Base url for Gitblit		String

  *  logger			Logs messages to Gitblit 	org.slf4j.Logger

  *  clientLogger	Logs messages to Git client	com.gitblit.utils.ClientLogger

+ *

+ * Accessing Gitblit Custom Fields:

+ *   def myCustomField = repository.customFields.myCustomField

  *  

  */

 

diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 57421a3..a86cfd7 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -30,6 +30,7 @@
 import java.util.Collections;

 import java.util.Date;

 import java.util.HashMap;

+import java.util.LinkedHashMap;

 import java.util.LinkedHashSet;

 import java.util.List;

 import java.util.Map;

@@ -255,6 +256,17 @@
 	}

 

 	/**

+	 * Returns a map of space-separated key-value pairs from the specified key.

+	 * 

+	 * @see IStoredSettings.getStrings(String key)

+	 * @param name

+	 * @return map of string, string

+	 */

+	public static Map<String, String> getMap(String key) {

+		return self().settings.getMap(key);

+	}

+

+	/**

 	 * Returns the list of keys whose name starts with the specified prefix. If

 	 * the prefix is null or empty, all key names are returned.

 	 * 

@@ -859,7 +871,7 @@
 					Constants.CONFIG_GITBLIT, null, "indexBranch")));

 			

 			// Custom defined properties

-			model.customFields = new HashMap<String, String>();

+			model.customFields = new LinkedHashMap<String, String>();

 			for (String aProperty : config.getNames(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS)) {

 				model.customFields.put(aProperty, config.getString(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS, aProperty));

 			}

diff --git a/src/com/gitblit/IStoredSettings.java b/src/com/gitblit/IStoredSettings.java
index 2f45f09..e060091 100644
--- a/src/com/gitblit/IStoredSettings.java
+++ b/src/com/gitblit/IStoredSettings.java
@@ -16,6 +16,7 @@
 package com.gitblit;

 

 import java.util.ArrayList;

+import java.util.LinkedHashMap;

 import java.util.List;

 import java.util.Map;

 import java.util.Properties;

@@ -203,6 +204,23 @@
 		}

 		return strings;

 	}

+	

+	/**

+	 * Returns a map of strings from the specified key.

+	 * 

+	 * @param name

+	 * @return map of string, string

+	 */

+	public Map<String, String> getMap(String name) {

+		Map<String, String> map = new LinkedHashMap<String, String>();

+		for (String string : getStrings(name)) {

+			String[] kvp = string.split("=", 2);

+			String key = kvp[0];

+			String value = kvp[1];				

+			map.put(key,  value);

+		}

+		return map;

+	}

 

 	/**

 	 * Override the specified key with the specified value.

diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index dfbdd4b..83b716a 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -203,7 +203,7 @@
 gb.postReceiveScripts = post-receive scripts

 gb.hookScripts = hook scripts

 gb.customFields = custom fields

-gb.customFieldsDescription = custom fields available to groovy hooks

+gb.customFieldsDescription = custom fields available to Groovy hooks

 gb.accessPermissions = access permissions

 gb.filters = filters

 gb.generalDescription = common settings

diff --git a/src/com/gitblit/wicket/GitBlitWebApp_ja.properties b/src/com/gitblit/wicket/GitBlitWebApp_ja.properties
index 7e50bd8..d73b37e 100755
--- a/src/com/gitblit/wicket/GitBlitWebApp_ja.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp_ja.properties
@@ -202,6 +202,8 @@
 gb.preReceiveScripts = pre-receive \u30b9\u30af\u30ea\u30d7\u30c8

 gb.postReceiveScripts = post-receive \u30b9\u30af\u30ea\u30d7\u30c8

 gb.hookScripts = \u30d5\u30c3\u30af\u30b9\u30af\u30ea\u30d7\u30c8

+gb.customFields = custom fields

+gb.customFieldsDescription = custom fields available to Groovy hooks

 gb.accessPermissions = \u30a2\u30af\u30bb\u30b9\u6a29\u9650

 gb.filters = \u30d5\u30a3\u30eb\u30bf\u30fc

 gb.generalDescription = \u4e00\u822c\u7684\u306a\u8a2d\u5b9a

diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/com/gitblit/wicket/pages/EditRepositoryPage.html
index 07c5522..a419698 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.html
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -36,11 +36,11 @@
 				<tr><td colspan="2"><h3><wicket:message key="gb.hookScripts"></wicket:message> &nbsp;<small><wicket:message key="gb.hookScriptsDescription"></wicket:message></small></h3></td></tr>	

 				<tr><th style="vertical-align: top;"><wicket:message key="gb.preReceiveScripts"></wicket:message><p></p><span wicket:id="inheritedPreReceive"></span></th><td style="padding:2px;"><span wicket:id="preReceiveScripts"></span></td></tr>

 				<tr><th style="vertical-align: top;"><wicket:message key="gb.postReceiveScripts"></wicket:message><p></p><span wicket:id="inheritedPostReceive"></span></th><td style="padding:2px;"><span wicket:id="postReceiveScripts"></span></td></tr>

-				<div wicket:id="customFiledsSection">

+				<div wicket:id="customFieldsSection">

 					<tr><td colspan="2"><h3><wicket:message key="gb.customFields"></wicket:message> &nbsp;<small><wicket:message key="gb.customFieldsDescription"></wicket:message></small></h3></td></tr>

-					<tr wicket:id="customFieldsListView"><th style="vertical-align: top;"><span wicket:id="customFieldLabel"></span></th><td class="edit"><input class="span8" type="text" wicket:id="customFieldValue" size="30" tabindex="16" /></td></tr>

+					<tr wicket:id="customFieldsListView"><th><span wicket:id="customFieldLabel"></span></th><td class="edit"><input class="span8" type="text" wicket:id="customFieldValue" /></td></tr>

 				</div>

-				<tr><td colspan='2'><div class="form-actions"><input class="btn btn-primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="17" /> &nbsp; <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="18" /></div></td></tr>

+				<tr><td colspan='2'><div class="form-actions"><input class="btn btn-primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" /> &nbsp; <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /></div></td></tr>

 			</tbody>

 		</table>

 	</form>	

diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
index f3e2c38..e057e2a 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -16,15 +16,14 @@
 package com.gitblit.wicket.pages;

 

 import java.text.MessageFormat;

-import java.util.AbstractMap;

 import java.util.ArrayList;

 import java.util.Arrays;

 import java.util.Collections;

 import java.util.HashSet;

 import java.util.Iterator;

+import java.util.LinkedHashMap;

 import java.util.List;

 import java.util.Map;

-import java.util.Map.Entry;

 import java.util.Set;

 

 import org.apache.wicket.PageParameters;

@@ -39,7 +38,6 @@
 import org.apache.wicket.markup.html.form.IChoiceRenderer;

 import org.apache.wicket.markup.html.form.TextField;

 import org.apache.wicket.markup.html.list.ListItem;

-import org.apache.wicket.markup.html.list.ListItemModel;

 import org.apache.wicket.markup.html.list.ListView;

 import org.apache.wicket.model.CompoundPropertyModel;

 import org.apache.wicket.model.IModel;

@@ -157,22 +155,24 @@
 						.self().getPostReceiveScriptsUnused(repositoryModel)),

 				new StringChoiceRenderer(), 12, true);

 		

-		// Dynamic Custom Defined Properties Properties

-		final List<Entry<String, String>> definedProperties = new ArrayList<Entry<String, String>>();

-		List<String> customFields = GitBlit.getStrings(Keys.repository.customFields);

-		for (String customFieldDef : customFields) {

-			String[] customFieldProperty = customFieldDef.split("=");

-			definedProperties.add(new AbstractMap.SimpleEntry<String, String>(customFieldProperty[0], customFieldProperty[1]));

-		}

-		

-		final ListView<Entry<String, String>> customFieldsListView = new ListView<Entry<String, String>>("customFieldsListView", definedProperties) {

+		// custom fields

+		final Map<String, String> customFieldsMap = GitBlit.getMap(Keys.groovy.customFields);

+		List<String> customKeys = new ArrayList<String>(customFieldsMap.keySet());

+		final ListView<String> customFieldsListView = new ListView<String>("customFieldsListView", customKeys) {

+			

+			private static final long serialVersionUID = 1L;

+

 			@Override

-			protected void populateItem(ListItem<Entry<String, String>> item) {

-				String value = repositoryModel.customFields.get(item.getModelObject().getKey());

+			protected void populateItem(ListItem<String> item) {

+				String key = item.getModelObject();

+				item.add(new Label("customFieldLabel", customFieldsMap.get(key)));

 				

-				item.add(new Label(item.getModelObject().getKey(), item.getModelObject().getValue()));		// Used to get the key later

-				item.add(new Label("customFieldLabel", item.getModelObject().getValue()));

-				item.add(new TextField<String>("customFieldValue", new Model<String>(value)));

+				String value = "";

+				if (repositoryModel.customFields != null && repositoryModel.customFields.containsKey(key)) {

+					value = repositoryModel.customFields.get(key);

+				}

+				TextField<String> field = new TextField<String>("customFieldValue", new Model<String>(value));

+				item.add(field);

 			}

 		};

 		customFieldsListView.setReuseItems(true);

@@ -277,11 +277,14 @@
 					}

 					repositoryModel.postReceiveScripts = postReceiveScripts;

 					

-					// Loop over each of the user defined properties

+					// custom fields

+					repositoryModel.customFields = new LinkedHashMap<String, String>();

 					for (int i = 0; i < customFieldsListView.size(); i++) {

-						ListItem<ListItemModel<String>> item = (ListItem<ListItemModel<String>>) customFieldsListView.get(i);

-						String key = item.get(0).getId();		// Item 0 is our 'fake' label

-						String value = ((TextField<String>)item.get(2)).getValue();		// Item 2 is out text box

+						ListItem<String> child = (ListItem<String>) customFieldsListView.get(i);

+						String key = child.getModelObject();

+

+						TextField<String> field = (TextField<String>) child.get("customFieldValue");

+						String value = field.getValue();

 						

 						repositoryModel.customFields.put(key, value);

 					}

@@ -371,13 +374,9 @@
 		form.add(new BulletListPanel("inheritedPostReceive", "inherited", GitBlit.self()

 				.getPostReceiveScriptsInherited(repositoryModel)));

 		

-		WebMarkupContainer customFiledsSection = new WebMarkupContainer("customFiledsSection") {

-			public boolean isVisible() {

-				return GitBlit.getString(Keys.repository.customFields, "").isEmpty() == false;

-			};

-		};

-		customFiledsSection.add(customFieldsListView);

-		form.add(customFiledsSection);

+		WebMarkupContainer customFieldsSection = new WebMarkupContainer("customFieldsSection");

+		customFieldsSection.add(customFieldsListView);

+		form.add(customFieldsSection.setVisible(!GitBlit.getString(Keys.groovy.customFields, "").isEmpty()));

 

 		form.add(new Button("save"));

 		Button cancel = new Button("cancel") {