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.nio.charset.StandardCharsets.UTF_8;
52  import static java.util.concurrent.TimeUnit.DAYS;
53  import static java.util.concurrent.TimeUnit.HOURS;
54  import static java.util.concurrent.TimeUnit.MICROSECONDS;
55  import static java.util.concurrent.TimeUnit.MILLISECONDS;
56  import static java.util.concurrent.TimeUnit.MINUTES;
57  import static java.util.concurrent.TimeUnit.NANOSECONDS;
58  import static java.util.concurrent.TimeUnit.SECONDS;
59  import static org.eclipse.jgit.util.FileUtils.pathToString;
60  import static org.junit.Assert.assertArrayEquals;
61  import static org.junit.Assert.assertEquals;
62  import static org.junit.Assert.assertFalse;
63  import static org.junit.Assert.assertNull;
64  import static org.junit.Assert.assertSame;
65  import static org.junit.Assert.assertTrue;
66  import static org.junit.Assert.fail;
67  
68  import java.io.File;
69  import java.io.IOException;
70  import java.nio.file.Files;
71  import java.text.MessageFormat;
72  import java.util.ArrayList;
73  import java.util.Arrays;
74  import java.util.Collections;
75  import java.util.Iterator;
76  import java.util.LinkedList;
77  import java.util.List;
78  import java.util.Set;
79  import java.util.concurrent.TimeUnit;
80  import java.util.function.Consumer;
81  
82  import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
83  import org.eclipse.jgit.errors.ConfigInvalidException;
84  import org.eclipse.jgit.internal.JGitText;
85  import org.eclipse.jgit.junit.MockSystemReader;
86  import org.eclipse.jgit.merge.MergeConfig;
87  import org.eclipse.jgit.storage.file.FileBasedConfig;
88  import org.eclipse.jgit.transport.RefSpec;
89  import org.eclipse.jgit.util.FS;
90  import org.eclipse.jgit.util.SystemReader;
91  import org.junit.After;
92  import org.junit.Rule;
93  import org.junit.Test;
94  import org.junit.rules.ExpectedException;
95  import org.junit.rules.TemporaryFolder;
96  
97  /**
98   * Test reading of git config
99   */
100 @SuppressWarnings("boxing")
101 public class ConfigTest {
102 	// A non-ASCII whitespace character: U+2002 EN QUAD.
103 	private static final char WS = '\u2002';
104 
105 	private static final String REFS_ORIGIN = "+refs/heads/*:refs/remotes/origin/*";
106 
107 	private static final String REFS_UPSTREAM = "+refs/heads/*:refs/remotes/upstream/*";
108 
109 	private static final String REFS_BACKUP = "+refs/heads/*:refs/remotes/backup/*";
110 
111 	@Rule
112 	public ExpectedException expectedEx = ExpectedException.none();
113 
114 	@Rule
115 	public TemporaryFolder tmp = new TemporaryFolder();
116 
117 	@After
118 	public void tearDown() {
119 		SystemReader.setInstance(null);
120 	}
121 
122 	@Test
123 	public void test001_ReadBareKey() throws ConfigInvalidException {
124 		final Config c = parse("[foo]\nbar\n");
125 		assertTrue(c.getBoolean("foo", null, "bar", false));
126 		assertEquals("", c.getString("foo", null, "bar"));
127 	}
128 
129 	@Test
130 	public void test002_ReadWithSubsection() throws ConfigInvalidException {
131 		final Config c = parse("[foo \"zip\"]\nbar\n[foo \"zap\"]\nbar=false\nn=3\n");
132 		assertTrue(c.getBoolean("foo", "zip", "bar", false));
133 		assertEquals("", c.getString("foo","zip", "bar"));
134 		assertFalse(c.getBoolean("foo", "zap", "bar", true));
135 		assertEquals("false", c.getString("foo", "zap", "bar"));
136 		assertEquals(3, c.getInt("foo", "zap", "n", 4));
137 		assertEquals(4, c.getInt("foo", "zap","m", 4));
138 	}
139 
140 	@Test
141 	public void test003_PutRemote() {
142 		final Config c = new Config();
143 		c.setString("sec", "ext", "name", "value");
144 		c.setString("sec", "ext", "name2", "value2");
145 		final String expText = "[sec \"ext\"]\n\tname = value\n\tname2 = value2\n";
146 		assertEquals(expText, c.toText());
147 	}
148 
149 	@Test
150 	public void test004_PutGetSimple() {
151 		Config c = new Config();
152 		c.setString("my", null, "somename", "false");
153 		assertEquals("false", c.getString("my", null, "somename"));
154 		assertEquals("[my]\n\tsomename = false\n", c.toText());
155 	}
156 
157 	@Test
158 	public void test005_PutGetStringList() {
159 		Config c = new Config();
160 		final LinkedList<String> values = new LinkedList<>();
161 		values.add("value1");
162 		values.add("value2");
163 		c.setStringList("my", null, "somename", values);
164 
165 		final Object[] expArr = values.toArray();
166 		final String[] actArr = c.getStringList("my", null, "somename");
167 		assertArrayEquals(expArr, actArr);
168 
169 		final String expText = "[my]\n\tsomename = value1\n\tsomename = value2\n";
170 		assertEquals(expText, c.toText());
171 	}
172 
173 	@Test
174 	public void test006_readCaseInsensitive() throws ConfigInvalidException {
175 		final Config c = parse("[Foo]\nBar\n");
176 		assertTrue(c.getBoolean("foo", null, "bar", false));
177 		assertEquals("", c.getString("foo", null, "bar"));
178 	}
179 
180 	@Test
181 	public void test007_readUserConfig() {
182 		final MockSystemReader mockSystemReader = new MockSystemReader();
183 		SystemReader.setInstance(mockSystemReader);
184 		final String hostname = mockSystemReader.getHostname();
185 		final Config userGitConfig = mockSystemReader.openUserConfig(null,
186 				FS.DETECTED);
187 		final Config localConfig = new Config(userGitConfig);
188 		mockSystemReader.clearProperties();
189 
190 		String authorName;
191 		String authorEmail;
192 
193 		// no values defined nowhere
194 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
195 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
196 		assertEquals(Constants.UNKNOWN_USER_DEFAULT, authorName);
197 		assertEquals(Constants.UNKNOWN_USER_DEFAULT + "@" + hostname, authorEmail);
198 		assertTrue(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
199 		assertTrue(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
200 
201 		// the system user name is defined
202 		mockSystemReader.setProperty(Constants.OS_USER_NAME_KEY, "os user name");
203 		localConfig.uncache(UserConfig.KEY);
204 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
205 		assertEquals("os user name", authorName);
206 		assertTrue(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
207 
208 		if (hostname != null && hostname.length() != 0) {
209 			authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
210 			assertEquals("os user name@" + hostname, authorEmail);
211 		}
212 		assertTrue(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
213 
214 		// the git environment variables are defined
215 		mockSystemReader.setProperty(Constants.GIT_AUTHOR_NAME_KEY, "git author name");
216 		mockSystemReader.setProperty(Constants.GIT_AUTHOR_EMAIL_KEY, "author@email");
217 		localConfig.uncache(UserConfig.KEY);
218 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
219 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
220 		assertEquals("git author name", authorName);
221 		assertEquals("author@email", authorEmail);
222 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
223 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
224 
225 		// the values are defined in the global configuration
226 		// first clear environment variables since they would override
227 		// configuration files
228 		mockSystemReader.clearProperties();
229 		userGitConfig.setString("user", null, "name", "global username");
230 		userGitConfig.setString("user", null, "email", "author@globalemail");
231 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
232 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
233 		assertEquals("global username", authorName);
234 		assertEquals("author@globalemail", authorEmail);
235 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
236 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
237 
238 		// the values are defined in the local configuration
239 		localConfig.setString("user", null, "name", "local username");
240 		localConfig.setString("user", null, "email", "author@localemail");
241 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
242 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
243 		assertEquals("local username", authorName);
244 		assertEquals("author@localemail", authorEmail);
245 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
246 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
247 
248 		authorName = localConfig.get(UserConfig.KEY).getCommitterName();
249 		authorEmail = localConfig.get(UserConfig.KEY).getCommitterEmail();
250 		assertEquals("local username", authorName);
251 		assertEquals("author@localemail", authorEmail);
252 		assertFalse(localConfig.get(UserConfig.KEY).isCommitterNameImplicit());
253 		assertFalse(localConfig.get(UserConfig.KEY).isCommitterEmailImplicit());
254 
255 		// also git environment variables are defined
256 		mockSystemReader.setProperty(Constants.GIT_AUTHOR_NAME_KEY,
257 				"git author name");
258 		mockSystemReader.setProperty(Constants.GIT_AUTHOR_EMAIL_KEY,
259 				"author@email");
260 		localConfig.setString("user", null, "name", "local username");
261 		localConfig.setString("user", null, "email", "author@localemail");
262 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
263 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
264 		assertEquals("git author name", authorName);
265 		assertEquals("author@email", authorEmail);
266 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
267 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
268 	}
269 
270 	@Test
271 	public void testReadUserConfigWithInvalidCharactersStripped() {
272 		final MockSystemReader mockSystemReader = new MockSystemReader();
273 		final Config localConfig = new Config(mockSystemReader.openUserConfig(
274 				null, FS.DETECTED));
275 
276 		localConfig.setString("user", null, "name", "foo<bar");
277 		localConfig.setString("user", null, "email", "baz>\nqux@example.com");
278 
279 		UserConfig userConfig = localConfig.get(UserConfig.KEY);
280 		assertEquals("foobar", userConfig.getAuthorName());
281 		assertEquals("bazqux@example.com", userConfig.getAuthorEmail());
282 	}
283 
284 	@Test
285 	public void testReadBoolean_TrueFalse1() throws ConfigInvalidException {
286 		final Config c = parse("[s]\na = true\nb = false\n");
287 		assertEquals("true", c.getString("s", null, "a"));
288 		assertEquals("false", c.getString("s", null, "b"));
289 
290 		assertTrue(c.getBoolean("s", "a", false));
291 		assertFalse(c.getBoolean("s", "b", true));
292 	}
293 
294 	@Test
295 	public void testReadBoolean_TrueFalse2() throws ConfigInvalidException {
296 		final Config c = parse("[s]\na = TrUe\nb = fAlSe\n");
297 		assertEquals("TrUe", c.getString("s", null, "a"));
298 		assertEquals("fAlSe", c.getString("s", null, "b"));
299 
300 		assertTrue(c.getBoolean("s", "a", false));
301 		assertFalse(c.getBoolean("s", "b", true));
302 	}
303 
304 	@Test
305 	public void testReadBoolean_YesNo1() throws ConfigInvalidException {
306 		final Config c = parse("[s]\na = yes\nb = no\n");
307 		assertEquals("yes", c.getString("s", null, "a"));
308 		assertEquals("no", c.getString("s", null, "b"));
309 
310 		assertTrue(c.getBoolean("s", "a", false));
311 		assertFalse(c.getBoolean("s", "b", true));
312 	}
313 
314 	@Test
315 	public void testReadBoolean_YesNo2() throws ConfigInvalidException {
316 		final Config c = parse("[s]\na = yEs\nb = NO\n");
317 		assertEquals("yEs", c.getString("s", null, "a"));
318 		assertEquals("NO", c.getString("s", null, "b"));
319 
320 		assertTrue(c.getBoolean("s", "a", false));
321 		assertFalse(c.getBoolean("s", "b", true));
322 	}
323 
324 	@Test
325 	public void testReadBoolean_OnOff1() throws ConfigInvalidException {
326 		final Config c = parse("[s]\na = on\nb = off\n");
327 		assertEquals("on", c.getString("s", null, "a"));
328 		assertEquals("off", c.getString("s", null, "b"));
329 
330 		assertTrue(c.getBoolean("s", "a", false));
331 		assertFalse(c.getBoolean("s", "b", true));
332 	}
333 
334 	@Test
335 	public void testReadBoolean_OnOff2() throws ConfigInvalidException {
336 		final Config c = parse("[s]\na = ON\nb = OFF\n");
337 		assertEquals("ON", c.getString("s", null, "a"));
338 		assertEquals("OFF", c.getString("s", null, "b"));
339 
340 		assertTrue(c.getBoolean("s", "a", false));
341 		assertFalse(c.getBoolean("s", "b", true));
342 	}
343 
344 	static enum TestEnum {
345 		ONE_TWO;
346 	}
347 
348 	@Test
349 	public void testGetEnum() throws ConfigInvalidException {
350 		Config c = parse("[s]\na = ON\nb = input\nc = true\nd = off\n");
351 		assertSame(CoreConfig.AutoCRLF.TRUE, c.getEnum("s", null, "a",
352 				CoreConfig.AutoCRLF.FALSE));
353 
354 		assertSame(CoreConfig.AutoCRLF.INPUT, c.getEnum("s", null, "b",
355 				CoreConfig.AutoCRLF.FALSE));
356 
357 		assertSame(CoreConfig.AutoCRLF.TRUE, c.getEnum("s", null, "c",
358 				CoreConfig.AutoCRLF.FALSE));
359 
360 		assertSame(CoreConfig.AutoCRLF.FALSE, c.getEnum("s", null, "d",
361 				CoreConfig.AutoCRLF.TRUE));
362 
363 		c = new Config();
364 		assertSame(CoreConfig.AutoCRLF.FALSE, c.getEnum("s", null, "d",
365 				CoreConfig.AutoCRLF.FALSE));
366 
367 		c = parse("[s \"b\"]\n\tc = one two\n");
368 		assertSame(TestEnum.ONE_TWO, c.getEnum("s", "b", "c", TestEnum.ONE_TWO));
369 
370 		c = parse("[s \"b\"]\n\tc = one-two\n");
371 		assertSame(TestEnum.ONE_TWO, c.getEnum("s", "b", "c", TestEnum.ONE_TWO));
372 	}
373 
374 	@Test
375 	public void testGetInvalidEnum() throws ConfigInvalidException {
376 		Config c = parse("[a]\n\tb = invalid\n");
377 		try {
378 			c.getEnum("a", null, "b", TestEnum.ONE_TWO);
379 			fail();
380 		} catch (IllegalArgumentException e) {
381 			assertEquals("Invalid value: a.b=invalid", e.getMessage());
382 		}
383 
384 		c = parse("[a \"b\"]\n\tc = invalid\n");
385 		try {
386 			c.getEnum("a", "b", "c", TestEnum.ONE_TWO);
387 			fail();
388 		} catch (IllegalArgumentException e) {
389 			assertEquals("Invalid value: a.b.c=invalid", e.getMessage());
390 		}
391 	}
392 
393 	@Test
394 	public void testSetEnum() {
395 		final Config c = new Config();
396 		c.setEnum("s", "b", "c", TestEnum.ONE_TWO);
397 		assertEquals("[s \"b\"]\n\tc = one two\n", c.toText());
398 	}
399 
400 	@Test
401 	public void testGetFastForwardMergeoptions() throws ConfigInvalidException {
402 		Config c = new Config(null); // not set
403 		assertSame(FastForwardMode.FF, c.getEnum(
404 				ConfigConstants.CONFIG_BRANCH_SECTION, "side",
405 				ConfigConstants.CONFIG_KEY_MERGEOPTIONS, FastForwardMode.FF));
406 		MergeConfig mergeConfig = c.get(MergeConfig.getParser("side"));
407 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
408 		c = parse("[branch \"side\"]\n\tmergeoptions = --ff-only\n");
409 		assertSame(FastForwardMode.FF_ONLY, c.getEnum(
410 				ConfigConstants.CONFIG_BRANCH_SECTION, "side",
411 				ConfigConstants.CONFIG_KEY_MERGEOPTIONS,
412 				FastForwardMode.FF_ONLY));
413 		mergeConfig = c.get(MergeConfig.getParser("side"));
414 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
415 		c = parse("[branch \"side\"]\n\tmergeoptions = --ff\n");
416 		assertSame(FastForwardMode.FF, c.getEnum(
417 				ConfigConstants.CONFIG_BRANCH_SECTION, "side",
418 				ConfigConstants.CONFIG_KEY_MERGEOPTIONS, FastForwardMode.FF));
419 		mergeConfig = c.get(MergeConfig.getParser("side"));
420 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
421 		c = parse("[branch \"side\"]\n\tmergeoptions = --no-ff\n");
422 		assertSame(FastForwardMode.NO_FF, c.getEnum(
423 				ConfigConstants.CONFIG_BRANCH_SECTION, "side",
424 				ConfigConstants.CONFIG_KEY_MERGEOPTIONS, FastForwardMode.NO_FF));
425 		mergeConfig = c.get(MergeConfig.getParser("side"));
426 		assertSame(FastForwardMode.NO_FF, mergeConfig.getFastForwardMode());
427 	}
428 
429 	@Test
430 	public void testSetFastForwardMergeoptions() {
431 		final Config c = new Config();
432 		c.setEnum("branch", "side", "mergeoptions", FastForwardMode.FF);
433 		assertEquals("[branch \"side\"]\n\tmergeoptions = --ff\n", c.toText());
434 		c.setEnum("branch", "side", "mergeoptions", FastForwardMode.FF_ONLY);
435 		assertEquals("[branch \"side\"]\n\tmergeoptions = --ff-only\n",
436 				c.toText());
437 		c.setEnum("branch", "side", "mergeoptions", FastForwardMode.NO_FF);
438 		assertEquals("[branch \"side\"]\n\tmergeoptions = --no-ff\n",
439 				c.toText());
440 	}
441 
442 	@Test
443 	public void testGetFastForwardMerge() throws ConfigInvalidException {
444 		Config c = new Config(null); // not set
445 		assertSame(FastForwardMode.Merge.TRUE, c.getEnum(
446 				ConfigConstants.CONFIG_KEY_MERGE, null,
447 				ConfigConstants.CONFIG_KEY_FF, FastForwardMode.Merge.TRUE));
448 		MergeConfig mergeConfig = c.get(MergeConfig.getParser("side"));
449 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
450 		c = parse("[merge]\n\tff = only\n");
451 		assertSame(FastForwardMode.Merge.ONLY, c.getEnum(
452 				ConfigConstants.CONFIG_KEY_MERGE, null,
453 				ConfigConstants.CONFIG_KEY_FF, FastForwardMode.Merge.ONLY));
454 		mergeConfig = c.get(MergeConfig.getParser("side"));
455 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
456 		c = parse("[merge]\n\tff = true\n");
457 		assertSame(FastForwardMode.Merge.TRUE, c.getEnum(
458 				ConfigConstants.CONFIG_KEY_MERGE, null,
459 				ConfigConstants.CONFIG_KEY_FF, FastForwardMode.Merge.TRUE));
460 		mergeConfig = c.get(MergeConfig.getParser("side"));
461 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
462 		c = parse("[merge]\n\tff = false\n");
463 		assertSame(FastForwardMode.Merge.FALSE, c.getEnum(
464 				ConfigConstants.CONFIG_KEY_MERGE, null,
465 				ConfigConstants.CONFIG_KEY_FF, FastForwardMode.Merge.FALSE));
466 		mergeConfig = c.get(MergeConfig.getParser("side"));
467 		assertSame(FastForwardMode.NO_FF, mergeConfig.getFastForwardMode());
468 	}
469 
470 	@Test
471 	public void testCombinedMergeOptions() throws ConfigInvalidException {
472 		Config c = new Config(null); // not set
473 		MergeConfig mergeConfig = c.get(MergeConfig.getParser("side"));
474 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
475 		assertTrue(mergeConfig.isCommit());
476 		assertFalse(mergeConfig.isSquash());
477 		// branch..mergeoptions should win over merge.ff
478 		c = parse("[merge]\n\tff = false\n"
479 				+ "[branch \"side\"]\n\tmergeoptions = --ff-only\n");
480 		mergeConfig = c.get(MergeConfig.getParser("side"));
481 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
482 		assertTrue(mergeConfig.isCommit());
483 		assertFalse(mergeConfig.isSquash());
484 		// merge.ff used for ff setting if not set via mergeoptions
485 		c = parse("[merge]\n\tff = only\n"
486 				+ "[branch \"side\"]\n\tmergeoptions = --squash\n");
487 		mergeConfig = c.get(MergeConfig.getParser("side"));
488 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
489 		assertTrue(mergeConfig.isCommit());
490 		assertTrue(mergeConfig.isSquash());
491 		// mergeoptions wins if it has ff options amongst other options
492 		c = parse("[merge]\n\tff = false\n"
493 				+ "[branch \"side\"]\n\tmergeoptions = --ff-only --no-commit\n");
494 		mergeConfig = c.get(MergeConfig.getParser("side"));
495 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
496 		assertFalse(mergeConfig.isCommit());
497 		assertFalse(mergeConfig.isSquash());
498 	}
499 
500 	@Test
501 	public void testSetFastForwardMerge() {
502 		final Config c = new Config();
503 		c.setEnum("merge", null, "ff",
504 				FastForwardMode.Merge.valueOf(FastForwardMode.FF));
505 		assertEquals("[merge]\n\tff = true\n", c.toText());
506 		c.setEnum("merge", null, "ff",
507 				FastForwardMode.Merge.valueOf(FastForwardMode.FF_ONLY));
508 		assertEquals("[merge]\n\tff = only\n", c.toText());
509 		c.setEnum("merge", null, "ff",
510 				FastForwardMode.Merge.valueOf(FastForwardMode.NO_FF));
511 		assertEquals("[merge]\n\tff = false\n", c.toText());
512 	}
513 
514 	@Test
515 	public void testReadLong() throws ConfigInvalidException {
516 		assertReadLong(1L);
517 		assertReadLong(-1L);
518 		assertReadLong(Long.MIN_VALUE);
519 		assertReadLong(Long.MAX_VALUE);
520 		assertReadLong(4L * 1024 * 1024 * 1024, "4g");
521 		assertReadLong(3L * 1024 * 1024, "3 m");
522 		assertReadLong(8L * 1024, "8 k");
523 
524 		try {
525 			assertReadLong(-1, "1.5g");
526 			fail("incorrectly accepted 1.5g");
527 		} catch (IllegalArgumentException e) {
528 			assertEquals("Invalid integer value: s.a=1.5g", e.getMessage());
529 		}
530 	}
531 
532 	@Test
533 	public void testBooleanWithNoValue() throws ConfigInvalidException {
534 		Config c = parse("[my]\n\tempty\n");
535 		assertEquals("", c.getString("my", null, "empty"));
536 		assertEquals(1, c.getStringList("my", null, "empty").length);
537 		assertEquals("", c.getStringList("my", null, "empty")[0]);
538 		assertTrue(c.getBoolean("my", "empty", false));
539 		assertEquals("[my]\n\tempty\n", c.toText());
540 	}
541 
542 	@Test
543 	public void testUnsetBranchSection() throws ConfigInvalidException {
544 		Config c = parse("" //
545 				+ "[branch \"keep\"]\n"
546 				+ "  merge = master.branch.to.keep.in.the.file\n"
547 				+ "\n"
548 				+ "[branch \"remove\"]\n"
549 				+ "  merge = this.will.get.deleted\n"
550 				+ "  remote = origin-for-some-long-gone-place\n"
551 				+ "\n"
552 				+ "[core-section-not-to-remove-in-test]\n"
553 				+ "  packedGitLimit = 14\n");
554 		c.unsetSection("branch", "does.not.exist");
555 		c.unsetSection("branch", "remove");
556 		assertEquals("" //
557 				+ "[branch \"keep\"]\n"
558 				+ "  merge = master.branch.to.keep.in.the.file\n"
559 				+ "\n"
560 				+ "[core-section-not-to-remove-in-test]\n"
561 				+ "  packedGitLimit = 14\n", c.toText());
562 	}
563 
564 	@Test
565 	public void testUnsetSingleSection() throws ConfigInvalidException {
566 		Config c = parse("" //
567 				+ "[branch \"keep\"]\n"
568 				+ "  merge = master.branch.to.keep.in.the.file\n"
569 				+ "\n"
570 				+ "[single]\n"
571 				+ "  merge = this.will.get.deleted\n"
572 				+ "  remote = origin-for-some-long-gone-place\n"
573 				+ "\n"
574 				+ "[core-section-not-to-remove-in-test]\n"
575 				+ "  packedGitLimit = 14\n");
576 		c.unsetSection("single", null);
577 		assertEquals("" //
578 				+ "[branch \"keep\"]\n"
579 				+ "  merge = master.branch.to.keep.in.the.file\n"
580 				+ "\n"
581 				+ "[core-section-not-to-remove-in-test]\n"
582 				+ "  packedGitLimit = 14\n", c.toText());
583 	}
584 
585 	@Test
586 	public void test008_readSectionNames() throws ConfigInvalidException {
587 		final Config c = parse("[a]\n [B]\n");
588 		Set<String> sections = c.getSections();
589 		assertTrue("Sections should contain \"a\"", sections.contains("a"));
590 		assertTrue("Sections should contain \"b\"", sections.contains("b"));
591 	}
592 
593 	@Test
594 	public void test009_readNamesInSection() throws ConfigInvalidException {
595 		String configString = "[core]\n" + "repositoryFormatVersion = 0\n"
596 				+ "filemode = false\n" + "logAllRefUpdates = true\n";
597 		final Config c = parse(configString);
598 		Set<String> names = c.getNames("core");
599 		assertEquals("Core section size", 3, names.size());
600 		assertTrue("Core section should contain \"filemode\"", names
601 				.contains("filemode"));
602 
603 		assertTrue("Core section should contain \"repositoryFormatVersion\"",
604 				names.contains("repositoryFormatVersion"));
605 
606 		assertTrue("Core section should contain \"repositoryformatversion\"",
607 				names.contains("repositoryformatversion"));
608 
609 		Iterator<String> itr = names.iterator();
610 		assertEquals("filemode", itr.next());
611 		assertEquals("logAllRefUpdates", itr.next());
612 		assertEquals("repositoryFormatVersion", itr.next());
613 		assertFalse(itr.hasNext());
614 	}
615 
616 	@Test
617 	public void test_ReadNamesInSectionRecursive()
618 			throws ConfigInvalidException {
619 		String baseConfigString = "[core]\n" + "logAllRefUpdates = true\n";
620 		String configString = "[core]\n" + "repositoryFormatVersion = 0\n"
621 				+ "filemode = false\n";
622 		final Config c = parse(configString, parse(baseConfigString));
623 		Set<String> names = c.getNames("core", true);
624 		assertEquals("Core section size", 3, names.size());
625 		assertTrue("Core section should contain \"filemode\"",
626 				names.contains("filemode"));
627 		assertTrue("Core section should contain \"repositoryFormatVersion\"",
628 				names.contains("repositoryFormatVersion"));
629 		assertTrue("Core section should contain \"logAllRefUpdates\"",
630 				names.contains("logAllRefUpdates"));
631 		assertTrue("Core section should contain \"logallrefupdates\"",
632 				names.contains("logallrefupdates"));
633 
634 		Iterator<String> itr = names.iterator();
635 		assertEquals("filemode", itr.next());
636 		assertEquals("repositoryFormatVersion", itr.next());
637 		assertEquals("logAllRefUpdates", itr.next());
638 		assertFalse(itr.hasNext());
639 	}
640 
641 	@Test
642 	public void test010_readNamesInSubSection() throws ConfigInvalidException {
643 		String configString = "[a \"sub1\"]\n"//
644 				+ "x = 0\n" //
645 				+ "y = false\n"//
646 				+ "z = true\n"//
647 				+ "[a \"sub2\"]\n"//
648 				+ "a=0\n"//
649 				+ "b=1\n";
650 		final Config c = parse(configString);
651 		Set<String> names = c.getNames("a", "sub1");
652 		assertEquals("Subsection size", 3, names.size());
653 		assertTrue("Subsection should contain \"x\"", names.contains("x"));
654 		assertTrue("Subsection should contain \"y\"", names.contains("y"));
655 		assertTrue("Subsection should contain \"z\"", names.contains("z"));
656 		names = c.getNames("a", "sub2");
657 		assertEquals("Subsection size", 2, names.size());
658 		assertTrue("Subsection should contain \"a\"", names.contains("a"));
659 		assertTrue("Subsection should contain \"b\"", names.contains("b"));
660 	}
661 
662 	@Test
663 	public void readNamesInSubSectionRecursive() throws ConfigInvalidException {
664 		String baseConfigString = "[a \"sub1\"]\n"//
665 				+ "x = 0\n" //
666 				+ "y = false\n"//
667 				+ "[a \"sub2\"]\n"//
668 				+ "A=0\n";//
669 		String configString = "[a \"sub1\"]\n"//
670 				+ "z = true\n"//
671 				+ "[a \"sub2\"]\n"//
672 				+ "B=1\n";
673 		final Config c = parse(configString, parse(baseConfigString));
674 		Set<String> names = c.getNames("a", "sub1", true);
675 		assertEquals("Subsection size", 3, names.size());
676 		assertTrue("Subsection should contain \"x\"", names.contains("x"));
677 		assertTrue("Subsection should contain \"y\"", names.contains("y"));
678 		assertTrue("Subsection should contain \"z\"", names.contains("z"));
679 		names = c.getNames("a", "sub2", true);
680 		assertEquals("Subsection size", 2, names.size());
681 		assertTrue("Subsection should contain \"A\"", names.contains("A"));
682 		assertTrue("Subsection should contain \"a\"", names.contains("a"));
683 		assertTrue("Subsection should contain \"B\"", names.contains("B"));
684 	}
685 
686 
687 	@Test
688 	public void testNoFinalNewline() throws ConfigInvalidException {
689 		Config c = parse("[a]\n"
690 				+ "x = 0\n"
691 				+ "y = 1");
692 		assertEquals("0", c.getString("a", null, "x"));
693 		assertEquals("1", c.getString("a", null, "y"));
694 	}
695 
696 	@Test
697 	public void testExplicitlySetEmptyString() throws Exception {
698 		Config c = new Config();
699 		c.setString("a", null, "x", "0");
700 		c.setString("a", null, "y", "");
701 
702 		assertEquals("0", c.getString("a", null, "x"));
703 		assertEquals(0, c.getInt("a", null, "x", 1));
704 
705 		assertEquals("", c.getString("a", null, "y"));
706 		assertArrayEquals(new String[]{""}, c.getStringList("a", null, "y"));
707 		assertEquals(1, c.getInt("a", null, "y", 1));
708 
709 		assertNull(c.getString("a", null, "z"));
710 		assertArrayEquals(new String[]{}, c.getStringList("a", null, "z"));
711 	}
712 
713 	@Test
714 	public void testParsedEmptyString() throws Exception {
715 		Config c = parse("[a]\n"
716 				+ "x = 0\n"
717 				+ "y =\n");
718 
719 		assertEquals("0", c.getString("a", null, "x"));
720 		assertEquals(0, c.getInt("a", null, "x", 1));
721 
722 		assertNull(c.getString("a", null, "y"));
723 		assertArrayEquals(new String[]{null}, c.getStringList("a", null, "y"));
724 		assertEquals(1, c.getInt("a", null, "y", 1));
725 
726 		assertNull(c.getString("a", null, "z"));
727 		assertArrayEquals(new String[]{}, c.getStringList("a", null, "z"));
728 	}
729 
730 	@Test
731 	public void testSetStringListWithEmptyValue() throws Exception {
732 		Config c = new Config();
733 		c.setStringList("a", null, "x", Arrays.asList(""));
734 		assertArrayEquals(new String[]{""}, c.getStringList("a", null, "x"));
735 	}
736 
737 	@Test
738 	public void testEmptyValueAtEof() throws Exception {
739 		String text = "[a]\nx =";
740 		Config c = parse(text);
741 		assertNull(c.getString("a", null, "x"));
742 		assertArrayEquals(new String[]{null},
743 				c.getStringList("a", null, "x"));
744 		c = parse(text + "\n");
745 		assertNull(c.getString("a", null, "x"));
746 		assertArrayEquals(new String[]{null},
747 				c.getStringList("a", null, "x"));
748 	}
749 
750 	@Test
751 	public void testReadMultipleValuesForName() throws ConfigInvalidException {
752 		Config c = parse("[foo]\nbar=false\nbar=true\n");
753 		assertTrue(c.getBoolean("foo", "bar", false));
754 	}
755 
756 	@Test
757 	public void testIncludeInvalidName() throws ConfigInvalidException {
758 		expectedEx.expect(ConfigInvalidException.class);
759 		expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile);
760 		parse("[include]\nbar\n");
761 	}
762 
763 	@Test
764 	public void testIncludeNoValue() throws ConfigInvalidException {
765 		expectedEx.expect(ConfigInvalidException.class);
766 		expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile);
767 		parse("[include]\npath\n");
768 	}
769 
770 	@Test
771 	public void testIncludeEmptyValue() throws ConfigInvalidException {
772 		expectedEx.expect(ConfigInvalidException.class);
773 		expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile);
774 		parse("[include]\npath=\n");
775 	}
776 
777 	@Test
778 	public void testIncludeValuePathNotFound() throws ConfigInvalidException {
779 		// we do not expect an exception, included path not found are ignored
780 		String notFound = "/not/found";
781 		Config parsed = parse("[include]\npath=" + notFound + "\n");
782 		assertEquals(1, parsed.getSections().size());
783 		assertEquals(notFound, parsed.getString("include", null, "path"));
784 	}
785 
786 	@Test
787 	public void testIncludeValuePathWithTilde() throws ConfigInvalidException {
788 		// we do not expect an exception, included path not supported are
789 		// ignored
790 		String notSupported = "~/someFile";
791 		Config parsed = parse("[include]\npath=" + notSupported + "\n");
792 		assertEquals(1, parsed.getSections().size());
793 		assertEquals(notSupported, parsed.getString("include", null, "path"));
794 	}
795 
796 	@Test
797 	public void testIncludeValuePathRelative() throws ConfigInvalidException {
798 		// we do not expect an exception, included path not supported are
799 		// ignored
800 		String notSupported = "someRelativeFile";
801 		Config parsed = parse("[include]\npath=" + notSupported + "\n");
802 		assertEquals(1, parsed.getSections().size());
803 		assertEquals(notSupported, parsed.getString("include", null, "path"));
804 	}
805 
806 	@Test
807 	public void testIncludeTooManyRecursions() throws IOException {
808 		File config = tmp.newFile("config");
809 		String include = "[include]\npath=" + pathToString(config) + "\n";
810 		Files.write(config.toPath(), include.getBytes(UTF_8));
811 		try {
812 			loadConfig(config);
813 			fail();
814 		} catch (ConfigInvalidException cie) {
815 			for (Throwable t = cie; t != null; t = t.getCause()) {
816 				if (t.getMessage()
817 						.equals(JGitText.get().tooManyIncludeRecursions)) {
818 					return;
819 				}
820 			}
821 			fail("Expected to find expected exception message: "
822 					+ JGitText.get().tooManyIncludeRecursions);
823 		}
824 	}
825 
826 	@Test
827 	public void testIncludeIsNoop() throws IOException, ConfigInvalidException {
828 		File config = tmp.newFile("config");
829 
830 		String fooBar = "[foo]\nbar=true\n";
831 		Files.write(config.toPath(), fooBar.getBytes(UTF_8));
832 
833 		Config parsed = parse("[include]\npath=" + pathToString(config) + "\n");
834 		assertFalse(parsed.getBoolean("foo", "bar", false));
835 	}
836 
837 	@Test
838 	public void testIncludeCaseInsensitiveSection()
839 			throws IOException, ConfigInvalidException {
840 		File included = tmp.newFile("included");
841 		String content = "[foo]\nbar=true\n";
842 		Files.write(included.toPath(), content.getBytes(UTF_8));
843 
844 		File config = tmp.newFile("config");
845 		content = "[Include]\npath=" + pathToString(included) + "\n";
846 		Files.write(config.toPath(), content.getBytes(UTF_8));
847 
848 		FileBasedConfig fbConfig = loadConfig(config);
849 		assertTrue(fbConfig.getBoolean("foo", "bar", false));
850 	}
851 
852 	@Test
853 	public void testIncludeCaseInsensitiveKey()
854 			throws IOException, ConfigInvalidException {
855 		File included = tmp.newFile("included");
856 		String content = "[foo]\nbar=true\n";
857 		Files.write(included.toPath(), content.getBytes(UTF_8));
858 
859 		File config = tmp.newFile("config");
860 		content = "[include]\nPath=" + pathToString(included) + "\n";
861 		Files.write(config.toPath(), content.getBytes(UTF_8));
862 
863 		FileBasedConfig fbConfig = loadConfig(config);
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(UTF_8));
885 
886 		File config = tmp.newFile("config");
887 		String include = "[include]\npath=" + includedPath + "\n";
888 		Files.write(config.toPath(), include.getBytes(UTF_8));
889 		try {
890 			loadConfig(config);
891 			fail("Expected ConfigInvalidException");
892 		} catch (ConfigInvalidException e) {
893 			// Check that there is some exception in the chain that contains
894 			// includedPath
895 			for (Throwable t = e; t != null; t = t.getCause()) {
896 				if (t.getMessage().contains(includedPath)) {
897 					return;
898 				}
899 			}
900 			fail("Expected to find the path in the exception message: "
901 					+ includedPath);
902 		}
903 	}
904 
905 	@Test
906 	public void testIncludeSetValueMustNotTouchIncludedLines1()
907 			throws IOException, ConfigInvalidException {
908 		File includedFile = createAllTypesIncludedContent();
909 
910 		File configFile = tmp.newFile("config");
911 		String content = createAllTypesSampleContent("Alice Parker", false, 11,
912 				21, 31, CoreConfig.AutoCRLF.FALSE,
913 				"+refs/heads/*:refs/remotes/origin/*") + "\n[include]\npath="
914 				+ pathToString(includedFile);
915 		Files.write(configFile.toPath(), content.getBytes(UTF_8));
916 
917 		FileBasedConfig fbConfig = loadConfig(configFile);
918 		assertValuesAsIncluded(fbConfig, REFS_ORIGIN, REFS_UPSTREAM);
919 		assertSections(fbConfig, "user", "core", "remote", "include");
920 
921 		setAllValuesNew(fbConfig);
922 		assertValuesAsIsSaveLoad(fbConfig, config -> {
923 			assertValuesAsIncluded(config, REFS_BACKUP, REFS_UPSTREAM);
924 			assertSections(fbConfig, "user", "core", "remote", "include");
925 		});
926 	}
927 
928 	@Test
929 	public void testIncludeSetValueMustNotTouchIncludedLines2()
930 			throws IOException, ConfigInvalidException {
931 		File includedFile = createAllTypesIncludedContent();
932 
933 		File configFile = tmp.newFile("config");
934 		String content = "[include]\npath=" + pathToString(includedFile) + "\n"
935 				+ createAllTypesSampleContent("Alice Parker", false, 11, 21, 31,
936 						CoreConfig.AutoCRLF.FALSE,
937 						"+refs/heads/*:refs/remotes/origin/*");
938 		Files.write(configFile.toPath(), content.getBytes(UTF_8));
939 
940 		FileBasedConfig fbConfig = loadConfig(configFile);
941 		assertValuesAsConfig(fbConfig, REFS_UPSTREAM, REFS_ORIGIN);
942 		assertSections(fbConfig, "include", "user", "core", "remote");
943 
944 		setAllValuesNew(fbConfig);
945 		assertValuesAsIsSaveLoad(fbConfig, config -> {
946 			assertValuesAsNew(config, REFS_UPSTREAM, REFS_BACKUP);
947 			assertSections(fbConfig, "include", "user", "core", "remote");
948 		});
949 	}
950 
951 	@Test
952 	public void testIncludeSetValueOnFileWithJustContainsInclude()
953 			throws IOException, ConfigInvalidException {
954 		File includedFile = createAllTypesIncludedContent();
955 
956 		File configFile = tmp.newFile("config");
957 		String content = "[include]\npath=" + pathToString(includedFile);
958 		Files.write(configFile.toPath(), content.getBytes(UTF_8));
959 
960 		FileBasedConfig fbConfig = loadConfig(configFile);
961 		assertValuesAsIncluded(fbConfig, REFS_UPSTREAM);
962 		assertSections(fbConfig, "include", "user", "core", "remote");
963 
964 		setAllValuesNew(fbConfig);
965 		assertValuesAsIsSaveLoad(fbConfig, config -> {
966 			assertValuesAsNew(config, REFS_UPSTREAM, REFS_BACKUP);
967 			assertSections(fbConfig, "include", "user", "core", "remote");
968 		});
969 	}
970 
971 	@Test
972 	public void testIncludeSetValueOnFileWithJustEmptySection1()
973 			throws IOException, ConfigInvalidException {
974 		File includedFile = createAllTypesIncludedContent();
975 
976 		File configFile = tmp.newFile("config");
977 		String content = "[user]\n[include]\npath="
978 				+ pathToString(includedFile);
979 		Files.write(configFile.toPath(), content.getBytes(UTF_8));
980 
981 		FileBasedConfig fbConfig = loadConfig(configFile);
982 		assertValuesAsIncluded(fbConfig, REFS_UPSTREAM);
983 		assertSections(fbConfig, "user", "include", "core", "remote");
984 
985 		setAllValuesNew(fbConfig);
986 		assertValuesAsIsSaveLoad(fbConfig, config -> {
987 			assertValuesAsNewWithName(config, "Alice Muller", REFS_UPSTREAM,
988 					REFS_BACKUP);
989 			assertSections(fbConfig, "user", "include", "core", "remote");
990 		});
991 	}
992 
993 	@Test
994 	public void testIncludeSetValueOnFileWithJustEmptySection2()
995 			throws IOException, ConfigInvalidException {
996 		File includedFile = createAllTypesIncludedContent();
997 
998 		File configFile = tmp.newFile("config");
999 		String content = "[include]\npath=" + pathToString(includedFile)
1000 				+ "\n[user]";
1001 		Files.write(configFile.toPath(), content.getBytes(UTF_8));
1002 
1003 		FileBasedConfig fbConfig = loadConfig(configFile);
1004 		assertValuesAsIncluded(fbConfig, REFS_UPSTREAM);
1005 		assertSections(fbConfig, "include", "user", "core", "remote");
1006 
1007 		setAllValuesNew(fbConfig);
1008 		assertValuesAsIsSaveLoad(fbConfig, config -> {
1009 			assertValuesAsNew(config, REFS_UPSTREAM, REFS_BACKUP);
1010 			assertSections(fbConfig, "include", "user", "core", "remote");
1011 		});
1012 	}
1013 
1014 	@Test
1015 	public void testIncludeSetValueOnFileWithJustExistingSection1()
1016 			throws IOException, ConfigInvalidException {
1017 		File includedFile = createAllTypesIncludedContent();
1018 
1019 		File configFile = tmp.newFile("config");
1020 		String content = "[user]\nemail=alice@home\n[include]\npath="
1021 				+ pathToString(includedFile);
1022 		Files.write(configFile.toPath(), content.getBytes(UTF_8));
1023 
1024 		FileBasedConfig fbConfig = loadConfig(configFile);
1025 		assertValuesAsIncluded(fbConfig, REFS_UPSTREAM);
1026 		assertSections(fbConfig, "user", "include", "core", "remote");
1027 
1028 		setAllValuesNew(fbConfig);
1029 		assertValuesAsIsSaveLoad(fbConfig, config -> {
1030 			assertValuesAsNewWithName(config, "Alice Muller", REFS_UPSTREAM,
1031 					REFS_BACKUP);
1032 			assertSections(fbConfig, "user", "include", "core", "remote");
1033 		});
1034 	}
1035 
1036 	@Test
1037 	public void testIncludeSetValueOnFileWithJustExistingSection2()
1038 			throws IOException, ConfigInvalidException {
1039 		File includedFile = createAllTypesIncludedContent();
1040 
1041 		File configFile = tmp.newFile("config");
1042 		String content = "[include]\npath=" + pathToString(includedFile)
1043 				+ "\n[user]\nemail=alice@home\n";
1044 		Files.write(configFile.toPath(), content.getBytes(UTF_8));
1045 
1046 		FileBasedConfig fbConfig = loadConfig(configFile);
1047 		assertValuesAsIncluded(fbConfig, REFS_UPSTREAM);
1048 		assertSections(fbConfig, "include", "user", "core", "remote");
1049 
1050 		setAllValuesNew(fbConfig);
1051 		assertValuesAsIsSaveLoad(fbConfig, config -> {
1052 			assertValuesAsNew(config, REFS_UPSTREAM, REFS_BACKUP);
1053 			assertSections(fbConfig, "include", "user", "core", "remote");
1054 		});
1055 	}
1056 
1057 	@Test
1058 	public void testIncludeUnsetSectionMustNotTouchIncludedLines()
1059 			throws IOException, ConfigInvalidException {
1060 		File includedFile = tmp.newFile("included");
1061 		RefSpec includedRefSpec = new RefSpec(REFS_UPSTREAM);
1062 		String includedContent = "[remote \"origin\"]\n" + "fetch="
1063 				+ includedRefSpec;
1064 		Files.write(includedFile.toPath(), includedContent.getBytes(UTF_8));
1065 
1066 		File configFile = tmp.newFile("config");
1067 		RefSpec refSpec = new RefSpec(REFS_ORIGIN);
1068 		String content = "[include]\npath=" + pathToString(includedFile) + "\n"
1069 				+ "[remote \"origin\"]\n" + "fetch=" + refSpec;
1070 		Files.write(configFile.toPath(), content.getBytes(UTF_8));
1071 
1072 		FileBasedConfig fbConfig = loadConfig(configFile);
1073 
1074 		Consumer<FileBasedConfig> assertion = config -> {
1075 			assertEquals(Arrays.asList(includedRefSpec, refSpec),
1076 					config.getRefSpecs("remote", "origin", "fetch"));
1077 		};
1078 		assertion.accept(fbConfig);
1079 
1080 		fbConfig.unsetSection("remote", "origin");
1081 		assertValuesAsIsSaveLoad(fbConfig, config -> {
1082 			assertEquals(Collections.singletonList(includedRefSpec),
1083 					config.getRefSpecs("remote", "origin", "fetch"));
1084 		});
1085 	}
1086 
1087 	private File createAllTypesIncludedContent() throws IOException {
1088 		File includedFile = tmp.newFile("included");
1089 		String includedContent = createAllTypesSampleContent("Alice Muller",
1090 				true, 10, 20, 30, CoreConfig.AutoCRLF.TRUE,
1091 				"+refs/heads/*:refs/remotes/upstream/*");
1092 		Files.write(includedFile.toPath(), includedContent.getBytes(UTF_8));
1093 		return includedFile;
1094 	}
1095 
1096 	private static void assertValuesAsIsSaveLoad(FileBasedConfig fbConfig,
1097 			Consumer<FileBasedConfig> assertion)
1098 			throws IOException, ConfigInvalidException {
1099 		assertion.accept(fbConfig);
1100 
1101 		fbConfig.save();
1102 		assertion.accept(fbConfig);
1103 
1104 		fbConfig = loadConfig(fbConfig.getFile());
1105 		assertion.accept(fbConfig);
1106 	}
1107 
1108 	private static void setAllValuesNew(Config config) {
1109 		config.setString("user", null, "name", "Alice Bauer");
1110 		config.setBoolean("core", null, "fileMode", false);
1111 		config.setInt("core", null, "deltaBaseCacheLimit", 12);
1112 		config.setLong("core", null, "packedGitLimit", 22);
1113 		config.setLong("core", null, "repositoryCacheExpireAfter", 32);
1114 		config.setEnum("core", null, "autocrlf", CoreConfig.AutoCRLF.FALSE);
1115 		config.setString("remote", "origin", "fetch",
1116 				"+refs/heads/*:refs/remotes/backup/*");
1117 	}
1118 
1119 	private static void assertValuesAsIncluded(Config config, String... refs) {
1120 		assertAllTypesSampleContent("Alice Muller", true, 10, 20, 30,
1121 				CoreConfig.AutoCRLF.TRUE, config, refs);
1122 	}
1123 
1124 	private static void assertValuesAsConfig(Config config, String... refs) {
1125 		assertAllTypesSampleContent("Alice Parker", false, 11, 21, 31,
1126 				CoreConfig.AutoCRLF.FALSE, config, refs);
1127 	}
1128 
1129 	private static void assertValuesAsNew(Config config, String... refs) {
1130 		assertValuesAsNewWithName(config, "Alice Bauer", refs);
1131 	}
1132 
1133 	private static void assertValuesAsNewWithName(Config config, String name,
1134 			String... refs) {
1135 		assertAllTypesSampleContent(name, false, 12, 22, 32,
1136 				CoreConfig.AutoCRLF.FALSE, config, refs);
1137 	}
1138 
1139 	private static void assertSections(Config config, String... sections) {
1140 		assertEquals(Arrays.asList(sections),
1141 				new ArrayList<>(config.getSections()));
1142 	}
1143 
1144 	private static String createAllTypesSampleContent(String name,
1145 			boolean fileMode, int deltaBaseCacheLimit, long packedGitLimit,
1146 			long repositoryCacheExpireAfter, CoreConfig.AutoCRLF autoCRLF,
1147 			String fetchRefSpec) {
1148 		final StringBuilder builder = new StringBuilder();
1149 		builder.append("[user]\n");
1150 		builder.append("name=");
1151 		builder.append(name);
1152 		builder.append("\n");
1153 
1154 		builder.append("[core]\n");
1155 		builder.append("fileMode=");
1156 		builder.append(fileMode);
1157 		builder.append("\n");
1158 
1159 		builder.append("deltaBaseCacheLimit=");
1160 		builder.append(deltaBaseCacheLimit);
1161 		builder.append("\n");
1162 
1163 		builder.append("packedGitLimit=");
1164 		builder.append(packedGitLimit);
1165 		builder.append("\n");
1166 
1167 		builder.append("repositoryCacheExpireAfter=");
1168 		builder.append(repositoryCacheExpireAfter);
1169 		builder.append("\n");
1170 
1171 		builder.append("autocrlf=");
1172 		builder.append(autoCRLF.name());
1173 		builder.append("\n");
1174 
1175 		builder.append("[remote \"origin\"]\n");
1176 		builder.append("fetch=");
1177 		builder.append(fetchRefSpec);
1178 		builder.append("\n");
1179 		return builder.toString();
1180 	}
1181 
1182 	private static void assertAllTypesSampleContent(String name,
1183 			boolean fileMode, int deltaBaseCacheLimit, long packedGitLimit,
1184 			long repositoryCacheExpireAfter, CoreConfig.AutoCRLF autoCRLF,
1185 			Config config, String... fetchRefSpecs) {
1186 		assertEquals(name, config.getString("user", null, "name"));
1187 		assertEquals(fileMode,
1188 				config.getBoolean("core", "fileMode", !fileMode));
1189 		assertEquals(deltaBaseCacheLimit,
1190 				config.getInt("core", "deltaBaseCacheLimit", -1));
1191 		assertEquals(packedGitLimit,
1192 				config.getLong("core", "packedGitLimit", -1));
1193 		assertEquals(repositoryCacheExpireAfter, config.getTimeUnit("core",
1194 				null, "repositoryCacheExpireAfter", -1, MILLISECONDS));
1195 		assertEquals(autoCRLF, config.getEnum("core", null, "autocrlf",
1196 				CoreConfig.AutoCRLF.INPUT));
1197 		final List<RefSpec> refspecs = new ArrayList<>();
1198 		for (String fetchRefSpec : fetchRefSpecs) {
1199 			refspecs.add(new RefSpec(fetchRefSpec));
1200 		}
1201 
1202 		assertEquals(refspecs, config.getRefSpecs("remote", "origin", "fetch"));
1203 	}
1204 
1205 	private static void assertReadLong(long exp) throws ConfigInvalidException {
1206 		assertReadLong(exp, String.valueOf(exp));
1207 	}
1208 
1209 	private static void assertReadLong(long exp, String act)
1210 			throws ConfigInvalidException {
1211 		final Config c = parse("[s]\na = " + act + "\n");
1212 		assertEquals(exp, c.getLong("s", null, "a", 0L));
1213 	}
1214 
1215 	private static Config parse(String content)
1216 			throws ConfigInvalidException {
1217 		return parse(content, null);
1218 	}
1219 
1220 	private static Config parse(String content, Config baseConfig)
1221 			throws ConfigInvalidException {
1222 		final Config c = new Config(baseConfig);
1223 		c.fromText(content);
1224 		return c;
1225 	}
1226 
1227 	@Test
1228 	public void testTimeUnit() throws ConfigInvalidException {
1229 		assertEquals(0, parseTime("0", NANOSECONDS));
1230 		assertEquals(2, parseTime("2ns", NANOSECONDS));
1231 		assertEquals(200, parseTime("200 nanoseconds", NANOSECONDS));
1232 
1233 		assertEquals(0, parseTime("0", MICROSECONDS));
1234 		assertEquals(2, parseTime("2us", MICROSECONDS));
1235 		assertEquals(2, parseTime("2000 nanoseconds", MICROSECONDS));
1236 		assertEquals(200, parseTime("200 microseconds", MICROSECONDS));
1237 
1238 		assertEquals(0, parseTime("0", MILLISECONDS));
1239 		assertEquals(2, parseTime("2ms", MILLISECONDS));
1240 		assertEquals(2, parseTime("2000microseconds", MILLISECONDS));
1241 		assertEquals(200, parseTime("200 milliseconds", MILLISECONDS));
1242 
1243 		assertEquals(0, parseTime("0s", SECONDS));
1244 		assertEquals(2, parseTime("2s", SECONDS));
1245 		assertEquals(231, parseTime("231sec", SECONDS));
1246 		assertEquals(1, parseTime("1second", SECONDS));
1247 		assertEquals(300, parseTime("300 seconds", SECONDS));
1248 
1249 		assertEquals(2, parseTime("2m", MINUTES));
1250 		assertEquals(2, parseTime("2min", MINUTES));
1251 		assertEquals(1, parseTime("1 minute", MINUTES));
1252 		assertEquals(10, parseTime("10 minutes", MINUTES));
1253 
1254 		assertEquals(5, parseTime("5h", HOURS));
1255 		assertEquals(5, parseTime("5hr", HOURS));
1256 		assertEquals(1, parseTime("1hour", HOURS));
1257 		assertEquals(48, parseTime("48hours", HOURS));
1258 
1259 		assertEquals(5, parseTime("5 h", HOURS));
1260 		assertEquals(5, parseTime("5 hr", HOURS));
1261 		assertEquals(1, parseTime("1 hour", HOURS));
1262 		assertEquals(48, parseTime("48 hours", HOURS));
1263 		assertEquals(48, parseTime("48 \t \r hours", HOURS));
1264 
1265 		assertEquals(4, parseTime("4d", DAYS));
1266 		assertEquals(1, parseTime("1day", DAYS));
1267 		assertEquals(14, parseTime("14days", DAYS));
1268 
1269 		assertEquals(7, parseTime("1w", DAYS));
1270 		assertEquals(7, parseTime("1week", DAYS));
1271 		assertEquals(14, parseTime("2w", DAYS));
1272 		assertEquals(14, parseTime("2weeks", DAYS));
1273 
1274 		assertEquals(30, parseTime("1mon", DAYS));
1275 		assertEquals(30, parseTime("1month", DAYS));
1276 		assertEquals(60, parseTime("2mon", DAYS));
1277 		assertEquals(60, parseTime("2months", DAYS));
1278 
1279 		assertEquals(365, parseTime("1y", DAYS));
1280 		assertEquals(365, parseTime("1year", DAYS));
1281 		assertEquals(365 * 2, parseTime("2years", DAYS));
1282 	}
1283 
1284 	private long parseTime(String value, TimeUnit unit)
1285 			throws ConfigInvalidException {
1286 		Config c = parse("[a]\na=" + value + "\n");
1287 		return c.getTimeUnit("a", null, "a", 0, unit);
1288 	}
1289 
1290 	@Test
1291 	public void testTimeUnitDefaultValue() throws ConfigInvalidException {
1292 		// value not present
1293 		assertEquals(20, parse("[a]\na=0\n").getTimeUnit("a", null, "b", 20,
1294 				MILLISECONDS));
1295 		// value is empty
1296 		assertEquals(20, parse("[a]\na=\" \"\n").getTimeUnit("a", null, "a", 20,
1297 				MILLISECONDS));
1298 
1299 		// value is not numeric
1300 		assertEquals(20, parse("[a]\na=test\n").getTimeUnit("a", null, "a", 20,
1301 				MILLISECONDS));
1302 	}
1303 
1304 	@Test
1305 	public void testTimeUnitInvalid() throws ConfigInvalidException {
1306 		expectedEx.expect(IllegalArgumentException.class);
1307 		expectedEx
1308 				.expectMessage("Invalid time unit value: a.a=1 monttthhh");
1309 		parseTime("1 monttthhh", DAYS);
1310 	}
1311 
1312 	@Test
1313 	public void testTimeUnitInvalidWithSection() throws ConfigInvalidException {
1314 		Config c = parse("[a \"b\"]\na=1 monttthhh\n");
1315 		expectedEx.expect(IllegalArgumentException.class);
1316 		expectedEx.expectMessage("Invalid time unit value: a.b.a=1 monttthhh");
1317 		c.getTimeUnit("a", "b", "a", 0, DAYS);
1318 	}
1319 
1320 	@Test
1321 	public void testTimeUnitNegative() throws ConfigInvalidException {
1322 		expectedEx.expect(IllegalArgumentException.class);
1323 		parseTime("-1", MILLISECONDS);
1324 	}
1325 
1326 	@Test
1327 	public void testEscapeSpacesOnly() throws ConfigInvalidException {
1328 		// Empty string is read back as null, so this doesn't round-trip.
1329 		assertEquals("", Config.escapeValue(""));
1330 
1331 		assertValueRoundTrip(" ", "\" \"");
1332 		assertValueRoundTrip("  ", "\"  \"");
1333 	}
1334 
1335 	@Test
1336 	public void testEscapeLeadingSpace() throws ConfigInvalidException {
1337 		assertValueRoundTrip("x", "x");
1338 		assertValueRoundTrip(" x", "\" x\"");
1339 		assertValueRoundTrip("  x", "\"  x\"");
1340 	}
1341 
1342 	@Test
1343 	public void testEscapeTrailingSpace() throws ConfigInvalidException {
1344 		assertValueRoundTrip("x", "x");
1345 		assertValueRoundTrip("x  ","\"x  \"");
1346 		assertValueRoundTrip("x ","\"x \"");
1347 	}
1348 
1349 	@Test
1350 	public void testEscapeLeadingAndTrailingSpace()
1351 			throws ConfigInvalidException {
1352 		assertValueRoundTrip(" x ", "\" x \"");
1353 		assertValueRoundTrip("  x ", "\"  x \"");
1354 		assertValueRoundTrip(" x  ", "\" x  \"");
1355 		assertValueRoundTrip("  x  ", "\"  x  \"");
1356 	}
1357 
1358 	@Test
1359 	public void testNoEscapeInternalSpaces() throws ConfigInvalidException {
1360 		assertValueRoundTrip("x y");
1361 		assertValueRoundTrip("x  y");
1362 		assertValueRoundTrip("x  y");
1363 		assertValueRoundTrip("x  y   z");
1364 		assertValueRoundTrip("x " + WS + " y");
1365 	}
1366 
1367 	@Test
1368 	public void testNoEscapeSpecialCharacters() throws ConfigInvalidException {
1369 		assertValueRoundTrip("x\\y", "x\\\\y");
1370 		assertValueRoundTrip("x\"y", "x\\\"y");
1371 		assertValueRoundTrip("x\ny", "x\\ny");
1372 		assertValueRoundTrip("x\ty", "x\\ty");
1373 		assertValueRoundTrip("x\by", "x\\by");
1374 	}
1375 
1376 	@Test
1377 	public void testParseLiteralBackspace() throws ConfigInvalidException {
1378 		// This is round-tripped with an escape sequence by JGit, but C git writes
1379 		// it out as a literal backslash.
1380 		assertEquals("x\by", parseEscapedValue("x\by"));
1381 	}
1382 
1383 	@Test
1384 	public void testEscapeCommentCharacters() throws ConfigInvalidException {
1385 		assertValueRoundTrip("x#y", "\"x#y\"");
1386 		assertValueRoundTrip("x;y", "\"x;y\"");
1387 	}
1388 
1389 	@Test
1390 	public void testEscapeValueInvalidCharacters() {
1391 		assertIllegalArgumentException(() -> Config.escapeSubsection("x\0y"));
1392 	}
1393 
1394 	@Test
1395 	public void testEscapeSubsectionInvalidCharacters() {
1396 		assertIllegalArgumentException(() -> Config.escapeSubsection("x\ny"));
1397 		assertIllegalArgumentException(() -> Config.escapeSubsection("x\0y"));
1398 	}
1399 
1400 	@Test
1401 	public void testParseMultipleQuotedRegions() throws ConfigInvalidException {
1402 		assertEquals("b a z; \n", parseEscapedValue("b\" a\"\" z; \\n\""));
1403 	}
1404 
1405 	@Test
1406 	public void testParseComments() throws ConfigInvalidException {
1407 		assertEquals("baz", parseEscapedValue("baz; comment"));
1408 		assertEquals("baz", parseEscapedValue("baz# comment"));
1409 		assertEquals("baz", parseEscapedValue("baz ; comment"));
1410 		assertEquals("baz", parseEscapedValue("baz # comment"));
1411 
1412 		assertEquals("baz", parseEscapedValue("baz ; comment"));
1413 		assertEquals("baz", parseEscapedValue("baz # comment"));
1414 		assertEquals("baz", parseEscapedValue("baz " + WS + " ; comment"));
1415 		assertEquals("baz", parseEscapedValue("baz " + WS + " # comment"));
1416 
1417 		assertEquals("baz ", parseEscapedValue("\"baz \"; comment"));
1418 		assertEquals("baz ", parseEscapedValue("\"baz \"# comment"));
1419 		assertEquals("baz ", parseEscapedValue("\"baz \" ; comment"));
1420 		assertEquals("baz ", parseEscapedValue("\"baz \" # comment"));
1421 	}
1422 
1423 	@Test
1424 	public void testEscapeSubsection() throws ConfigInvalidException {
1425 		assertSubsectionRoundTrip("", "\"\"");
1426 		assertSubsectionRoundTrip("x", "\"x\"");
1427 		assertSubsectionRoundTrip(" x", "\" x\"");
1428 		assertSubsectionRoundTrip("x ", "\"x \"");
1429 		assertSubsectionRoundTrip(" x ", "\" x \"");
1430 		assertSubsectionRoundTrip("x y", "\"x y\"");
1431 		assertSubsectionRoundTrip("x  y", "\"x  y\"");
1432 		assertSubsectionRoundTrip("x\\y", "\"x\\\\y\"");
1433 		assertSubsectionRoundTrip("x\"y", "\"x\\\"y\"");
1434 
1435 		// Unlike for values, \b and \t are not escaped.
1436 		assertSubsectionRoundTrip("x\by", "\"x\by\"");
1437 		assertSubsectionRoundTrip("x\ty", "\"x\ty\"");
1438 	}
1439 
1440 	@Test
1441 	public void testParseInvalidValues() {
1442 		assertInvalidValue(JGitText.get().newlineInQuotesNotAllowed, "x\"\n\"y");
1443 		assertInvalidValue(JGitText.get().endOfFileInEscape, "x\\");
1444 		assertInvalidValue(
1445 				MessageFormat.format(JGitText.get().badEscape, 'q'), "x\\q");
1446 	}
1447 
1448 	@Test
1449 	public void testParseInvalidSubsections() {
1450 		assertInvalidSubsection(
1451 				JGitText.get().newlineInQuotesNotAllowed, "\"x\ny\"");
1452 	}
1453 
1454 	@Test
1455 	public void testDropBackslashFromInvalidEscapeSequenceInSubsectionName()
1456 			throws ConfigInvalidException {
1457 		assertEquals("x0", parseEscapedSubsection("\"x\\0\""));
1458 		assertEquals("xq", parseEscapedSubsection("\"x\\q\""));
1459 		// Unlike for values, \b, \n, and \t are not valid escape sequences.
1460 		assertEquals("xb", parseEscapedSubsection("\"x\\b\""));
1461 		assertEquals("xn", parseEscapedSubsection("\"x\\n\""));
1462 		assertEquals("xt", parseEscapedSubsection("\"x\\t\""));
1463 	}
1464 
1465 	private static void assertValueRoundTrip(String value)
1466 			throws ConfigInvalidException {
1467 		assertValueRoundTrip(value, value);
1468 	}
1469 
1470 	private static void assertValueRoundTrip(String value, String expectedEscaped)
1471 			throws ConfigInvalidException {
1472 		String escaped = Config.escapeValue(value);
1473 		assertEquals("escape failed;", expectedEscaped, escaped);
1474 		assertEquals("parse failed;", value, parseEscapedValue(escaped));
1475 	}
1476 
1477 	private static String parseEscapedValue(String escapedValue)
1478 			throws ConfigInvalidException {
1479 		String text = "[foo]\nbar=" + escapedValue;
1480 		Config c = parse(text);
1481 		return c.getString("foo", null, "bar");
1482 	}
1483 
1484 	private static void assertInvalidValue(String expectedMessage,
1485 			String escapedValue) {
1486 		try {
1487 			parseEscapedValue(escapedValue);
1488 			fail("expected ConfigInvalidException");
1489 		} catch (ConfigInvalidException e) {
1490 			assertEquals(expectedMessage, e.getMessage());
1491 		}
1492 	}
1493 
1494 	private static void assertSubsectionRoundTrip(String subsection,
1495 			String expectedEscaped) throws ConfigInvalidException {
1496 		String escaped = Config.escapeSubsection(subsection);
1497 		assertEquals("escape failed;", expectedEscaped, escaped);
1498 		assertEquals("parse failed;", subsection, parseEscapedSubsection(escaped));
1499 	}
1500 
1501 	private static String parseEscapedSubsection(String escapedSubsection)
1502 			throws ConfigInvalidException {
1503 		String text = "[foo " + escapedSubsection + "]\nbar = value";
1504 		Config c = parse(text);
1505 		Set<String> subsections = c.getSubsections("foo");
1506 		assertEquals("only one section", 1, subsections.size());
1507 		return subsections.iterator().next();
1508 	}
1509 
1510 	private static void assertIllegalArgumentException(Runnable r) {
1511 		try {
1512 			r.run();
1513 			fail("expected IllegalArgumentException");
1514 		} catch (IllegalArgumentException e) {
1515 			// Expected.
1516 		}
1517 	}
1518 
1519 	private static void assertInvalidSubsection(String expectedMessage,
1520 			String escapedSubsection) {
1521 		try {
1522 			parseEscapedSubsection(escapedSubsection);
1523 			fail("expected ConfigInvalidException");
1524 		} catch (ConfigInvalidException e) {
1525 			assertEquals(expectedMessage, e.getMessage());
1526 		}
1527 	}
1528 
1529 	private static FileBasedConfig loadConfig(File file)
1530 			throws IOException, ConfigInvalidException {
1531 		final FileBasedConfig config = new FileBasedConfig(null, file,
1532 				FS.DETECTED);
1533 		config.load();
1534 		return config;
1535 	}
1536 }