From 8c4d7b2c20d2925ad3d61d9938160bc5261ee806 Mon Sep 17 00:00:00 2001
From: ludovicp <ludovicp@localhost>
Date: Wed, 02 Jul 2008 17:38:36 +0000
Subject: [PATCH] Fix to defect 1805: Added options to LDIFdiff to ignore specific attributes or entries when comparing LDIF files. --ignoreattrs (-a) <FILE> --ignoreentries (-e) <FILE> Contribution from Gautam Gopalakrishnan, Sun Microsystems, Inc.
---
opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/ignore-attributes | 2
opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/ignore-entries | 4
opendj-sdk/opends/src/messages/messages/tools.properties | 12 +
opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-ignore-attributes.ldif | 41 ++++
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDIFDiffTestCase.java | 297 ++++++++++++++++++++++----------
opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-ignore-entries.ldif | 35 +++
opendj-sdk/opends/src/server/org/opends/server/tools/LDIFDiff.java | 128 +++++++++++++
7 files changed, 412 insertions(+), 107 deletions(-)
diff --git a/opendj-sdk/opends/src/messages/messages/tools.properties b/opendj-sdk/opends/src/messages/messages/tools.properties
index 38562b1..a3e9a83 100644
--- a/opendj-sdk/opends/src/messages/messages/tools.properties
+++ b/opendj-sdk/opends/src/messages/messages/tools.properties
@@ -2359,5 +2359,13 @@
that the provided trust store exists and that you have read access rights to it
SEVERE_ERR_CANNOT_READ_KEYSTORE_1611=Cannot access key store '%s'. Verify \
that the provided key store exists and that you have read access rights to it
-
-
+INFO_LDIFDIFF_DESCRIPTION_IGNORE_ATTRS_1612=File containing a list of attributes \
+ to ignore when computing the difference
+INFO_LDIFDIFF_DESCRIPTION_IGNORE_ENTRIES_1613=File containing a list of entries (DN) \
+ to ignore when computing the difference
+SEVERE_ERR_LDIFDIFF_CANNOT_READ_FILE_IGNORE_ENTRIES_1614=An error occurred while attempting \
+ to read the file '%s' containing the list of ignored entries: %s
+SEVERE_ERR_LDIFDIFF_CANNOT_READ_FILE_IGNORE_ATTRIBS_1615=An error occurred while attempting \
+ to read the file '%s' containing the list of ignored attributes: %s
+INFO_LDIFDIFF_CANNOT_PARSE_STRING_AS_DN_1616=The string '%s' from file '%s' could \
+ not be parsed as a dn
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFDiff.java b/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFDiff.java
index 1b2cf9e..1d9365f 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFDiff.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/tools/LDIFDiff.java
@@ -27,15 +27,18 @@
package org.opends.server.tools;
import org.opends.messages.Message;
-
-
+import java.io.BufferedReader;
+import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.ListIterator;
import java.util.TreeMap;
import org.opends.server.core.DirectoryServer;
@@ -45,6 +48,7 @@
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.ExistingFileBehavior;
import org.opends.server.types.LDIFImportConfig;
@@ -166,6 +170,8 @@
StringArgument outputLDIF;
StringArgument sourceLDIF;
StringArgument targetLDIF;
+ StringArgument ignoreAttrsFile;
+ StringArgument ignoreEntriesFile;
Message toolDescription = INFO_LDIFDIFF_TOOL_DESCRIPTION.get();
@@ -191,6 +197,18 @@
INFO_LDIFDIFF_DESCRIPTION_OUTPUT_LDIF.get());
argParser.addArgument(outputLDIF);
+ ignoreAttrsFile = new StringArgument(
+ "ignoreattrs", 'a', "ignoreAttrs", false,
+ false, true, INFO_FILE_PLACEHOLDER.get(), null, null,
+ INFO_LDIFDIFF_DESCRIPTION_IGNORE_ATTRS.get());
+ argParser.addArgument(ignoreAttrsFile);
+
+ ignoreEntriesFile = new StringArgument(
+ "ignoreentries", 'e', "ignoreEntries", false,
+ false, true, INFO_FILE_PLACEHOLDER.get(), null, null,
+ INFO_LDIFDIFF_DESCRIPTION_IGNORE_ENTRIES.get());
+ argParser.addArgument(ignoreEntriesFile);
+
overwriteExisting =
new BooleanArgument(
"overwriteexisting", 'O',
@@ -314,6 +332,82 @@
}
}
+ // Read in ignored entries and attributes if any
+ BufferedReader ignReader = null;
+ Collection<DN> ignoreEntries = new HashSet<DN>();
+ Collection<String> ignoreAttrs = new HashSet<String>();
+
+ if (ignoreAttrsFile.getValue() != null)
+ {
+ try
+ {
+ ignReader = new BufferedReader(
+ new FileReader(ignoreAttrsFile.getValue()));
+ String line = null;
+ while ((line = ignReader.readLine()) != null)
+ {
+ ignoreAttrs.add(line.toLowerCase());
+ }
+ ignReader.close();
+ }
+ catch (Exception e)
+ {
+ Message message = ERR_LDIFDIFF_CANNOT_READ_FILE_IGNORE_ATTRIBS.get(
+ ignoreAttrsFile.getValue(),
+ String.valueOf(e));
+ err.println(message);
+ return 1;
+ }
+ finally
+ {
+ try
+ {
+ ignReader.close();
+ }
+ catch (Exception e) {}
+ }
+ }
+
+ if (ignoreEntriesFile.getValue() != null)
+ {
+ try
+ {
+ ignReader = new BufferedReader(
+ new FileReader(ignoreEntriesFile.getValue()));
+ String line = null;
+ while ((line = ignReader.readLine()) != null)
+ {
+ try
+ {
+ DN dn = DN.decode(line);
+ ignoreEntries.add(dn);
+ }
+ catch (DirectoryException e)
+ {
+ Message message = INFO_LDIFDIFF_CANNOT_PARSE_STRING_AS_DN.get(
+ line, ignoreEntriesFile.getValue());
+ err.println(message);
+ }
+ }
+ ignReader.close();
+ }
+ catch (Exception e)
+ {
+ Message message = ERR_LDIFDIFF_CANNOT_READ_FILE_IGNORE_ENTRIES.get(
+ ignoreEntriesFile.getValue(),
+ String.valueOf(e));
+ err.println(message);
+ return 1;
+ }
+ finally
+ {
+ try
+ {
+ ignReader.close();
+ }
+ catch (Exception e) {}
+ }
+ }
// Open the source LDIF file and read it into a tree map.
LDIFReader reader;
@@ -342,7 +436,10 @@
break;
}
- sourceMap.put(entry.getDN(), entry);
+ if (! ignoreEntries.contains(entry.getDN()))
+ {
+ sourceMap.put(entry.getDN(), entry);
+ }
}
}
catch (Exception e)
@@ -388,7 +485,10 @@
break;
}
- targetMap.put(entry.getDN(), entry);
+ if (! ignoreEntries.contains(entry.getDN()))
+ {
+ targetMap.put(entry.getDN(), entry);
+ }
}
}
catch (Exception e)
@@ -550,7 +650,7 @@
{
// The DNs are the same, so check to see if the entries are the
// same or have been modified.
- if (writeModify(writer, sourceEntry, targetEntry,
+ if (writeModify(writer, sourceEntry, targetEntry, ignoreAttrs,
singleValueChanges.isPresent()))
{
differenceFound = true;
@@ -678,6 +778,8 @@
* written.
* @param sourceEntry The source form of the entry.
* @param targetEntry The target form of the entry.
+ * @param ignoreAttrs Attributes that are ignored while calculating
+ * the differences.
* @param singleValueChanges Indicates whether each attribute-level change
* should be written in a separate modification
* per attribute value.
@@ -689,7 +791,7 @@
* change record.
*/
private static boolean writeModify(LDIFWriter writer, Entry sourceEntry,
- Entry targetEntry,
+ Entry targetEntry, Collection ignoreAttrs,
boolean singleValueChanges)
throws IOException
{
@@ -854,6 +956,19 @@
}
}
+ // Remove ignored attributes
+ if (! ignoreAttrs.isEmpty())
+ {
+ ListIterator<Modification> modIter = modifications.listIterator();
+ while (modIter.hasNext())
+ {
+ String name = modIter.next().getAttribute().getName().toLowerCase();
+ if (ignoreAttrs.contains(name))
+ {
+ modIter.remove();
+ }
+ }
+ }
// Write the modification change record.
if (modifications.isEmpty())
@@ -902,4 +1017,3 @@
}
}
}
-
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-ignore-attributes.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-ignore-attributes.ldif
new file mode 100644
index 0000000..d9d2dcd
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-ignore-attributes.ldif
@@ -0,0 +1,41 @@
+dn: dc=example,dc=com
+changetype: modify
+add: o
+o: Example Corp.
+
+dn: ou=Applications,dc=example,dc=com
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Applications
+
+dn: ou=Groups,dc=example,dc=com
+changetype: delete
+# objectClass: top
+# objectClass: organizationalUnit
+# ou: Groups
+
+dn: cn=Test User,ou=People,dc=example,dc=com
+changetype: add
+objectClass: person
+objectClass: organizationalPerson
+objectClass: top
+objectClass: inetOrgPerson
+cn: Test User
+sn: User
+uid: test.user
+userPassword: password
+givenName: Test
+
+dn: uid=test.user,ou=People,dc=example,dc=com
+changetype: delete
+# objectClass: person
+# objectClass: organizationalPerson
+# objectClass: top
+# objectClass: inetOrgPerson
+# cn: Test User
+# sn: User
+# uid: test.user
+# userPassword: password
+# givenName: Test
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-ignore-entries.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-ignore-entries.ldif
new file mode 100644
index 0000000..c7cf5d8
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/diff-multipleentries-ignore-entries.ldif
@@ -0,0 +1,35 @@
+dn: dc=example,dc=com
+changetype: modify
+delete: objectClass
+objectClass: domain
+-
+add: objectClass
+objectClass: organization
+objectClass: dcObject
+-
+add: o
+o: Example Corp.
+-
+add: description
+description: description 1
+description: description 2
+description: description 3
+
+dn: ou=Applications,dc=example,dc=com
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Applications
+
+dn: cn=Test User,ou=People,dc=example,dc=com
+changetype: add
+objectClass: person
+objectClass: organizationalPerson
+objectClass: top
+objectClass: inetOrgPerson
+cn: Test User
+sn: User
+uid: test.user
+userPassword: password
+givenName: Test
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/ignore-attributes b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/ignore-attributes
new file mode 100644
index 0000000..4fd63cd
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/ignore-attributes
@@ -0,0 +1,2 @@
+description
+objectcLASs
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/ignore-entries b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/ignore-entries
new file mode 100644
index 0000000..abea341
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/resource/ldif-diff/ignore-entries
@@ -0,0 +1,4 @@
+OU=PEOpLE,Dc=exaMPLE,DC=COM
+uid=test.user,ou=People,dc=example,dc=com
+a b c
+ou=Groups, dc=example, dc=com
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDIFDiffTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDIFDiffTestCase.java
index caa0889..96294c1 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDIFDiffTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDIFDiffTestCase.java
@@ -77,6 +77,27 @@
}
+ /**
+ * Calculates the checksum of a file
+ */
+ private long calcChecksum(String filename) throws Exception
+ {
+ return calcChecksum(new File(filename));
+ }
+
+ private long calcChecksum(File file) throws Exception
+ {
+ long checksum = 0L;
+ BufferedReader reader = new BufferedReader(new FileReader(file));
+ String line = null;
+ while ((line =reader.readLine()) != null)
+ {
+ checksum += line.hashCode();
+ }
+ reader.close();
+ return checksum;
+ }
+
/**
* Tests the LDIFDiff tool with an argument that will simply cause it to
@@ -234,8 +255,6 @@
}
-
-
/**
* Tests the LDIFDiff tool with the provided information to ensure that the
* normal mode of operation works as expected. This is a bit tricky because
@@ -285,29 +304,7 @@
}
assertEquals(LDIFDiff.mainDiff(args, true, System.out, System.err), 0);
-
- long outputChecksum = 0L;
- BufferedReader reader = new BufferedReader(new FileReader(outputFile));
- String line = reader.readLine();
- while (line != null)
- {
- outputChecksum += line.hashCode();
- line = reader.readLine();
- }
- reader.close();
-
- long expectedChecksum = 0L;
- reader = new BufferedReader(new FileReader(normalDiffFile));
- line = reader.readLine();
- while (line != null)
- {
- expectedChecksum += line.hashCode();
- line = reader.readLine();
- }
- reader.close();
-
- assertEquals(outputChecksum, expectedChecksum);
-
+ assertEquals(calcChecksum(outputFile), calcChecksum(normalDiffFile));
outputFile.delete();
}
@@ -364,29 +361,7 @@
}
assertEquals(LDIFDiff.mainDiff(args, true, System.out, System.err), 0);
-
- long outputChecksum = 0L;
- BufferedReader reader = new BufferedReader(new FileReader(outputFile));
- String line = reader.readLine();
- while (line != null)
- {
- outputChecksum += line.hashCode();
- line = reader.readLine();
- }
- reader.close();
-
- long expectedChecksum = 0L;
- reader = new BufferedReader(new FileReader(singleValueDiffFile));
- line = reader.readLine();
- while (line != null)
- {
- expectedChecksum += line.hashCode();
- line = reader.readLine();
- }
- reader.close();
-
- assertEquals(outputChecksum, expectedChecksum);
-
+ assertEquals(calcChecksum(outputFile), calcChecksum(singleValueDiffFile));
outputFile.delete();
}
@@ -470,31 +445,7 @@
};
assertEquals(LDIFDiff.mainDiff(args, true, System.out, System.err), 0);
-
-
- // Read the contents of the new diff file and make sure it matches the
- // contents of the "no changes" diff file.
- long newDiffChecksum = 0L;
- BufferedReader reader = new BufferedReader(new FileReader(newDiffFile));
- String line = reader.readLine();
- while (line != null)
- {
- newDiffChecksum += line.hashCode();
- line = reader.readLine();
- }
- reader.close();
-
- long expectedChecksum = 0L;
- reader = new BufferedReader(new FileReader(noDiffsFile));
- line = reader.readLine();
- while (line != null)
- {
- expectedChecksum += line.hashCode();
- line = reader.readLine();
- }
- reader.close();
-
- assertEquals(newDiffChecksum, expectedChecksum);
+ assertEquals(calcChecksum(newDiffFile), calcChecksum(noDiffsFile));
diffOutputFile.delete();
newTargetFile.delete();
@@ -582,35 +533,185 @@
};
assertEquals(LDIFDiff.mainDiff(args, true, System.out, System.err), 0);
-
-
- // Read the contents of the new diff file and make sure it matches the
- // contents of the "no changes" diff file.
- long newDiffChecksum = 0L;
- BufferedReader reader = new BufferedReader(new FileReader(newDiffFile));
- String line = reader.readLine();
- while (line != null)
- {
- newDiffChecksum += line.hashCode();
- line = reader.readLine();
- }
- reader.close();
-
- long expectedChecksum = 0L;
- reader = new BufferedReader(new FileReader(noDiffsFile));
- line = reader.readLine();
- while (line != null)
- {
- expectedChecksum += line.hashCode();
- line = reader.readLine();
- }
- reader.close();
-
- assertEquals(newDiffChecksum, expectedChecksum);
+ assertEquals(calcChecksum(newDiffFile), calcChecksum(noDiffsFile));
diffOutputFile.delete();
newTargetFile.delete();
newDiffFile.delete();
}
+
+
+ /**
+ * Retrieves the names of the files that should be used when testing the
+ * ldif-diff tool. Each element of the outer array should be an array
+ * containing the following elements:
+ * <OL>
+ * <LI>The path to the source LDIF file</LI>
+ * <LI>The path to the target LDIF file</LI>
+ * <LI>The path to the file with attributes to be ignored</LI>
+ * <LI>The path to the diff file</LI>
+ * </OL>
+ */
+ @DataProvider(name = "ignoreattributesdata")
+ public Object[][] getIATestData()
+ {
+ String buildRoot = System.getProperty(TestCaseUtils.PROPERTY_BUILD_ROOT);
+ String ldifRoot = buildRoot + File.separator + "tests" + File.separator +
+ "unit-tests-testng" + File.separator + "resource" +
+ File.separator + "ldif-diff" + File.separator;
+
+ return new Object[][]
+ {
+ // Make changes to multiple entries in the target->source direction.
+ new Object[] { ldifRoot + "source-multipleentries.ldif",
+ ldifRoot + "target-multipleentries.ldif",
+ ldifRoot + "ignore-attributes",
+ ldifRoot + "diff-multipleentries-ignore-attributes.ldif" },
+
+ new Object[] { ldifRoot + "source-multipleentries.ldif",
+ ldifRoot + "target-multipleentries.ldif",
+ ldifRoot + "does-not-exist",
+ ldifRoot + "diff-multipleentries-ignore-attributes.ldif" }
+ };
+
+ }
+
+
+
+ /**
+ * Tests the LDIFDiff tool with the provided information to ensure that the
+ * normal mode of operation works as expected. This is a bit tricky because
+ * the attributes and values will be written in an indeterminite order, so we
+ * can't just use string equality. We'll have to use a crude checksum
+ * mechanism to test whether they are equal. Combined with other methods in
+ * this class, this should be good enough.
+ *
+ * @param sourceFile The path to the file containing the source
+ * data set.
+ * @param targetFile The path to the file containing the target
+ * data set.
+ * @param normalDiffFile The path to the file containing the expected
+ * diff in "normal" form (at most one record per
+ * entry), or {@code null} if the diff is
+ * supposed to fail.
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "ignoreattributesdata")
+ public void testVerifyIgnoreAttributes(String sourceFile, String targetFile,
+ String ignoreAttributesFile,
+ String normalDiffFile)
+ throws Exception
+ {
+ File outputFile = File.createTempFile("difftest", "ldif");
+ outputFile.deleteOnExit();
+
+ String[] args =
+ {
+ "-s", sourceFile,
+ "-t", targetFile,
+ "-a", ignoreAttributesFile,
+ "-o", outputFile.getAbsolutePath(),
+ "-O"
+ };
+
+ if (ignoreAttributesFile.endsWith("/does-not-exist"))
+ {
+ // We expect this to fail, so just make sure that it does.
+ assertEquals(LDIFDiff.mainDiff(args, true, System.out, System.err), 1);
+ return;
+ }
+
+ assertEquals(LDIFDiff.mainDiff(args, true, System.out, System.err), 0);
+ assertEquals(calcChecksum(outputFile), calcChecksum(normalDiffFile));
+
+ outputFile.delete();
+ }
+
+
+ /**
+ * Retrieves the names of the files that should be used when testing the
+ * ldif-diff tool. Each element of the outer array should be an array
+ * containing the following elements:
+ * <OL>
+ * <LI>The path to the source LDIF file</LI>
+ * <LI>The path to the target LDIF file</LI>
+ * <LI>The path to the file with entries to be ignored</LI>
+ * <LI>The path to the diff file</LI>
+ * </OL>
+ */
+ @DataProvider(name = "ignoreentriesdata")
+ public Object[][] getIETestData()
+ {
+ String buildRoot = System.getProperty(TestCaseUtils.PROPERTY_BUILD_ROOT);
+ String ldifRoot = buildRoot + File.separator + "tests" + File.separator +
+ "unit-tests-testng" + File.separator + "resource" +
+ File.separator + "ldif-diff" + File.separator;
+
+ return new Object[][]
+ {
+ // Make changes to multiple entries in the target->source direction.
+ new Object[] { ldifRoot + "source-multipleentries.ldif",
+ ldifRoot + "target-multipleentries.ldif",
+ ldifRoot + "ignore-entries",
+ ldifRoot + "diff-multipleentries-ignore-entries.ldif" },
+
+ new Object[] { ldifRoot + "source-multipleentries.ldif",
+ ldifRoot + "target-multipleentries.ldif",
+ ldifRoot + "does-not-exist",
+ ldifRoot + "diff-multipleentries-ignore-entries.ldif" }
+ };
+
+ }
+
+
+
+ /**
+ * Tests the LDIFDiff tool with the provided information to ensure that the
+ * normal mode of operation works as expected. This is a bit tricky because
+ * the attributes and values will be written in an indeterminite order, so we
+ * can't just use string equality. We'll have to use a crude checksum
+ * mechanism to test whether they are equal. Combined with other methods in
+ * this class, this should be good enough.
+ *
+ * @param sourceFile The path to the file containing the source
+ * data set.
+ * @param targetFile The path to the file containing the target
+ * data set.
+ * @param normalDiffFile The path to the file containing the expected
+ * diff in "normal" form (at most one record per
+ * entry), or {@code null} if the diff is
+ * supposed to fail.
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test(dataProvider = "ignoreentriesdata")
+ public void testVerifyIgnoreEntries(String sourceFile, String targetFile,
+ String ignoreEntriesFile,
+ String normalDiffFile)
+ throws Exception
+ {
+ File outputFile = File.createTempFile("difftest", "ldif");
+ outputFile.deleteOnExit();
+
+ String[] args =
+ {
+ "-s", sourceFile,
+ "-t", targetFile,
+ "-e", ignoreEntriesFile,
+ "-o", outputFile.getAbsolutePath(),
+ "-O"
+ };
+
+ if (ignoreEntriesFile.endsWith("/does-not-exist"))
+ {
+ // We expect this to fail, so just make sure that it does.
+ assertEquals(LDIFDiff.mainDiff(args, true, System.out, System.err), 1);
+ return;
+ }
+
+ assertEquals(LDIFDiff.mainDiff(args, true, System.out, System.err), 0);
+ assertEquals(calcChecksum(outputFile), calcChecksum(normalDiffFile));
+
+ outputFile.delete();
+ }
}
--
Gitblit v1.10.0