From 345e2648df3e17bd18d0f4251774d3f8b298f77c Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Mon, 28 Oct 2019 18:11:57 +0100 Subject: [PATCH] DiffFormatter: support core.quotePath = false core.quotePath = false means that "bytes higher than 0x80 are not considered "unusal" anymore"[1], i.e., they are not escaped. In essence this preserves non-ASCII characters in path names in output. Note that control characters and other special characters in the ASCII range will still be escaped. Add a new QuotedString.GIT_PATH_MINIMAL singleton implementing this. Change the normal GIT_PATH algorithm to use bytes instead of characters so it can be re-used. Provide a setter in DiffFormatter for the quoting style so that an application can override the default, which is the setting from the git config (and by default "true"). Use the new QuotedString.GIT_PATH_MINIMAL when core.quotePath == false. [1] https://git-scm.com/docs/git-config#Documentation/git-config.txt-corequotePath Bug: 552467 Change-Id: Ifcb233e7d10676333bf42011e32d01a4e1138059 Signed-off-by: Thomas Wolf --- .../util/QuotedStringGitPathStyleTest.java | 78 +++++++++++++++++++ .../org/eclipse/jgit/diff/DiffFormatter.java | 29 ++++++- .../org/eclipse/jgit/lib/ConfigConstants.java | 6 ++ .../org/eclipse/jgit/util/QuotedString.java | 50 ++++++++---- 4 files changed, 144 insertions(+), 19 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringGitPathStyleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringGitPathStyleTest.java index 9a0c96e17..c09b136ca 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringGitPathStyleTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringGitPathStyleTest.java @@ -45,6 +45,7 @@ package org.eclipse.jgit.util; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static org.eclipse.jgit.util.QuotedString.GIT_PATH; +import static org.eclipse.jgit.util.QuotedString.GIT_PATH_MINIMAL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; @@ -67,6 +68,12 @@ public class QuotedStringGitPathStyleTest { assertEquals(exp, r); } + private static void assertDequoteMinimal(String exp, String in) { + final byte[] b = ('"' + in + '"').getBytes(ISO_8859_1); + final String r = GIT_PATH_MINIMAL.dequote(b, 0, b.length); + assertEquals(exp, r); + } + @Test public void testQuote_Empty() { assertEquals("\"\"", GIT_PATH.quote("")); @@ -206,4 +213,75 @@ public class QuotedStringGitPathStyleTest { assertSame("abc@2x.png", GIT_PATH.quote("abc@2x.png")); assertDequote("abc@2x.png", "abc\\1002x.png"); } + + @Test + public void testNoQuote() { + assertSame("\u00c5ngstr\u00f6m", + GIT_PATH_MINIMAL.quote("\u00c5ngstr\u00f6m")); + } + + @Test + public void testQuoteMinimal() { + assertEquals("\"\u00c5n\\\\str\u00f6m\"", + GIT_PATH_MINIMAL.quote("\u00c5n\\str\u00f6m")); + } + + @Test + public void testDequoteMinimal() { + assertEquals("\u00c5n\\str\u00f6m", GIT_PATH_MINIMAL + .dequote(GIT_PATH_MINIMAL.quote("\u00c5n\\str\u00f6m"))); + + } + + @Test + public void testRoundtripMinimal() { + assertEquals("\u00c5ngstr\u00f6m", GIT_PATH_MINIMAL + .dequote(GIT_PATH_MINIMAL.quote("\u00c5ngstr\u00f6m"))); + + } + + @Test + public void testQuoteMinimalDequoteNormal() { + assertEquals("\u00c5n\\str\u00f6m", GIT_PATH + .dequote(GIT_PATH_MINIMAL.quote("\u00c5n\\str\u00f6m"))); + + } + + @Test + public void testQuoteNormalDequoteMinimal() { + assertEquals("\u00c5n\\str\u00f6m", GIT_PATH_MINIMAL + .dequote(GIT_PATH.quote("\u00c5n\\str\u00f6m"))); + + } + + @Test + public void testRoundtripMinimalDequoteNormal() { + assertEquals("\u00c5ngstr\u00f6m", + GIT_PATH.dequote(GIT_PATH_MINIMAL.quote("\u00c5ngstr\u00f6m"))); + + } + + @Test + public void testRoundtripNormalDequoteMinimal() { + assertEquals("\u00c5ngstr\u00f6m", + GIT_PATH_MINIMAL.dequote(GIT_PATH.quote("\u00c5ngstr\u00f6m"))); + + } + + @Test + public void testDequote_UTF8_Minimal() { + assertDequoteMinimal("\u00c5ngstr\u00f6m", + "\\303\\205ngstr\\303\\266m"); + } + + @Test + public void testDequote_RawUTF8_Minimal() { + assertDequoteMinimal("\u00c5ngstr\u00f6m", "\303\205ngstr\303\266m"); + } + + @Test + public void testDequote_RawLatin1_Minimal() { + assertDequoteMinimal("\u00c5ngstr\u00f6m", "\305ngstr\366m"); + } + } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java index fa552aabe..d76449915 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java @@ -145,6 +145,8 @@ public class DiffFormatter implements AutoCloseable { private Repository repository; + private Boolean quotePaths; + /** * Create a new formatter with a default level of context. * @@ -199,6 +201,11 @@ public class DiffFormatter implements AutoCloseable { this.closeReader = closeReader; this.reader = reader; this.diffCfg = cfg.get(DiffConfig.KEY); + if (quotePaths == null) { + quotePaths = Boolean + .valueOf(cfg.getBoolean(ConfigConstants.CONFIG_CORE_SECTION, + ConfigConstants.CONFIG_KEY_QUOTE_PATH, true)); + } ContentSource cs = ContentSource.create(reader); source = new ContentSource.Pair(cs, cs); @@ -378,6 +385,21 @@ public class DiffFormatter implements AutoCloseable { progressMonitor = pm; } + /** + * Sets whether or not path names should be quoted. + *

