View Javadoc
1   /*
2    * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3    * Copyright (C) 2009-2010, Google Inc.
4    * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
5    * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
6    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
7    * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
8    * and other copyright owners as documented in the project's IP log.
9    *
10   * This program and the accompanying materials are made available
11   * under the terms of the Eclipse Distribution License v1.0 which
12   * accompanies this distribution, is reproduced below, and is
13   * available at http://www.eclipse.org/org/documents/edl-v10.php
14   *
15   * All rights reserved.
16   *
17   * Redistribution and use in source and binary forms, with or
18   * without modification, are permitted provided that the following
19   * conditions are met:
20   *
21   * - Redistributions of source code must retain the above copyright
22   *   notice, this list of conditions and the following disclaimer.
23   *
24   * - Redistributions in binary form must reproduce the above
25   *   copyright notice, this list of conditions and the following
26   *   disclaimer in the documentation and/or other materials provided
27   *   with the distribution.
28   *
29   * - Neither the name of the Eclipse Foundation, Inc. nor the
30   *   names of its contributors may be used to endorse or promote
31   *   products derived from this software without specific prior
32   *   written permission.
33   *
34   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
35   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
36   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
38   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
39   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
43   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
46   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47   */
48  
49  package org.eclipse.jgit.lib;
50  
51  import static java.util.concurrent.TimeUnit.DAYS;
52  import static java.util.concurrent.TimeUnit.HOURS;
53  import static java.util.concurrent.TimeUnit.MILLISECONDS;
54  import static java.util.concurrent.TimeUnit.MINUTES;
55  import static java.util.concurrent.TimeUnit.SECONDS;
56  import static org.eclipse.jgit.util.FileUtils.pathToString;
57  import static org.junit.Assert.assertArrayEquals;
58  import static org.junit.Assert.assertEquals;
59  import static org.junit.Assert.assertFalse;
60  import static org.junit.Assert.assertNull;
61  import static org.junit.Assert.assertSame;
62  import static org.junit.Assert.assertTrue;
63  import static org.junit.Assert.fail;
64  
65  import java.io.File;
66  import java.io.IOException;
67  import java.nio.file.Files;
68  import java.text.MessageFormat;
69  import java.util.Arrays;
70  import java.util.Iterator;
71  import java.util.LinkedList;
72  import java.util.Set;
73  import java.util.concurrent.TimeUnit;
74  
75  import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
76  import org.eclipse.jgit.errors.ConfigInvalidException;
77  import org.eclipse.jgit.internal.JGitText;
78  import org.eclipse.jgit.junit.MockSystemReader;
79  import org.eclipse.jgit.merge.MergeConfig;
80  import org.eclipse.jgit.storage.file.FileBasedConfig;
81  import org.eclipse.jgit.util.FS;
82  import org.eclipse.jgit.util.SystemReader;
83  import org.junit.After;
84  import org.junit.Rule;
85  import org.junit.Test;
86  import org.junit.rules.ExpectedException;
87  import org.junit.rules.TemporaryFolder;
88  
89  /**
90   * Test reading of git config
91   */
92  @SuppressWarnings("boxing")
93  public class ConfigTest {
94  	// A non-ASCII whitespace character: U+2002 EN QUAD.
95  	private static final char WS = '\u2002';
96  
97  	@Rule
98  	public ExpectedException expectedEx = ExpectedException.none();
99  
100 	@Rule
101 	public TemporaryFolder tmp = new TemporaryFolder();
102 
103 	@After
104 	public void tearDown() {
105 		SystemReader.setInstance(null);
106 	}
107 
108 	@Test
109 	public void test001_ReadBareKey() throws ConfigInvalidException {
110 		final Config c = parse("[foo]\nbar\n");
111 		assertTrue(c.getBoolean("foo", null, "bar", false));
112 		assertEquals("", c.getString("foo", null, "bar"));
113 	}
114 
115 	@Test
116 	public void test002_ReadWithSubsection() throws ConfigInvalidException {
117 		final Config c = parse("[foo \"zip\"]\nbar\n[foo \"zap\"]\nbar=false\nn=3\n");
118 		assertTrue(c.getBoolean("foo", "zip", "bar", false));
119 		assertEquals("", c.getString("foo","zip", "bar"));
120 		assertFalse(c.getBoolean("foo", "zap", "bar", true));
121 		assertEquals("false", c.getString("foo", "zap", "bar"));
122 		assertEquals(3, c.getInt("foo", "zap", "n", 4));
123 		assertEquals(4, c.getInt("foo", "zap","m", 4));
124 	}
125 
126 	@Test
127 	public void test003_PutRemote() {
128 		final Config c = new Config();
129 		c.setString("sec", "ext", "name", "value");
130 		c.setString("sec", "ext", "name2", "value2");
131 		final String expText = "[sec \"ext\"]\n\tname = value\n\tname2 = value2\n";
132 		assertEquals(expText, c.toText());
133 	}
134 
135 	@Test
136 	public void test004_PutGetSimple() {
137 		Config c = new Config();
138 		c.setString("my", null, "somename", "false");
139 		assertEquals("false", c.getString("my", null, "somename"));
140 		assertEquals("[my]\n\tsomename = false\n", c.toText());
141 	}
142 
143 	@Test
144 	public void test005_PutGetStringList() {
145 		Config c = new Config();
146 		final LinkedList<String> values = new LinkedList<>();
147 		values.add("value1");
148 		values.add("value2");
149 		c.setStringList("my", null, "somename", values);
150 
151 		final Object[] expArr = values.toArray();
152 		final String[] actArr = c.getStringList("my", null, "somename");
153 		assertArrayEquals(expArr, actArr);
154 
155 		final String expText = "[my]\n\tsomename = value1\n\tsomename = value2\n";
156 		assertEquals(expText, c.toText());
157 	}
158 
159 	@Test
160 	public void test006_readCaseInsensitive() throws ConfigInvalidException {
161 		final Config c = parse("[Foo]\nBar\n");
162 		assertTrue(c.getBoolean("foo", null, "bar", false));
163 		assertEquals("", c.getString("foo", null, "bar"));
164 	}
165 
166 	@Test
167 	public void test007_readUserConfig() {
168 		final MockSystemReader mockSystemReader = new MockSystemReader();
169 		SystemReader.setInstance(mockSystemReader);
170 		final String hostname = mockSystemReader.getHostname();
171 		final Config userGitConfig = mockSystemReader.openUserConfig(null,
172 				FS.DETECTED);
173 		final Config localConfig = new Config(userGitConfig);
174 		mockSystemReader.clearProperties();
175 
176 		String authorName;
177 		String authorEmail;
178 
179 		// no values defined nowhere
180 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
181 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
182 		assertEquals(Constants.UNKNOWN_USER_DEFAULT, authorName);
183 		assertEquals(Constants.UNKNOWN_USER_DEFAULT + "@" + hostname, authorEmail);
184 		assertTrue(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
185 		assertTrue(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
186 
187 		// the system user name is defined
188 		mockSystemReader.setProperty(Constants.OS_USER_NAME_KEY, "os user name");
189 		localConfig.uncache(UserConfig.KEY);
190 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
191 		assertEquals("os user name", authorName);
192 		assertTrue(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
193 
194 		if (hostname != null && hostname.length() != 0) {
195 			authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
196 			assertEquals("os user name@" + hostname, authorEmail);
197 		}
198 		assertTrue(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
199 
200 		// the git environment variables are defined
201 		mockSystemReader.setProperty(Constants.GIT_AUTHOR_NAME_KEY, "git author name");
202 		mockSystemReader.setProperty(Constants.GIT_AUTHOR_EMAIL_KEY, "author@email");
203 		localConfig.uncache(UserConfig.KEY);
204 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
205 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
206 		assertEquals("git author name", authorName);
207 		assertEquals("author@email", authorEmail);
208 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
209 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
210 
211 		// the values are defined in the global configuration
212 		// first clear environment variables since they would override
213 		// configuration files
214 		mockSystemReader.clearProperties();
215 		userGitConfig.setString("user", null, "name", "global username");
216 		userGitConfig.setString("user", null, "email", "author@globalemail");
217 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
218 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
219 		assertEquals("global username", authorName);
220 		assertEquals("author@globalemail", authorEmail);
221 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
222 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
223 
224 		// the values are defined in the local configuration
225 		localConfig.setString("user", null, "name", "local username");
226 		localConfig.setString("user", null, "email", "author@localemail");
227 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
228 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
229 		assertEquals("local username", authorName);
230 		assertEquals("author@localemail", authorEmail);
231 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
232 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
233 
234 		authorName = localConfig.get(UserConfig.KEY).getCommitterName();
235 		authorEmail = localConfig.get(UserConfig.KEY).getCommitterEmail();
236 		assertEquals("local username", authorName);
237 		assertEquals("author@localemail", authorEmail);
238 		assertFalse(localConfig.get(UserConfig.KEY).isCommitterNameImplicit());
239 		assertFalse(localConfig.get(UserConfig.KEY).isCommitterEmailImplicit());
240 
241 		// also git environment variables are defined
242 		mockSystemReader.setProperty(Constants.GIT_AUTHOR_NAME_KEY,
243 				"git author name");
244 		mockSystemReader.setProperty(Constants.GIT_AUTHOR_EMAIL_KEY,
245 				"author@email");
246 		localConfig.setString("user", null, "name", "local username");
247 		localConfig.setString("user", null, "email", "author@localemail");
248 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
249 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
250 		assertEquals("git author name", authorName);
251 		assertEquals("author@email", authorEmail);
252 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
253 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
254 	}
255 
256 	@Test
257 	public void testReadUserConfigWithInvalidCharactersStripped() {
258 		final MockSystemReader mockSystemReader = new MockSystemReader();
259 		final Config localConfig = new Config(mockSystemReader.openUserConfig(
260 				null, FS.DETECTED));
261 
262 		localConfig.setString("user", null, "name", "foo<bar");
263 		localConfig.setString("user", null, "email", "baz>\nqux@example.com");
264 
265 		UserConfig userConfig = localConfig.get(UserConfig.KEY);
266 		assertEquals("foobar", userConfig.getAuthorName());
267 		assertEquals("bazqux@example.com", userConfig.getAuthorEmail());
268 	}
269 
270 	@Test
271 	public void testReadBoolean_TrueFalse1() throws ConfigInvalidException {
272 		final Config c = parse("[s]\na = true\nb = false\n");
273 		assertEquals("true", c.getString("s", null, "a"));
274 		assertEquals("false", c.getString("s", null, "b"));
275 
276 		assertTrue(c.getBoolean("s", "a", false));
277 		assertFalse(c.getBoolean("s", "b", true));
278 	}
279 
280 	@Test
281 	public void testReadBoolean_TrueFalse2() throws ConfigInvalidException {
282 		final Config c = parse("[s]\na = TrUe\nb = fAlSe\n");
283 		assertEquals("TrUe", c.getString("s", null, "a"));
284 		assertEquals("fAlSe", c.getString("s", null, "b"));
285 
286 		assertTrue(c.getBoolean("s", "a", false));
287 		assertFalse(c.getBoolean("s", "b", true));
288 	}
289 
290 	@Test
291 	public void testReadBoolean_YesNo1() throws ConfigInvalidException {
292 		final Config c = parse("[s]\na = yes\nb = no\n");
293 		assertEquals("yes", c.getString("s", null, "a"));
294 		assertEquals("no", c.getString("s", null, "b"));
295 
296 		assertTrue(c.getBoolean("s", "a", false));
297 		assertFalse(c.getBoolean("s", "b", true));
298 	}
299 
300 	@Test
301 	public void testReadBoolean_YesNo2() throws ConfigInvalidException {
302 		final Config c = parse("[s]\na = yEs\nb = NO\n");
303 		assertEquals("yEs", c.getString("s", null, "a"));
304 		assertEquals("NO", c.getString("s", null, "b"));
305 
306 		assertTrue(c.getBoolean("s", "a", false));
307 		assertFalse(c.getBoolean("s", "b", true));
308 	}
309 
310 	@Test
311 	public void testReadBoolean_OnOff1() throws ConfigInvalidException {
312 		final Config c = parse("[s]\na = on\nb = off\n");
313 		assertEquals("on", c.getString("s", null, "a"));
314 		assertEquals("off", c.getString("s", null, "b"));
315 
316 		assertTrue(c.getBoolean("s", "a", false));
317 		assertFalse(c.getBoolean("s", "b", true));
318 	}
319 
320 	@Test
321 	public void testReadBoolean_OnOff2() throws ConfigInvalidException {
322 		final Config c = parse("[s]\na = ON\nb = OFF\n");
323 		assertEquals("ON", c.getString("s", null, "a"));
324 		assertEquals("OFF", c.getString("s", null, "b"));
325 
326 		assertTrue(c.getBoolean("s", "a", false));
327 		assertFalse(c.getBoolean("s", "b", true));
328 	}
329 
330 	static enum TestEnum {
331 		ONE_TWO;
332 	}
333 
334 	@Test
335 	public void testGetEnum() throws ConfigInvalidException {
336 		Config c = parse("[s]\na = ON\nb = input\nc = true\nd = off\n");
337 		assertSame(CoreConfig.AutoCRLF.TRUE, c.getEnum("s", null, "a",
338 				CoreConfig.AutoCRLF.FALSE));
339 
340 		assertSame(CoreConfig.AutoCRLF.INPUT, c.getEnum("s", null, "b",
341 				CoreConfig.AutoCRLF.FALSE));
342 
343 		assertSame(CoreConfig.AutoCRLF.TRUE, c.getEnum("s", null, "c",
344 				CoreConfig.AutoCRLF.FALSE));
345 
346 		assertSame(CoreConfig.AutoCRLF.FALSE, c.getEnum("s", null, "d",
347 				CoreConfig.AutoCRLF.TRUE));
348 
349 		c = new Config();
350 		assertSame(CoreConfig.AutoCRLF.FALSE, c.getEnum("s", null, "d",
351 				CoreConfig.AutoCRLF.FALSE));
352 
353 		c = parse("[s \"b\"]\n\tc = one two\n");
354 		assertSame(TestEnum.ONE_TWO, c.getEnum("s", "b", "c", TestEnum.ONE_TWO));
355 
356 		c = parse("[s \"b\"]\n\tc = one-two\n");
357 		assertSame(TestEnum.ONE_TWO, c.getEnum("s", "b", "c", TestEnum.ONE_TWO));
358 	}
359 
360 	@Test
361 	public void testGetInvalidEnum() throws ConfigInvalidException {
362 		Config c = parse("[a]\n\tb = invalid\n");
363 		try {
364 			c.getEnum("a", null, "b", TestEnum.ONE_TWO);
365 			fail();
366 		} catch (IllegalArgumentException e) {
367 			assertEquals("Invalid value: a.b=invalid", e.getMessage());
368 		}
369 
370 		c = parse("[a \"b\"]\n\tc = invalid\n");
371 		try {
372 			c.getEnum("a", "b", "c", TestEnum.ONE_TWO);
373 			fail();
374 		} catch (IllegalArgumentException e) {
375 			assertEquals("Invalid value: a.b.c=invalid", e.getMessage());
376 		}
377 	}
378 
379 	@Test
380 	public void testSetEnum() {
381 		final Config c = new Config();
382 		c.setEnum("s", "b", "c", TestEnum.ONE_TWO);
383 		assertEquals("[s \"b\"]\n\tc = one two\n", c.toText());
384 	}
385 
386 	@Test
387 	public void testGetFastForwardMergeoptions() throws ConfigInvalidException {
388 		Config c = new Config(null); // not set
389 		assertSame(FastForwardMode.FF, c.getEnum(
390 				ConfigConstants.CONFIG_BRANCH_SECTION, "side",
391 				ConfigConstants.CONFIG_KEY_MERGEOPTIONS, FastForwardMode.FF));
392 		MergeConfig mergeConfig = c.get(MergeConfig.getParser("side"));
393 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
394 		c = parse("[branch \"side\"]\n\tmergeoptions = --ff-only\n");
395 		assertSame(FastForwardMode.FF_ONLY, c.getEnum(
396 				ConfigConstants.CONFIG_BRANCH_SECTION, "side",
397 				ConfigConstants.CONFIG_KEY_MERGEOPTIONS,
398 				FastForwardMode.FF_ONLY));
399 		mergeConfig = c.get(MergeConfig.getParser("side"));
400 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
401 		c = parse("[branch \"side\"]\n\tmergeoptions = --ff\n");
402 		assertSame(FastForwardMode.FF, c.getEnum(
403 				ConfigConstants.CONFIG_BRANCH_SECTION, "side",
404 				ConfigConstants.CONFIG_KEY_MERGEOPTIONS, FastForwardMode.FF));
405 		mergeConfig = c.get(MergeConfig.getParser("side"));
406 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
407 		c = parse("[branch \"side\"]\n\tmergeoptions = --no-ff\n");
408 		assertSame(FastForwardMode.NO_FF, c.getEnum(
409 				ConfigConstants.CONFIG_BRANCH_SECTION, "side",
410 				ConfigConstants.CONFIG_KEY_MERGEOPTIONS, FastForwardMode.NO_FF));
411 		mergeConfig = c.get(MergeConfig.getParser("side"));
412 		assertSame(FastForwardMode.NO_FF, mergeConfig.getFastForwardMode());
413 	}
414 
415 	@Test
416 	public void testSetFastForwardMergeoptions() {
417 		final Config c = new Config();
418 		c.setEnum("branch", "side", "mergeoptions", FastForwardMode.FF);
419 		assertEquals("[branch \"side\"]\n\tmergeoptions = --ff\n", c.toText());
420 		c.setEnum("branch", "side", "mergeoptions", FastForwardMode.FF_ONLY);
421 		assertEquals("[branch \"side\"]\n\tmergeoptions = --ff-only\n",
422 				c.toText());
423 		c.setEnum("branch", "side", "mergeoptions", FastForwardMode.NO_FF);
424 		assertEquals("[branch \"side\"]\n\tmergeoptions = --no-ff\n",
425 				c.toText());
426 	}
427 
428 	@Test
429 	public void testGetFastForwardMerge() throws ConfigInvalidException {
430 		Config c = new Config(null); // not set
431 		assertSame(FastForwardMode.Merge.TRUE, c.getEnum(
432 				ConfigConstants.CONFIG_KEY_MERGE, null,
433 				ConfigConstants.CONFIG_KEY_FF, FastForwardMode.Merge.TRUE));
434 		MergeConfig mergeConfig = c.get(MergeConfig.getParser("side"));
435 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
436 		c = parse("[merge]\n\tff = only\n");
437 		assertSame(FastForwardMode.Merge.ONLY, c.getEnum(
438 				ConfigConstants.CONFIG_KEY_MERGE, null,
439 				ConfigConstants.CONFIG_KEY_FF, FastForwardMode.Merge.ONLY));
440 		mergeConfig = c.get(MergeConfig.getParser("side"));
441 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
442 		c = parse("[merge]\n\tff = true\n");
443 		assertSame(FastForwardMode.Merge.TRUE, c.getEnum(
444 				ConfigConstants.CONFIG_KEY_MERGE, null,
445 				ConfigConstants.CONFIG_KEY_FF, FastForwardMode.Merge.TRUE));
446 		mergeConfig = c.get(MergeConfig.getParser("side"));
447 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
448 		c = parse("[merge]\n\tff = false\n");
449 		assertSame(FastForwardMode.Merge.FALSE, c.getEnum(
450 				ConfigConstants.CONFIG_KEY_MERGE, null,
451 				ConfigConstants.CONFIG_KEY_FF, FastForwardMode.Merge.FALSE));
452 		mergeConfig = c.get(MergeConfig.getParser("side"));
453 		assertSame(FastForwardMode.NO_FF, mergeConfig.getFastForwardMode());
454 	}
455 
456 	@Test
457 	public void testCombinedMergeOptions() throws ConfigInvalidException {
458 		Config c = new Config(null); // not set
459 		MergeConfig mergeConfig = c.get(MergeConfig.getParser("side"));
460 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
461 		assertTrue(mergeConfig.isCommit());
462 		assertFalse(mergeConfig.isSquash());
463 		// branch..mergeoptions should win over merge.ff
464 		c = parse("[merge]\n\tff = false\n"
465 				+ "[branch \"side\"]\n\tmergeoptions = --ff-only\n");
466 		mergeConfig = c.get(MergeConfig.getParser("side"));
467 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
468 		assertTrue(mergeConfig.isCommit());
469 		assertFalse(mergeConfig.isSquash());
470 		// merge.ff used for ff setting if not set via mergeoptions
471 		c = parse("[merge]\n\tff = only\n"
472 				+ "[branch \"side\"]\n\tmergeoptions = --squash\n");
473 		mergeConfig = c.get(MergeConfig.getParser("side"));
474 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
475 		assertTrue(mergeConfig.isCommit());
476 		assertTrue(mergeConfig.isSquash());
477 		// mergeoptions wins if it has ff options amongst other options
478 		c = parse("[merge]\n\tff = false\n"
479 				+ "[branch \"side\"]\n\tmergeoptions = --ff-only --no-commit\n");
480 		mergeConfig = c.get(MergeConfig.getParser("side"));
481 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
482 		assertFalse(mergeConfig.isCommit());
483 		assertFalse(mergeConfig.isSquash());
484 	}
485 
486 	@Test
487 	public void testSetFastForwardMerge() {
488 		final Config c = new Config();
489 		c.setEnum("merge", null, "ff",
490 				FastForwardMode.Merge.valueOf(FastForwardMode.FF));
491 		assertEquals("[merge]\n\tff = true\n", c.toText());
492 		c.setEnum("merge", null, "ff",
493 				FastForwardMode.Merge.valueOf(FastForwardMode.FF_ONLY));
494 		assertEquals("[merge]\n\tff = only\n", c.toText());
495 		c.setEnum("merge", null, "ff",
496 				FastForwardMode.Merge.valueOf(FastForwardMode.NO_FF));
497 		assertEquals("[merge]\n\tff = false\n", c.toText());
498 	}
499 
500 	@Test
501 	public void testReadLong() throws ConfigInvalidException {
502 		assertReadLong(1L);
503 		assertReadLong(-1L);
504 		assertReadLong(Long.MIN_VALUE);
505 		assertReadLong(Long.MAX_VALUE);
506 		assertReadLong(4L * 1024 * 1024 * 1024, "4g");
507 		assertReadLong(3L * 1024 * 1024, "3 m");
508 		assertReadLong(8L * 1024, "8 k");
509 
510 		try {
511 			assertReadLong(-1, "1.5g");
512 			fail("incorrectly accepted 1.5g");
513 		} catch (IllegalArgumentException e) {
514 			assertEquals("Invalid integer value: s.a=1.5g", e.getMessage());
515 		}
516 	}
517 
518 	@Test
519 	public void testBooleanWithNoValue() throws ConfigInvalidException {
520 		Config c = parse("[my]\n\tempty\n");
521 		assertEquals("", c.getString("my", null, "empty"));
522 		assertEquals(1, c.getStringList("my", null, "empty").length);
523 		assertEquals("", c.getStringList("my", null, "empty")[0]);
524 		assertTrue(c.getBoolean("my", "empty", false));
525 		assertEquals("[my]\n\tempty\n", c.toText());
526 	}
527 
528 	@Test
529 	public void testUnsetBranchSection() throws ConfigInvalidException {
530 		Config c = parse("" //
531 				+ "[branch \"keep\"]\n"
532 				+ "  merge = master.branch.to.keep.in.the.file\n"
533 				+ "\n"
534 				+ "[branch \"remove\"]\n"
535 				+ "  merge = this.will.get.deleted\n"
536 				+ "  remote = origin-for-some-long-gone-place\n"
537 				+ "\n"
538 				+ "[core-section-not-to-remove-in-test]\n"
539 				+ "  packedGitLimit = 14\n");
540 		c.unsetSection("branch", "does.not.exist");
541 		c.unsetSection("branch", "remove");
542 		assertEquals("" //
543 				+ "[branch \"keep\"]\n"
544 				+ "  merge = master.branch.to.keep.in.the.file\n"
545 				+ "\n"
546 				+ "[core-section-not-to-remove-in-test]\n"
547 				+ "  packedGitLimit = 14\n", c.toText());
548 	}
549 
550 	@Test
551 	public void testUnsetSingleSection() throws ConfigInvalidException {
552 		Config c = parse("" //
553 				+ "[branch \"keep\"]\n"
554 				+ "  merge = master.branch.to.keep.in.the.file\n"
555 				+ "\n"
556 				+ "[single]\n"
557 				+ "  merge = this.will.get.deleted\n"
558 				+ "  remote = origin-for-some-long-gone-place\n"
559 				+ "\n"
560 				+ "[core-section-not-to-remove-in-test]\n"
561 				+ "  packedGitLimit = 14\n");
562 		c.unsetSection("single", null);
563 		assertEquals("" //
564 				+ "[branch \"keep\"]\n"
565 				+ "  merge = master.branch.to.keep.in.the.file\n"
566 				+ "\n"
567 				+ "[core-section-not-to-remove-in-test]\n"
568 				+ "  packedGitLimit = 14\n", c.toText());
569 	}
570 
571 	@Test
572 	public void test008_readSectionNames() throws ConfigInvalidException {
573 		final Config c = parse("[a]\n [B]\n");
574 		Set<String> sections = c.getSections();
575 		assertTrue("Sections should contain \"a\"", sections.contains("a"));
576 		assertTrue("Sections should contain \"b\"", sections.contains("b"));
577 	}
578 
579 	@Test
580 	public void test009_readNamesInSection() throws ConfigInvalidException {
581 		String configString = "[core]\n" + "repositoryFormatVersion = 0\n"
582 				+ "filemode = false\n" + "logAllRefUpdates = true\n";
583 		final Config c = parse(configString);
584 		Set<String> names = c.getNames("core");
585 		assertEquals("Core section size", 3, names.size());
586 		assertTrue("Core section should contain \"filemode\"", names
587 				.contains("filemode"));
588 
589 		assertTrue("Core section should contain \"repositoryFormatVersion\"",
590 				names.contains("repositoryFormatVersion"));
591 
592 		assertTrue("Core section should contain \"repositoryformatversion\"",
593 				names.contains("repositoryformatversion"));
594 
595 		Iterator<String> itr = names.iterator();
596 		assertEquals("filemode", itr.next());
597 		assertEquals("logAllRefUpdates", itr.next());
598 		assertEquals("repositoryFormatVersion", itr.next());
599 		assertFalse(itr.hasNext());
600 	}
601 
602 	@Test
603 	public void test_ReadNamesInSectionRecursive()
604 			throws ConfigInvalidException {
605 		String baseConfigString = "[core]\n" + "logAllRefUpdates = true\n";
606 		String configString = "[core]\n" + "repositoryFormatVersion = 0\n"
607 				+ "filemode = false\n";
608 		final Config c = parse(configString, parse(baseConfigString));
609 		Set<String> names = c.getNames("core", true);
610 		assertEquals("Core section size", 3, names.size());
611 		assertTrue("Core section should contain \"filemode\"",
612 				names.contains("filemode"));
613 		assertTrue("Core section should contain \"repositoryFormatVersion\"",
614 				names.contains("repositoryFormatVersion"));
615 		assertTrue("Core section should contain \"logAllRefUpdates\"",
616 				names.contains("logAllRefUpdates"));
617 		assertTrue("Core section should contain \"logallrefupdates\"",
618 				names.contains("logallrefupdates"));
619 
620 		Iterator<String> itr = names.iterator();
621 		assertEquals("filemode", itr.next());
622 		assertEquals("repositoryFormatVersion", itr.next());
623 		assertEquals("logAllRefUpdates", itr.next());
624 		assertFalse(itr.hasNext());
625 	}
626 
627 	@Test
628 	public void test010_readNamesInSubSection() throws ConfigInvalidException {
629 		String configString = "[a \"sub1\"]\n"//
630 				+ "x = 0\n" //
631 				+ "y = false\n"//
632 				+ "z = true\n"//
633 				+ "[a \"sub2\"]\n"//
634 				+ "a=0\n"//
635 				+ "b=1\n";
636 		final Config c = parse(configString);
637 		Set<String> names = c.getNames("a", "sub1");
638 		assertEquals("Subsection size", 3, names.size());
639 		assertTrue("Subsection should contain \"x\"", names.contains("x"));
640 		assertTrue("Subsection should contain \"y\"", names.contains("y"));
641 		assertTrue("Subsection should contain \"z\"", names.contains("z"));
642 		names = c.getNames("a", "sub2");
643 		assertEquals("Subsection size", 2, names.size());
644 		assertTrue("Subsection should contain \"a\"", names.contains("a"));
645 		assertTrue("Subsection should contain \"b\"", names.contains("b"));
646 	}
647 
648 	@Test
649 	public void readNamesInSubSectionRecursive() throws ConfigInvalidException {
650 		String baseConfigString = "[a \"sub1\"]\n"//
651 				+ "x = 0\n" //
652 				+ "y = false\n"//
653 				+ "[a \"sub2\"]\n"//
654 				+ "A=0\n";//
655 		String configString = "[a \"sub1\"]\n"//
656 				+ "z = true\n"//
657 				+ "[a \"sub2\"]\n"//
658 				+ "B=1\n";
659 		final Config c = parse(configString, parse(baseConfigString));
660 		Set<String> names = c.getNames("a", "sub1", true);
661 		assertEquals("Subsection size", 3, names.size());
662 		assertTrue("Subsection should contain \"x\"", names.contains("x"));
663 		assertTrue("Subsection should contain \"y\"", names.contains("y"));
664 		assertTrue("Subsection should contain \"z\"", names.contains("z"));
665 		names = c.getNames("a", "sub2", true);
666 		assertEquals("Subsection size", 2, names.size());
667 		assertTrue("Subsection should contain \"A\"", names.contains("A"));
668 		assertTrue("Subsection should contain \"a\"", names.contains("a"));
669 		assertTrue("Subsection should contain \"B\"", names.contains("B"));
670 	}
671 
672 
673 	@Test
674 	public void testNoFinalNewline() throws ConfigInvalidException {
675 		Config c = parse("[a]\n"
676 				+ "x = 0\n"
677 				+ "y = 1");
678 		assertEquals("0", c.getString("a", null, "x"));
679 		assertEquals("1", c.getString("a", null, "y"));
680 	}
681 
682 	@Test
683 	public void testExplicitlySetEmptyString() throws Exception {
684 		Config c = new Config();
685 		c.setString("a", null, "x", "0");
686 		c.setString("a", null, "y", "");
687 
688 		assertEquals("0", c.getString("a", null, "x"));
689 		assertEquals(0, c.getInt("a", null, "x", 1));
690 
691 		assertEquals("", c.getString("a", null, "y"));
692 		assertArrayEquals(new String[]{""}, c.getStringList("a", null, "y"));
693 		try {
694 			c.getInt("a", null, "y", 1);
695 		} catch (IllegalArgumentException e) {
696 			assertEquals("Invalid integer value: a.y=", e.getMessage());
697 		}
698 
699 		assertNull(c.getString("a", null, "z"));
700 		assertArrayEquals(new String[]{}, c.getStringList("a", null, "z"));
701 	}
702 
703 	@Test
704 	public void testParsedEmptyString() throws Exception {
705 		Config c = parse("[a]\n"
706 				+ "x = 0\n"
707 				+ "y =\n");
708 
709 		assertEquals("0", c.getString("a", null, "x"));
710 		assertEquals(0, c.getInt("a", null, "x", 1));
711 
712 		assertNull(c.getString("a", null, "y"));
713 		assertArrayEquals(new String[]{null}, c.getStringList("a", null, "y"));
714 		try {
715 			c.getInt("a", null, "y", 1);
716 		} catch (IllegalArgumentException e) {
717 			assertEquals("Invalid integer value: a.y=", e.getMessage());
718 		}
719 
720 		assertNull(c.getString("a", null, "z"));
721 		assertArrayEquals(new String[]{}, c.getStringList("a", null, "z"));
722 	}
723 
724 	@Test
725 	public void testSetStringListWithEmptyValue() throws Exception {
726 		Config c = new Config();
727 		c.setStringList("a", null, "x", Arrays.asList(""));
728 		assertArrayEquals(new String[]{""}, c.getStringList("a", null, "x"));
729 	}
730 
731 	@Test
732 	public void testEmptyValueAtEof() throws Exception {
733 		String text = "[a]\nx =";
734 		Config c = parse(text);
735 		assertNull(c.getString("a", null, "x"));
736 		assertArrayEquals(new String[]{null},
737 				c.getStringList("a", null, "x"));
738 		c = parse(text + "\n");
739 		assertNull(c.getString("a", null, "x"));
740 		assertArrayEquals(new String[]{null},
741 				c.getStringList("a", null, "x"));
742 	}
743 
744 	@Test
745 	public void testReadMultipleValuesForName() throws ConfigInvalidException {
746 		Config c = parse("[foo]\nbar=false\nbar=true\n");
747 		assertTrue(c.getBoolean("foo", "bar", false));
748 	}
749 
750 	@Test
751 	public void testIncludeInvalidName() throws ConfigInvalidException {
752 		expectedEx.expect(ConfigInvalidException.class);
753 		expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile);
754 		parse("[include]\nbar\n");
755 	}
756 
757 	@Test
758 	public void testIncludeNoValue() throws ConfigInvalidException {
759 		expectedEx.expect(ConfigInvalidException.class);
760 		expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile);
761 		parse("[include]\npath\n");
762 	}
763 
764 	@Test
765 	public void testIncludeEmptyValue() throws ConfigInvalidException {
766 		expectedEx.expect(ConfigInvalidException.class);
767 		expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile);
768 		parse("[include]\npath=\n");
769 	}
770 
771 	@Test
772 	public void testIncludeValuePathNotFound() throws ConfigInvalidException {
773 		// we do not expect an exception, included path not found are ignored
774 		String notFound = "/not/found";
775 		Config parsed = parse("[include]\npath=" + notFound + "\n");
776 		assertEquals(1, parsed.getSections().size());
777 		assertEquals(notFound, parsed.getString("include", null, "path"));
778 	}
779 
780 	@Test
781 	public void testIncludeValuePathWithTilde() throws ConfigInvalidException {
782 		// we do not expect an exception, included path not supported are
783 		// ignored
784 		String notSupported = "~/someFile";
785 		Config parsed = parse("[include]\npath=" + notSupported + "\n");
786 		assertEquals(1, parsed.getSections().size());
787 		assertEquals(notSupported, parsed.getString("include", null, "path"));
788 	}
789 
790 	@Test
791 	public void testIncludeValuePathRelative() throws ConfigInvalidException {
792 		// we do not expect an exception, included path not supported are
793 		// ignored
794 		String notSupported = "someRelativeFile";
795 		Config parsed = parse("[include]\npath=" + notSupported + "\n");
796 		assertEquals(1, parsed.getSections().size());
797 		assertEquals(notSupported, parsed.getString("include", null, "path"));
798 	}
799 
800 	@Test
801 	public void testIncludeTooManyRecursions() throws IOException {
802 		File config = tmp.newFile("config");
803 		String include = "[include]\npath=" + pathToString(config) + "\n";
804 		Files.write(config.toPath(), include.getBytes());
805 		FileBasedConfig fbConfig = new FileBasedConfig(null, config,
806 				FS.DETECTED);
807 		try {
808 			fbConfig.load();
809 			fail();
810 		} catch (ConfigInvalidException cie) {
811 			for (Throwable t = cie; t != null; t = t.getCause()) {
812 				if (t.getMessage()
813 						.equals(JGitText.get().tooManyIncludeRecursions)) {
814 					return;
815 				}
816 			}
817 			fail("Expected to find expected exception message: "
818 					+ JGitText.get().tooManyIncludeRecursions);
819 		}
820 	}
821 
822 	@Test
823 	public void testIncludeIsNoop() throws IOException, ConfigInvalidException {
824 		File config = tmp.newFile("config");
825 
826 		String fooBar = "[foo]\nbar=true\n";
827 		Files.write(config.toPath(), fooBar.getBytes());
828 
829 		Config parsed = parse("[include]\npath=" + pathToString(config) + "\n");
830 		assertFalse(parsed.getBoolean("foo", "bar", false));
831 	}
832 
833 	@Test
834 	public void testIncludeCaseInsensitiveSection()
835 			throws IOException, ConfigInvalidException {
836 		File included = tmp.newFile("included");
837 		String content = "[foo]\nbar=true\n";
838 		Files.write(included.toPath(), content.getBytes());
839 
840 		File config = tmp.newFile("config");
841 		content = "[Include]\npath=" + pathToString(included) + "\n";
842 		Files.write(config.toPath(), content.getBytes());
843 
844 		FileBasedConfig fbConfig = new FileBasedConfig(null, config,
845 				FS.DETECTED);
846 		fbConfig.load();
847 		assertTrue(fbConfig.getBoolean("foo", "bar", false));
848 	}
849 
850 	@Test
851 	public void testIncludeCaseInsensitiveKey()
852 			throws IOException, ConfigInvalidException {
853 		File included = tmp.newFile("included");
854 		String content = "[foo]\nbar=true\n";
855 		Files.write(included.toPath(), content.getBytes());
856 
857 		File config = tmp.newFile("config");
858 		content = "[include]\nPath=" + pathToString(included) + "\n";
859 		Files.write(config.toPath(), content.getBytes());
860 
861 		FileBasedConfig fbConfig = new FileBasedConfig(null, config,
862 				FS.DETECTED);
863 		fbConfig.load();
864 		assertTrue(fbConfig.getBoolean("foo", "bar", false));
865 	}
866 
867 	@Test
868 	public void testIncludeExceptionContainsLine() {
869 		try {
870 			parse("[include]\npath=\n");
871 			fail("Expected ConfigInvalidException");
872 		} catch (ConfigInvalidException e) {
873 			assertTrue(
874 					"Expected to find the problem line in the exception message",
875 					e.getMessage().contains("include.path"));
876 		}
877 	}
878 
879 	@Test
880 	public void testIncludeExceptionContainsFile() throws IOException {
881 		File included = tmp.newFile("included");
882 		String includedPath = pathToString(included);
883 		String content = "[include]\npath=\n";
884 		Files.write(included.toPath(), content.getBytes());
885 
886 		File config = tmp.newFile("config");
887 		String include = "[include]\npath=" + includedPath + "\n";
888 		Files.write(config.toPath(), include.getBytes());
889 		FileBasedConfig fbConfig = new FileBasedConfig(null, config,
890 				FS.DETECTED);
891 		try {
892 			fbConfig.load();
893 			fail("Expected ConfigInvalidException");
894 		} catch (ConfigInvalidException e) {
895 			// Check that there is some exception in the chain that contains
896 			// includedPath
897 			for (Throwable t = e; t != null; t = t.getCause()) {
898 				if (t.getMessage().contains(includedPath)) {
899 					return;
900 				}
901 			}
902 			fail("Expected to find the path in the exception message: "
903 					+ includedPath);
904 		}
905 	}
906 
907 	private static void assertReadLong(long exp) throws ConfigInvalidException {
908 		assertReadLong(exp, String.valueOf(exp));
909 	}
910 
911 	private static void assertReadLong(long exp, String act)
912 			throws ConfigInvalidException {
913 		final Config c = parse("[s]\na = " + act + "\n");
914 		assertEquals(exp, c.getLong("s", null, "a", 0L));
915 	}
916 
917 	private static Config parse(String content)
918 			throws ConfigInvalidException {
919 		return parse(content, null);
920 	}
921 
922 	private static Config parse(String content, Config baseConfig)
923 			throws ConfigInvalidException {
924 		final Config c = new Config(baseConfig);
925 		c.fromText(content);
926 		return c;
927 	}
928 
929 	@Test
930 	public void testTimeUnit() throws ConfigInvalidException {
931 		assertEquals(0, parseTime("0", MILLISECONDS));
932 		assertEquals(2, parseTime("2ms", MILLISECONDS));
933 		assertEquals(200, parseTime("200 milliseconds", MILLISECONDS));
934 
935 		assertEquals(0, parseTime("0s", SECONDS));
936 		assertEquals(2, parseTime("2s", SECONDS));
937 		assertEquals(231, parseTime("231sec", SECONDS));
938 		assertEquals(1, parseTime("1second", SECONDS));
939 		assertEquals(300, parseTime("300 seconds", SECONDS));
940 
941 		assertEquals(2, parseTime("2m", MINUTES));
942 		assertEquals(2, parseTime("2min", MINUTES));
943 		assertEquals(1, parseTime("1 minute", MINUTES));
944 		assertEquals(10, parseTime("10 minutes", MINUTES));
945 
946 		assertEquals(5, parseTime("5h", HOURS));
947 		assertEquals(5, parseTime("5hr", HOURS));
948 		assertEquals(1, parseTime("1hour", HOURS));
949 		assertEquals(48, parseTime("48hours", HOURS));
950 
951 		assertEquals(5, parseTime("5 h", HOURS));
952 		assertEquals(5, parseTime("5 hr", HOURS));
953 		assertEquals(1, parseTime("1 hour", HOURS));
954 		assertEquals(48, parseTime("48 hours", HOURS));
955 		assertEquals(48, parseTime("48 \t \r hours", HOURS));
956 
957 		assertEquals(4, parseTime("4d", DAYS));
958 		assertEquals(1, parseTime("1day", DAYS));
959 		assertEquals(14, parseTime("14days", DAYS));
960 
961 		assertEquals(7, parseTime("1w", DAYS));
962 		assertEquals(7, parseTime("1week", DAYS));
963 		assertEquals(14, parseTime("2w", DAYS));
964 		assertEquals(14, parseTime("2weeks", DAYS));
965 
966 		assertEquals(30, parseTime("1mon", DAYS));
967 		assertEquals(30, parseTime("1month", DAYS));
968 		assertEquals(60, parseTime("2mon", DAYS));
969 		assertEquals(60, parseTime("2months", DAYS));
970 
971 		assertEquals(365, parseTime("1y", DAYS));
972 		assertEquals(365, parseTime("1year", DAYS));
973 		assertEquals(365 * 2, parseTime("2years", DAYS));
974 	}
975 
976 	private long parseTime(String value, TimeUnit unit)
977 			throws ConfigInvalidException {
978 		Config c = parse("[a]\na=" + value + "\n");
979 		return c.getTimeUnit("a", null, "a", 0, unit);
980 	}
981 
982 	@Test
983 	public void testTimeUnitDefaultValue() throws ConfigInvalidException {
984 		// value not present
985 		assertEquals(20, parse("[a]\na=0\n").getTimeUnit("a", null, "b", 20,
986 				MILLISECONDS));
987 		// value is empty
988 		assertEquals(20, parse("[a]\na=\" \"\n").getTimeUnit("a", null, "a", 20,
989 				MILLISECONDS));
990 
991 		// value is not numeric
992 		assertEquals(20, parse("[a]\na=test\n").getTimeUnit("a", null, "a", 20,
993 				MILLISECONDS));
994 	}
995 
996 	@Test
997 	public void testTimeUnitInvalid() throws ConfigInvalidException {
998 		expectedEx.expect(IllegalArgumentException.class);
999 		expectedEx
1000 				.expectMessage("Invalid time unit value: a.a=1 monttthhh");
1001 		parseTime("1 monttthhh", DAYS);
1002 	}
1003 
1004 	@Test
1005 	public void testTimeUnitInvalidWithSection() throws ConfigInvalidException {
1006 		Config c = parse("[a \"b\"]\na=1 monttthhh\n");
1007 		expectedEx.expect(IllegalArgumentException.class);
1008 		expectedEx.expectMessage("Invalid time unit value: a.b.a=1 monttthhh");
1009 		c.getTimeUnit("a", "b", "a", 0, DAYS);
1010 	}
1011 
1012 	@Test
1013 	public void testTimeUnitNegative() throws ConfigInvalidException {
1014 		expectedEx.expect(IllegalArgumentException.class);
1015 		parseTime("-1", MILLISECONDS);
1016 	}
1017 
1018 	@Test
1019 	public void testEscapeSpacesOnly() throws ConfigInvalidException {
1020 		// Empty string is read back as null, so this doesn't round-trip.
1021 		assertEquals("", Config.escapeValue(""));
1022 
1023 		assertValueRoundTrip(" ", "\" \"");
1024 		assertValueRoundTrip("  ", "\"  \"");
1025 	}
1026 
1027 	@Test
1028 	public void testEscapeLeadingSpace() throws ConfigInvalidException {
1029 		assertValueRoundTrip("x", "x");
1030 		assertValueRoundTrip(" x", "\" x\"");
1031 		assertValueRoundTrip("  x", "\"  x\"");
1032 	}
1033 
1034 	@Test
1035 	public void testEscapeTrailingSpace() throws ConfigInvalidException {
1036 		assertValueRoundTrip("x", "x");
1037 		assertValueRoundTrip("x  ","\"x  \"");
1038 		assertValueRoundTrip("x ","\"x \"");
1039 	}
1040 
1041 	@Test
1042 	public void testEscapeLeadingAndTrailingSpace()
1043 			throws ConfigInvalidException {
1044 		assertValueRoundTrip(" x ", "\" x \"");
1045 		assertValueRoundTrip("  x ", "\"  x \"");
1046 		assertValueRoundTrip(" x  ", "\" x  \"");
1047 		assertValueRoundTrip("  x  ", "\"  x  \"");
1048 	}
1049 
1050 	@Test
1051 	public void testNoEscapeInternalSpaces() throws ConfigInvalidException {
1052 		assertValueRoundTrip("x y");
1053 		assertValueRoundTrip("x  y");
1054 		assertValueRoundTrip("x  y");
1055 		assertValueRoundTrip("x  y   z");
1056 		assertValueRoundTrip("x " + WS + " y");
1057 	}
1058 
1059 	@Test
1060 	public void testNoEscapeSpecialCharacters() throws ConfigInvalidException {
1061 		assertValueRoundTrip("x\\y", "x\\\\y");
1062 		assertValueRoundTrip("x\"y", "x\\\"y");
1063 		assertValueRoundTrip("x\ny", "x\\ny");
1064 		assertValueRoundTrip("x\ty", "x\\ty");
1065 		assertValueRoundTrip("x\by", "x\\by");
1066 	}
1067 
1068 	@Test
1069 	public void testParseLiteralBackspace() throws ConfigInvalidException {
1070 		// This is round-tripped with an escape sequence by JGit, but C git writes
1071 		// it out as a literal backslash.
1072 		assertEquals("x\by", parseEscapedValue("x\by"));
1073 	}
1074 
1075 	@Test
1076 	public void testEscapeCommentCharacters() throws ConfigInvalidException {
1077 		assertValueRoundTrip("x#y", "\"x#y\"");
1078 		assertValueRoundTrip("x;y", "\"x;y\"");
1079 	}
1080 
1081 	@Test
1082 	public void testEscapeValueInvalidCharacters() {
1083 		assertIllegalArgumentException(() -> Config.escapeSubsection("x\0y"));
1084 	}
1085 
1086 	@Test
1087 	public void testEscapeSubsectionInvalidCharacters() {
1088 		assertIllegalArgumentException(() -> Config.escapeSubsection("x\ny"));
1089 		assertIllegalArgumentException(() -> Config.escapeSubsection("x\0y"));
1090 	}
1091 
1092 	@Test
1093 	public void testParseMultipleQuotedRegions() throws ConfigInvalidException {
1094 		assertEquals("b a z; \n", parseEscapedValue("b\" a\"\" z; \\n\""));
1095 	}
1096 
1097 	@Test
1098 	public void testParseComments() throws ConfigInvalidException {
1099 		assertEquals("baz", parseEscapedValue("baz; comment"));
1100 		assertEquals("baz", parseEscapedValue("baz# comment"));
1101 		assertEquals("baz", parseEscapedValue("baz ; comment"));
1102 		assertEquals("baz", parseEscapedValue("baz # comment"));
1103 
1104 		assertEquals("baz", parseEscapedValue("baz ; comment"));
1105 		assertEquals("baz", parseEscapedValue("baz # comment"));
1106 		assertEquals("baz", parseEscapedValue("baz " + WS + " ; comment"));
1107 		assertEquals("baz", parseEscapedValue("baz " + WS + " # comment"));
1108 
1109 		assertEquals("baz ", parseEscapedValue("\"baz \"; comment"));
1110 		assertEquals("baz ", parseEscapedValue("\"baz \"# comment"));
1111 		assertEquals("baz ", parseEscapedValue("\"baz \" ; comment"));
1112 		assertEquals("baz ", parseEscapedValue("\"baz \" # comment"));
1113 	}
1114 
1115 	@Test
1116 	public void testEscapeSubsection() throws ConfigInvalidException {
1117 		assertSubsectionRoundTrip("", "\"\"");
1118 		assertSubsectionRoundTrip("x", "\"x\"");
1119 		assertSubsectionRoundTrip(" x", "\" x\"");
1120 		assertSubsectionRoundTrip("x ", "\"x \"");
1121 		assertSubsectionRoundTrip(" x ", "\" x \"");
1122 		assertSubsectionRoundTrip("x y", "\"x y\"");
1123 		assertSubsectionRoundTrip("x  y", "\"x  y\"");
1124 		assertSubsectionRoundTrip("x\\y", "\"x\\\\y\"");
1125 		assertSubsectionRoundTrip("x\"y", "\"x\\\"y\"");
1126 
1127 		// Unlike for values, \b and \t are not escaped.
1128 		assertSubsectionRoundTrip("x\by", "\"x\by\"");
1129 		assertSubsectionRoundTrip("x\ty", "\"x\ty\"");
1130 	}
1131 
1132 	@Test
1133 	public void testParseInvalidValues() {
1134 		assertInvalidValue(JGitText.get().newlineInQuotesNotAllowed, "x\"\n\"y");
1135 		assertInvalidValue(JGitText.get().endOfFileInEscape, "x\\");
1136 		assertInvalidValue(
1137 				MessageFormat.format(JGitText.get().badEscape, 'q'), "x\\q");
1138 	}
1139 
1140 	@Test
1141 	public void testParseInvalidSubsections() {
1142 		assertInvalidSubsection(
1143 				JGitText.get().newlineInQuotesNotAllowed, "\"x\ny\"");
1144 	}
1145 
1146 	@Test
1147 	public void testDropBackslashFromInvalidEscapeSequenceInSubsectionName()
1148 			throws ConfigInvalidException {
1149 		assertEquals("x0", parseEscapedSubsection("\"x\\0\""));
1150 		assertEquals("xq", parseEscapedSubsection("\"x\\q\""));
1151 		// Unlike for values, \b, \n, and \t are not valid escape sequences.
1152 		assertEquals("xb", parseEscapedSubsection("\"x\\b\""));
1153 		assertEquals("xn", parseEscapedSubsection("\"x\\n\""));
1154 		assertEquals("xt", parseEscapedSubsection("\"x\\t\""));
1155 	}
1156 
1157 	private static void assertValueRoundTrip(String value)
1158 			throws ConfigInvalidException {
1159 		assertValueRoundTrip(value, value);
1160 	}
1161 
1162 	private static void assertValueRoundTrip(String value, String expectedEscaped)
1163 			throws ConfigInvalidException {
1164 		String escaped = Config.escapeValue(value);
1165 		assertEquals("escape failed;", expectedEscaped, escaped);
1166 		assertEquals("parse failed;", value, parseEscapedValue(escaped));
1167 	}
1168 
1169 	private static String parseEscapedValue(String escapedValue)
1170 			throws ConfigInvalidException {
1171 		String text = "[foo]\nbar=" + escapedValue;
1172 		Config c = parse(text);
1173 		return c.getString("foo", null, "bar");
1174 	}
1175 
1176 	private static void assertInvalidValue(String expectedMessage,
1177 			String escapedValue) {
1178 		try {
1179 			parseEscapedValue(escapedValue);
1180 			fail("expected ConfigInvalidException");
1181 		} catch (ConfigInvalidException e) {
1182 			assertEquals(expectedMessage, e.getMessage());
1183 		}
1184 	}
1185 
1186 	private static void assertSubsectionRoundTrip(String subsection,
1187 			String expectedEscaped) throws ConfigInvalidException {
1188 		String escaped = Config.escapeSubsection(subsection);
1189 		assertEquals("escape failed;", expectedEscaped, escaped);
1190 		assertEquals("parse failed;", subsection, parseEscapedSubsection(escaped));
1191 	}
1192 
1193 	private static String parseEscapedSubsection(String escapedSubsection)
1194 			throws ConfigInvalidException {
1195 		String text = "[foo " + escapedSubsection + "]\nbar = value";
1196 		Config c = parse(text);
1197 		Set<String> subsections = c.getSubsections("foo");
1198 		assertEquals("only one section", 1, subsections.size());
1199 		return subsections.iterator().next();
1200 	}
1201 
1202 	private static void assertIllegalArgumentException(Runnable r) {
1203 		try {
1204 			r.run();
1205 			fail("expected IllegalArgumentException");
1206 		} catch (IllegalArgumentException e) {
1207 			// Expected.
1208 		}
1209 	}
1210 
1211 	private static void assertInvalidSubsection(String expectedMessage,
1212 			String escapedSubsection) {
1213 		try {
1214 			parseEscapedSubsection(escapedSubsection);
1215 			fail("expected ConfigInvalidException");
1216 		} catch (ConfigInvalidException e) {
1217 			assertEquals(expectedMessage, e.getMessage());
1218 		}
1219 	}
1220 }