Merge "Update JGit to 1.3.0.201202151440-r.190-g65f6e06"
diff --git a/Documentation/cmd-ls-projects.txt b/Documentation/cmd-ls-projects.txt
index c1b37fa..25cd9a9 100644
--- a/Documentation/cmd-ls-projects.txt
+++ b/Documentation/cmd-ls-projects.txt
@@ -46,8 +46,11 @@
Allows listing of projects together with their respective
description.
+
-Line-feeds are escaped to allow ls-project to keep the
-"one project per line"-style.
+For text format output, all non-printable characters (ASCII value 31 or
+less) are escaped according to the conventions used in languages like C,
+Python, and Perl, employing standard sequences like `\n` and `\t`, and
+`\xNN` for all others. In shell scripts, the `printf` command can be
+used to unescape the output.
--tree::
-t::
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/StringUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/StringUtil.java
new file mode 100644
index 0000000..fe1072d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/StringUtil.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// 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.google.gerrit.server;
+
+public class StringUtil {
+ /**
+ * An array of the string representations that should be used in place
+ * of the non-printable characters in the beginning of the ASCII table
+ * when escaping a string. The index of each element in the array
+ * corresponds to its ASCII value, i.e. the string representation of
+ * ASCII 0 is found in the first element of this array.
+ */
+ static String[] NON_PRINTABLE_CHARS =
+ { "\\x00", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a",
+ "\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f",
+ "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
+ "\\x18", "\\x19", "\\x1a", "\\x1b", "\\x1c", "\\x1d", "\\x1e", "\\x1f" };
+
+ /**
+ * Escapes the input string so that all non-printable characters
+ * (0x00-0x1f) are represented as a hex escape (\x00, \x01, ...)
+ * or as a C-style escape sequence (\a, \b, \t, \n, \v, \f, or \r).
+ * Backslashes in the input string are doubled (\\).
+ */
+ public static String escapeString(final String str) {
+ // Allocate a buffer big enough to cover the case with a string needed
+ // very excessive escaping without having to reallocate the buffer.
+ final StringBuilder result = new StringBuilder(3 * str.length());
+
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (c < NON_PRINTABLE_CHARS.length) {
+ result.append(NON_PRINTABLE_CHARS[c]);
+ } else if (c == '\\') {
+ result.append("\\\\");
+ } else {
+ result.append(c);
+ }
+ }
+ return result.toString();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ioutil/ColumnFormatter.java b/gerrit-server/src/main/java/com/google/gerrit/server/ioutil/ColumnFormatter.java
new file mode 100644
index 0000000..a73f1cb
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ioutil/ColumnFormatter.java
@@ -0,0 +1,82 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// 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.google.gerrit.server.ioutil;
+
+import com.google.gerrit.server.StringUtil;
+
+import java.io.PrintWriter;
+
+/**
+ * Simple output formatter for column-oriented data, writing its output to
+ * a {@link java.io.PrintWriter} object. Handles escaping of the column
+ * data so that the resulting output is unambiguous and reasonably safe and
+ * machine parsable.
+ */
+public class ColumnFormatter {
+ private char columnSeparator;
+ private boolean firstColumn;
+ private final PrintWriter out;
+
+ /**
+ * @param out The writer to which output should be sent.
+ * @param columnSeparator A character that should serve as the separator
+ * token between columns of output. As only non-printable characters
+ * in the column text are ever escaped, the column separator must be
+ * a non-printable character if the output needs to be unambiguously
+ * parsed.
+ */
+ public ColumnFormatter(final PrintWriter out, final char columnSeparator) {
+ this.out = out;
+ this.columnSeparator = columnSeparator;
+ this.firstColumn = true;
+ }
+
+ /**
+ * Adds a text string as a new column in the current line of output,
+ * taking care of escaping as necessary.
+ *
+ * @param content the string to add.
+ */
+ public void addColumn(final String content) {
+ if (!firstColumn) {
+ out.print(columnSeparator);
+ }
+ out.print(StringUtil.escapeString(content));
+ firstColumn = false;
+ }
+
+ /**
+ * Finishes the output by flushing the current line and takes care of any
+ * other cleanup action.
+ */
+ public void finish() {
+ nextLine();
+ out.flush();
+ }
+
+ /**
+ * Flushes the current line of output and makes the formatter ready to
+ * start receiving new column data for a new line (or end-of-file).
+ * If the current line is empty nothing is done, i.e. consecutive calls
+ * to this method without intervening calls to {@link #addColumn} will
+ * be squashed.
+ */
+ public void nextLine() {
+ if (!firstColumn) {
+ out.print('\n');
+ firstColumn = true;
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
index 716a5a8..8b4e000 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
@@ -19,6 +19,7 @@
import com.google.gerrit.reviewdb.client.Project.NameKey;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.OutputFormat;
+import com.google.gerrit.server.StringUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.util.TreeFormatter;
import com.google.gson.reflect.TypeToken;
@@ -269,7 +270,7 @@
if (info.description != null) {
// We still want to list every project as one-liners, hence escaping \n.
- stdout.print(" - " + info.description.replace("\n", "\\n"));
+ stdout.print(" - " + StringUtil.escapeString(info.description));
}
stdout.print('\n');
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java
new file mode 100644
index 0000000..24f3386
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// 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.google.gerrit.server;
+
+import junit.framework.TestCase;
+
+public class StringUtilTest extends TestCase {
+ /**
+ * Test the boundary condition that the first character of a string
+ * should be escaped.
+ */
+ public void testEscapeFirstChar() {
+ assertEquals(StringUtil.escapeString("\tLeading tab"), "\\tLeading tab");
+ }
+
+ /**
+ * Test the boundary condition that the last character of a string
+ * should be escaped.
+ */
+ public void testEscapeLastChar() {
+ assertEquals(StringUtil.escapeString("Trailing tab\t"), "Trailing tab\\t");
+ }
+
+ /**
+ * Test that various forms of input strings are escaped (or left as-is)
+ * in the expected way.
+ */
+ public void testEscapeString() {
+ final String[] testPairs =
+ { "", "",
+ "plain string", "plain string",
+ "string with \"quotes\"", "string with \"quotes\"",
+ "string with 'quotes'", "string with 'quotes'",
+ "string with 'quotes'", "string with 'quotes'",
+ "C:\\Program Files\\MyProgram", "C:\\\\Program Files\\\\MyProgram",
+ "string\nwith\nnewlines", "string\\nwith\\nnewlines",
+ "string\twith\ttabs", "string\\twith\\ttabs" };
+ for (int i = 0; i < testPairs.length; i += 2) {
+ assertEquals(StringUtil.escapeString(testPairs[i]), testPairs[i + 1]);
+ }
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/ColumnFormatterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/ColumnFormatterTest.java
new file mode 100644
index 0000000..2d432e6
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/ColumnFormatterTest.java
@@ -0,0 +1,138 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// 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.google.gerrit.server.ioutil;
+
+import com.google.gerrit.server.ioutil.ColumnFormatter;
+
+import junit.framework.TestCase;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+public class ColumnFormatterTest extends TestCase {
+ /**
+ * Holds an in-memory {@link java.io.PrintWriter} object and allows
+ * comparisons of its contents to a supplied string via an assert statement.
+ */
+ class PrintWriterComparator {
+ private PrintWriter printWriter;
+ private StringWriter stringWriter;
+
+ public PrintWriterComparator() {
+ stringWriter = new StringWriter();
+ printWriter = new PrintWriter(stringWriter);
+ }
+
+ public void assertEquals(String str) {
+ printWriter.flush();
+ TestCase.assertEquals(stringWriter.toString(), str);
+ }
+
+ public PrintWriter getPrintWriter() {
+ return printWriter;
+ }
+ }
+
+ /**
+ * Test that only lines with at least one column of text emit output.
+ */
+ public void testEmptyLine() {
+ final PrintWriterComparator comparator = new PrintWriterComparator();
+ final ColumnFormatter formatter =
+ new ColumnFormatter(comparator.getPrintWriter(), '\t');
+ formatter.addColumn("foo");
+ formatter.addColumn("bar");
+ formatter.nextLine();
+ formatter.nextLine();
+ formatter.nextLine();
+ formatter.addColumn("foo");
+ formatter.addColumn("bar");
+ formatter.finish();
+ comparator.assertEquals("foo\tbar\nfoo\tbar\n");
+ }
+
+ /**
+ * Test that there is no output if no columns are ever added.
+ */
+ public void testEmptyOutput() {
+ final PrintWriterComparator comparator = new PrintWriterComparator();
+ final ColumnFormatter formatter =
+ new ColumnFormatter(comparator.getPrintWriter(), '\t');
+ formatter.nextLine();
+ formatter.nextLine();
+ formatter.finish();
+ comparator.assertEquals("");
+ }
+
+ /**
+ * Test that there is no output (nor any exceptions) if we finalize
+ * the output immediately after the creation of the {@link ColumnFormatter}.
+ */
+ public void testNoNextLine() {
+ final PrintWriterComparator comparator = new PrintWriterComparator();
+ final ColumnFormatter formatter =
+ new ColumnFormatter(comparator.getPrintWriter(), '\t');
+ formatter.finish();
+ comparator.assertEquals("");
+ }
+
+ /**
+ * Test that the text in added columns is escaped while the column separator
+ * (which of course shouldn't be escaped) is left alone.
+ */
+ public void testEscapingTakesPlace() {
+ final PrintWriterComparator comparator = new PrintWriterComparator();
+ final ColumnFormatter formatter =
+ new ColumnFormatter(comparator.getPrintWriter(), '\t');
+ formatter.addColumn("foo");
+ formatter.addColumn(
+ "\tan indented multi-line\ntext");
+ formatter.nextLine();
+ formatter.finish();
+ comparator.assertEquals("foo\t\\tan indented multi-line\\ntext\n");
+ }
+
+ /**
+ * Test that we get the correct output with multi-line input where the number
+ * of columns in each line varies.
+ */
+ public void testMultiLineDifferentColumnCount() {
+ final PrintWriterComparator comparator = new PrintWriterComparator();
+ final ColumnFormatter formatter =
+ new ColumnFormatter(comparator.getPrintWriter(), '\t');
+ formatter.addColumn("foo");
+ formatter.addColumn("bar");
+ formatter.addColumn("baz");
+ formatter.nextLine();
+ formatter.addColumn("foo");
+ formatter.addColumn("bar");
+ formatter.nextLine();
+ formatter.finish();
+ comparator.assertEquals("foo\tbar\tbaz\nfoo\tbar\n");
+ }
+
+ /**
+ * Test that we get the correct output with a single column of input.
+ */
+ public void testOneColumn() {
+ final PrintWriterComparator comparator = new PrintWriterComparator();
+ final ColumnFormatter formatter =
+ new ColumnFormatter(comparator.getPrintWriter(), '\t');
+ formatter.addColumn("foo");
+ formatter.nextLine();
+ formatter.finish();
+ comparator.assertEquals("foo\n");
+ }
+}