+ * By default the setting of git config {@code core.quotePath} is active, + * but this can be overridden through this method. + *

+ * + * @param quote + * whether to quote path names + * @since 5.6 + */ + public void setQuotePaths(boolean quote) { + quotePaths = Boolean.valueOf(quote); + } + /** * Set the filter to produce only specific paths. * @@ -726,8 +748,11 @@ public class DiffFormatter implements AutoCloseable { return id.name(); } - private static String quotePath(String name) { - return QuotedString.GIT_PATH.quote(name); + private String quotePath(String path) { + if (quotePaths == null || quotePaths.booleanValue()) { + return QuotedString.GIT_PATH.quote(path); + } + return QuotedString.GIT_PATH_MINIMAL.quote(path); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index f2f1d5a48..e0bd59219 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -155,6 +155,12 @@ public final class ConfigConstants { */ public static final String CONFIG_KEY_HOOKS_PATH = "hooksPath"; + /** + * The "quotePath" key. + * @since 5.6 + */ + public static final String CONFIG_KEY_QUOTE_PATH = "quotePath"; + /** The "algorithm" key */ public static final String CONFIG_KEY_ALGORITHM = "algorithm"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java index a55cad370..2b2358abe 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, 2019 Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -54,7 +54,15 @@ import org.eclipse.jgit.lib.Constants; */ public abstract class QuotedString { /** Quoting style that obeys the rules Git applies to file names */ - public static final GitPathStyle GIT_PATH = new GitPathStyle(); + public static final GitPathStyle GIT_PATH = new GitPathStyle(true); + + /** + * Quoting style that obeys the rules Git applies to file names when + * {@code core.quotePath = false}. + * + * @since 5.6 + */ + public static final QuotedString GIT_PATH_MINIMAL = new GitPathStyle(false); /** * Quoting style used by the Bourne shell. @@ -256,40 +264,48 @@ public abstract class QuotedString { quote['"'] = '"'; } + private final boolean quoteHigh; + @Override public String quote(String instr) { - if (instr.length() == 0) + if (instr.isEmpty()) { return "\"\""; //$NON-NLS-1$ + } boolean reuse = true; final byte[] in = Constants.encode(instr); - final StringBuilder r = new StringBuilder(2 + in.length); - r.append('"'); + final byte[] out = new byte[4 * in.length + 2]; + int o = 0; + out[o++] = '"'; for (int i = 0; i < in.length; i++) { final int c = in[i] & 0xff; if (c < quote.length) { final byte style = quote[c]; if (style == 0) { - r.append((char) c); + out[o++] = (byte) c; continue; } if (style > 0) { reuse = false; - r.append('\\'); - r.append((char) style); + out[o++] = '\\'; + out[o++] = style; continue; } + } else if (!quoteHigh) { + out[o++] = (byte) c; + continue; } reuse = false; - r.append('\\'); - r.append((char) (((c >> 6) & 03) + '0')); - r.append((char) (((c >> 3) & 07) + '0')); - r.append((char) (((c >> 0) & 07) + '0')); + out[o++] = '\\'; + out[o++] = (byte) (((c >> 6) & 03) + '0'); + out[o++] = (byte) (((c >> 3) & 07) + '0'); + out[o++] = (byte) (((c >> 0) & 07) + '0'); } - if (reuse) + if (reuse) { return instr; - r.append('"'); - return r.toString(); + } + out[o++] = '"'; + return new String(out, 0, o, UTF_8); } @Override @@ -375,8 +391,8 @@ public abstract class QuotedString { return RawParseUtils.decode(UTF_8, r, 0, rPtr); } - private GitPathStyle() { - // Singleton + private GitPathStyle(boolean doQuote) { + quoteHigh = doQuote; } } }