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 <thomas.wolf@paranor.ch>
This commit is contained in:
Thomas Wolf 2019-10-28 18:11:57 +01:00 committed by Matthias Sohn
parent a227dc3ba0
commit 345e2648df
4 changed files with 144 additions and 19 deletions

View File

@ -45,6 +45,7 @@
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 @@ private static void assertDequote(String exp, String in) {
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 void testQuoteAtAndNumber() {
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");
}
}

View File

@ -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 @@ private void setReader(ObjectReader reader, Config cfg, boolean closeReader) {
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 void setProgressMonitor(ProgressMonitor pm) {
progressMonitor = pm;
}
/**
* Sets whether or not path names should be quoted.
* <p>
* By default the setting of git config {@code core.quotePath} is active,
* but this can be overridden through this method.
* </p>
*
* @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 @@ private String format(AbbreviatedObjectId id) {
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);
}
/**

View File

@ -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";

View File

@ -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 @@
*/
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 static final class GitPathStyle extends 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 @@ private static String dq(byte[] in, int inPtr, int inEnd) {
return RawParseUtils.decode(UTF_8, r, 0, rPtr);
}
private GitPathStyle() {
// Singleton
private GitPathStyle(boolean doQuote) {
quoteHigh = doQuote;
}
}
}