Supported include keys in gitblit.properties

This allows you to build a hierarchy of properties files or for a properties file
to include default settings.
diff --git a/src/main/java/com/gitblit/FileSettings.java b/src/main/java/com/gitblit/FileSettings.java
index 21a2043..2f5f04f 100644
--- a/src/main/java/com/gitblit/FileSettings.java
+++ b/src/main/java/com/gitblit/FileSettings.java
@@ -18,10 +18,13 @@
 import java.io.File;

 import java.io.FileInputStream;

 import java.io.FileNotFoundException;

+import java.io.IOException;

+import java.util.List;

 import java.util.Map;

 import java.util.Properties;

 

 import com.gitblit.utils.FileUtils;

+import com.gitblit.utils.StringUtils;

 

 /**

  * Dynamically loads and reloads a properties file by keeping track of the last

@@ -81,6 +84,9 @@
 				is = new FileInputStream(propertiesFile);

 				props.load(is);

 

+				// ticket-110

+				props = readIncludes(props);

+

 				// load properties after we have successfully read file

 				properties.clear();

 				properties.putAll(props);

@@ -103,6 +109,55 @@
 		return properties;

 	}

 

+	/**

+	 * Recursively read "include" properties files.

+	 *

+	 * @param properties

+	 * @return

+	 * @throws IOException

+	 */

+	private Properties readIncludes(Properties properties) throws IOException {

+

+		Properties baseProperties = new Properties();

+

+		String include = (String) properties.remove("include");

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

+

+			// allow for multiples

+			List<String> names = StringUtils.getStringsFromValue(include, " ");

+			for (String name : names) {

+

+				// try co-located

+				File file = new File(propertiesFile.getParentFile(), name.trim());

+				if (!file.exists()) {

+					// try absolute path

+					file = new File(name.trim());

+				}

+

+				if (file.exists()) {

+					// load properties

+					try (FileInputStream iis = new FileInputStream(file)) {

+						baseProperties.load(iis);

+					}

+

+					// read nested includes

+					baseProperties = readIncludes(baseProperties);

+

+				}

+

+			}

+

+		}

+

+		// includes are "default" properties, they must be set first and the

+		// props which specified the "includes" must override

+		Properties merged = new Properties();

+		merged.putAll(baseProperties);

+		merged.putAll(properties);

+

+		return merged;

+	}

+

 	@Override

 	public boolean saveSettings() {

 		String content = FileUtils.readContent(propertiesFile, "\n");