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 org.junit.Assert.assertArrayEquals;
52  import static org.junit.Assert.assertEquals;
53  import static org.junit.Assert.assertFalse;
54  import static org.junit.Assert.assertNotNull;
55  import static org.junit.Assert.assertNull;
56  import static org.junit.Assert.assertSame;
57  import static org.junit.Assert.assertTrue;
58  import static org.junit.Assert.fail;
59  
60  import java.text.MessageFormat;
61  import java.util.Arrays;
62  import java.util.Iterator;
63  import java.util.LinkedList;
64  import java.util.Set;
65  
66  import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
67  import org.eclipse.jgit.errors.ConfigInvalidException;
68  import org.eclipse.jgit.junit.MockSystemReader;
69  import org.eclipse.jgit.merge.MergeConfig;
70  import org.eclipse.jgit.util.FS;
71  import org.eclipse.jgit.util.SystemReader;
72  import org.junit.After;
73  import org.junit.Test;
74  
75  /**
76   * Test reading of git config
77   */
78  public class ConfigTest {
79  
80  	@After
81  	public void tearDown() {
82  		SystemReader.setInstance(null);
83  	}
84  
85  	@Test
86  	public void test001_ReadBareKey() throws ConfigInvalidException {
87  		final Config c = parse("[foo]\nbar\n");
88  		assertTrue(c.getBoolean("foo", null, "bar", false));
89  		assertEquals("", c.getString("foo", null, "bar"));
90  	}
91  
92  	@Test
93  	public void test002_ReadWithSubsection() throws ConfigInvalidException {
94  		final Config c = parse("[foo \"zip\"]\nbar\n[foo \"zap\"]\nbar=false\nn=3\n");
95  		assertTrue(c.getBoolean("foo", "zip", "bar", false));
96  		assertEquals("", c.getString("foo","zip", "bar"));
97  		assertFalse(c.getBoolean("foo", "zap", "bar", true));
98  		assertEquals("false", c.getString("foo", "zap", "bar"));
99  		assertEquals(3, c.getInt("foo", "zap", "n", 4));
100 		assertEquals(4, c.getInt("foo", "zap","m", 4));
101 	}
102 
103 	@Test
104 	public void test003_PutRemote() {
105 		final Config c = new Config();
106 		c.setString("sec", "ext", "name", "value");
107 		c.setString("sec", "ext", "name2", "value2");
108 		final String expText = "[sec \"ext\"]\n\tname = value\n\tname2 = value2\n";
109 		assertEquals(expText, c.toText());
110 	}
111 
112 	@Test
113 	public void test004_PutGetSimple() {
114 		Config c = new Config();
115 		c.setString("my", null, "somename", "false");
116 		assertEquals("false", c.getString("my", null, "somename"));
117 		assertEquals("[my]\n\tsomename = false\n", c.toText());
118 	}
119 
120 	@Test
121 	public void test005_PutGetStringList() {
122 		Config c = new Config();
123 		final LinkedList<String> values = new LinkedList<String>();
124 		values.add("value1");
125 		values.add("value2");
126 		c.setStringList("my", null, "somename", values);
127 
128 		final Object[] expArr = values.toArray();
129 		final String[] actArr = c.getStringList("my", null, "somename");
130 		assertArrayEquals(expArr, actArr);
131 
132 		final String expText = "[my]\n\tsomename = value1\n\tsomename = value2\n";
133 		assertEquals(expText, c.toText());
134 	}
135 
136 	@Test
137 	public void test006_readCaseInsensitive() throws ConfigInvalidException {
138 		final Config c = parse("[Foo]\nBar\n");
139 		assertTrue(c.getBoolean("foo", null, "bar", false));
140 		assertEquals("", c.getString("foo", null, "bar"));
141 	}
142 
143 	@Test
144 	public void test007_readUserConfig() {
145 		final MockSystemReader mockSystemReader = new MockSystemReader();
146 		SystemReader.setInstance(mockSystemReader);
147 		final String hostname = mockSystemReader.getHostname();
148 		final Config userGitConfig = mockSystemReader.openUserConfig(null,
149 				FS.DETECTED);
150 		final Config localConfig = new Config(userGitConfig);
151 		mockSystemReader.clearProperties();
152 
153 		String authorName;
154 		String authorEmail;
155 
156 		// no values defined nowhere
157 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
158 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
159 		assertEquals(Constants.UNKNOWN_USER_DEFAULT, authorName);
160 		assertEquals(Constants.UNKNOWN_USER_DEFAULT + "@" + hostname, authorEmail);
161 		assertTrue(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
162 		assertTrue(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
163 
164 		// the system user name is defined
165 		mockSystemReader.setProperty(Constants.OS_USER_NAME_KEY, "os user name");
166 		localConfig.uncache(UserConfig.KEY);
167 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
168 		assertEquals("os user name", authorName);
169 		assertTrue(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
170 
171 		if (hostname != null && hostname.length() != 0) {
172 			authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
173 			assertEquals("os user name@" + hostname, authorEmail);
174 		}
175 		assertTrue(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
176 
177 		// the git environment variables are defined
178 		mockSystemReader.setProperty(Constants.GIT_AUTHOR_NAME_KEY, "git author name");
179 		mockSystemReader.setProperty(Constants.GIT_AUTHOR_EMAIL_KEY, "author@email");
180 		localConfig.uncache(UserConfig.KEY);
181 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
182 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
183 		assertEquals("git author name", authorName);
184 		assertEquals("author@email", authorEmail);
185 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
186 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
187 
188 		// the values are defined in the global configuration
189 		// first clear environment variables since they would override
190 		// configuration files
191 		mockSystemReader.clearProperties();
192 		userGitConfig.setString("user", null, "name", "global username");
193 		userGitConfig.setString("user", null, "email", "author@globalemail");
194 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
195 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
196 		assertEquals("global username", authorName);
197 		assertEquals("author@globalemail", authorEmail);
198 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
199 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
200 
201 		// the values are defined in the local configuration
202 		localConfig.setString("user", null, "name", "local username");
203 		localConfig.setString("user", null, "email", "author@localemail");
204 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
205 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
206 		assertEquals("local username", authorName);
207 		assertEquals("author@localemail", authorEmail);
208 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
209 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
210 
211 		authorName = localConfig.get(UserConfig.KEY).getCommitterName();
212 		authorEmail = localConfig.get(UserConfig.KEY).getCommitterEmail();
213 		assertEquals("local username", authorName);
214 		assertEquals("author@localemail", authorEmail);
215 		assertFalse(localConfig.get(UserConfig.KEY).isCommitterNameImplicit());
216 		assertFalse(localConfig.get(UserConfig.KEY).isCommitterEmailImplicit());
217 
218 		// also git environment variables are defined
219 		mockSystemReader.setProperty(Constants.GIT_AUTHOR_NAME_KEY,
220 				"git author name");
221 		mockSystemReader.setProperty(Constants.GIT_AUTHOR_EMAIL_KEY,
222 				"author@email");
223 		localConfig.setString("user", null, "name", "local username");
224 		localConfig.setString("user", null, "email", "author@localemail");
225 		authorName = localConfig.get(UserConfig.KEY).getAuthorName();
226 		authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();
227 		assertEquals("git author name", authorName);
228 		assertEquals("author@email", authorEmail);
229 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorNameImplicit());
230 		assertFalse(localConfig.get(UserConfig.KEY).isAuthorEmailImplicit());
231 	}
232 
233 	@Test
234 	public void testReadUserConfigWithInvalidCharactersStripped() {
235 		final MockSystemReader mockSystemReader = new MockSystemReader();
236 		final Config localConfig = new Config(mockSystemReader.openUserConfig(
237 				null, FS.DETECTED));
238 
239 		localConfig.setString("user", null, "name", "foo<bar");
240 		localConfig.setString("user", null, "email", "baz>\nqux@example.com");
241 
242 		UserConfig userConfig = localConfig.get(UserConfig.KEY);
243 		assertEquals("foobar", userConfig.getAuthorName());
244 		assertEquals("bazqux@example.com", userConfig.getAuthorEmail());
245 	}
246 
247 	@Test
248 	public void testReadBoolean_TrueFalse1() throws ConfigInvalidException {
249 		final Config c = parse("[s]\na = true\nb = false\n");
250 		assertEquals("true", c.getString("s", null, "a"));
251 		assertEquals("false", c.getString("s", null, "b"));
252 
253 		assertTrue(c.getBoolean("s", "a", false));
254 		assertFalse(c.getBoolean("s", "b", true));
255 	}
256 
257 	@Test
258 	public void testReadBoolean_TrueFalse2() throws ConfigInvalidException {
259 		final Config c = parse("[s]\na = TrUe\nb = fAlSe\n");
260 		assertEquals("TrUe", c.getString("s", null, "a"));
261 		assertEquals("fAlSe", c.getString("s", null, "b"));
262 
263 		assertTrue(c.getBoolean("s", "a", false));
264 		assertFalse(c.getBoolean("s", "b", true));
265 	}
266 
267 	@Test
268 	public void testReadBoolean_YesNo1() throws ConfigInvalidException {
269 		final Config c = parse("[s]\na = yes\nb = no\n");
270 		assertEquals("yes", c.getString("s", null, "a"));
271 		assertEquals("no", c.getString("s", null, "b"));
272 
273 		assertTrue(c.getBoolean("s", "a", false));
274 		assertFalse(c.getBoolean("s", "b", true));
275 	}
276 
277 	@Test
278 	public void testReadBoolean_YesNo2() throws ConfigInvalidException {
279 		final Config c = parse("[s]\na = yEs\nb = NO\n");
280 		assertEquals("yEs", c.getString("s", null, "a"));
281 		assertEquals("NO", c.getString("s", null, "b"));
282 
283 		assertTrue(c.getBoolean("s", "a", false));
284 		assertFalse(c.getBoolean("s", "b", true));
285 	}
286 
287 	@Test
288 	public void testReadBoolean_OnOff1() throws ConfigInvalidException {
289 		final Config c = parse("[s]\na = on\nb = off\n");
290 		assertEquals("on", c.getString("s", null, "a"));
291 		assertEquals("off", c.getString("s", null, "b"));
292 
293 		assertTrue(c.getBoolean("s", "a", false));
294 		assertFalse(c.getBoolean("s", "b", true));
295 	}
296 
297 	@Test
298 	public void testReadBoolean_OnOff2() throws ConfigInvalidException {
299 		final Config c = parse("[s]\na = ON\nb = OFF\n");
300 		assertEquals("ON", c.getString("s", null, "a"));
301 		assertEquals("OFF", c.getString("s", null, "b"));
302 
303 		assertTrue(c.getBoolean("s", "a", false));
304 		assertFalse(c.getBoolean("s", "b", true));
305 	}
306 
307 	static enum TestEnum {
308 		ONE_TWO;
309 	}
310 
311 	@Test
312 	public void testGetEnum() throws ConfigInvalidException {
313 		Config c = parse("[s]\na = ON\nb = input\nc = true\nd = off\n");
314 		assertSame(CoreConfig.AutoCRLF.TRUE, c.getEnum("s", null, "a",
315 				CoreConfig.AutoCRLF.FALSE));
316 
317 		assertSame(CoreConfig.AutoCRLF.INPUT, c.getEnum("s", null, "b",
318 				CoreConfig.AutoCRLF.FALSE));
319 
320 		assertSame(CoreConfig.AutoCRLF.TRUE, c.getEnum("s", null, "c",
321 				CoreConfig.AutoCRLF.FALSE));
322 
323 		assertSame(CoreConfig.AutoCRLF.FALSE, c.getEnum("s", null, "d",
324 				CoreConfig.AutoCRLF.TRUE));
325 
326 		c = new Config();
327 		assertSame(CoreConfig.AutoCRLF.FALSE, c.getEnum("s", null, "d",
328 				CoreConfig.AutoCRLF.FALSE));
329 
330 		c = parse("[s \"b\"]\n\tc = one two\n");
331 		assertSame(TestEnum.ONE_TWO, c.getEnum("s", "b", "c", TestEnum.ONE_TWO));
332 
333 		c = parse("[s \"b\"]\n\tc = one-two\n");
334 		assertSame(TestEnum.ONE_TWO, c.getEnum("s", "b", "c", TestEnum.ONE_TWO));
335 	}
336 
337 	@Test
338 	public void testGetInvalidEnum() throws ConfigInvalidException {
339 		Config c = parse("[a]\n\tb = invalid\n");
340 		try {
341 			c.getEnum("a", null, "b", TestEnum.ONE_TWO);
342 			fail();
343 		} catch (IllegalArgumentException e) {
344 			assertEquals("Invalid value: a.b=invalid", e.getMessage());
345 		}
346 
347 		c = parse("[a \"b\"]\n\tc = invalid\n");
348 		try {
349 			c.getEnum("a", "b", "c", TestEnum.ONE_TWO);
350 			fail();
351 		} catch (IllegalArgumentException e) {
352 			assertEquals("Invalid value: a.b.c=invalid", e.getMessage());
353 		}
354 	}
355 
356 	@Test
357 	public void testSetEnum() {
358 		final Config c = new Config();
359 		c.setEnum("s", "b", "c", TestEnum.ONE_TWO);
360 		assertEquals("[s \"b\"]\n\tc = one two\n", c.toText());
361 	}
362 
363 	@Test
364 	public void testGetFastForwardMergeoptions() throws ConfigInvalidException {
365 		Config c = new Config(null); // not set
366 		assertSame(FastForwardMode.FF, c.getEnum(
367 				ConfigConstants.CONFIG_BRANCH_SECTION, "side",
368 				ConfigConstants.CONFIG_KEY_MERGEOPTIONS, FastForwardMode.FF));
369 		MergeConfig mergeConfig = c.get(MergeConfig.getParser("side"));
370 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
371 		c = parse("[branch \"side\"]\n\tmergeoptions = --ff-only\n");
372 		assertSame(FastForwardMode.FF_ONLY, c.getEnum(
373 				ConfigConstants.CONFIG_BRANCH_SECTION, "side",
374 				ConfigConstants.CONFIG_KEY_MERGEOPTIONS,
375 				FastForwardMode.FF_ONLY));
376 		mergeConfig = c.get(MergeConfig.getParser("side"));
377 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
378 		c = parse("[branch \"side\"]\n\tmergeoptions = --ff\n");
379 		assertSame(FastForwardMode.FF, c.getEnum(
380 				ConfigConstants.CONFIG_BRANCH_SECTION, "side",
381 				ConfigConstants.CONFIG_KEY_MERGEOPTIONS, FastForwardMode.FF));
382 		mergeConfig = c.get(MergeConfig.getParser("side"));
383 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
384 		c = parse("[branch \"side\"]\n\tmergeoptions = --no-ff\n");
385 		assertSame(FastForwardMode.NO_FF, c.getEnum(
386 				ConfigConstants.CONFIG_BRANCH_SECTION, "side",
387 				ConfigConstants.CONFIG_KEY_MERGEOPTIONS, FastForwardMode.NO_FF));
388 		mergeConfig = c.get(MergeConfig.getParser("side"));
389 		assertSame(FastForwardMode.NO_FF, mergeConfig.getFastForwardMode());
390 	}
391 
392 	@Test
393 	public void testSetFastForwardMergeoptions() {
394 		final Config c = new Config();
395 		c.setEnum("branch", "side", "mergeoptions", FastForwardMode.FF);
396 		assertEquals("[branch \"side\"]\n\tmergeoptions = --ff\n", c.toText());
397 		c.setEnum("branch", "side", "mergeoptions", FastForwardMode.FF_ONLY);
398 		assertEquals("[branch \"side\"]\n\tmergeoptions = --ff-only\n",
399 				c.toText());
400 		c.setEnum("branch", "side", "mergeoptions", FastForwardMode.NO_FF);
401 		assertEquals("[branch \"side\"]\n\tmergeoptions = --no-ff\n",
402 				c.toText());
403 	}
404 
405 	@Test
406 	public void testGetFastForwardMerge() throws ConfigInvalidException {
407 		Config c = new Config(null); // not set
408 		assertSame(FastForwardMode.Merge.TRUE, c.getEnum(
409 				ConfigConstants.CONFIG_KEY_MERGE, null,
410 				ConfigConstants.CONFIG_KEY_FF, FastForwardMode.Merge.TRUE));
411 		MergeConfig mergeConfig = c.get(MergeConfig.getParser("side"));
412 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
413 		c = parse("[merge]\n\tff = only\n");
414 		assertSame(FastForwardMode.Merge.ONLY, c.getEnum(
415 				ConfigConstants.CONFIG_KEY_MERGE, null,
416 				ConfigConstants.CONFIG_KEY_FF, FastForwardMode.Merge.ONLY));
417 		mergeConfig = c.get(MergeConfig.getParser("side"));
418 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
419 		c = parse("[merge]\n\tff = true\n");
420 		assertSame(FastForwardMode.Merge.TRUE, c.getEnum(
421 				ConfigConstants.CONFIG_KEY_MERGE, null,
422 				ConfigConstants.CONFIG_KEY_FF, FastForwardMode.Merge.TRUE));
423 		mergeConfig = c.get(MergeConfig.getParser("side"));
424 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
425 		c = parse("[merge]\n\tff = false\n");
426 		assertSame(FastForwardMode.Merge.FALSE, c.getEnum(
427 				ConfigConstants.CONFIG_KEY_MERGE, null,
428 				ConfigConstants.CONFIG_KEY_FF, FastForwardMode.Merge.FALSE));
429 		mergeConfig = c.get(MergeConfig.getParser("side"));
430 		assertSame(FastForwardMode.NO_FF, mergeConfig.getFastForwardMode());
431 	}
432 
433 	@Test
434 	public void testCombinedMergeOptions() throws ConfigInvalidException {
435 		Config c = new Config(null); // not set
436 		MergeConfig mergeConfig = c.get(MergeConfig.getParser("side"));
437 		assertSame(FastForwardMode.FF, mergeConfig.getFastForwardMode());
438 		assertTrue(mergeConfig.isCommit());
439 		assertFalse(mergeConfig.isSquash());
440 		// branch..mergeoptions should win over merge.ff
441 		c = parse("[merge]\n\tff = false\n"
442 				+ "[branch \"side\"]\n\tmergeoptions = --ff-only\n");
443 		mergeConfig = c.get(MergeConfig.getParser("side"));
444 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
445 		assertTrue(mergeConfig.isCommit());
446 		assertFalse(mergeConfig.isSquash());
447 		// merge.ff used for ff setting if not set via mergeoptions
448 		c = parse("[merge]\n\tff = only\n"
449 				+ "[branch \"side\"]\n\tmergeoptions = --squash\n");
450 		mergeConfig = c.get(MergeConfig.getParser("side"));
451 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
452 		assertTrue(mergeConfig.isCommit());
453 		assertTrue(mergeConfig.isSquash());
454 		// mergeoptions wins if it has ff options amongst other options
455 		c = parse("[merge]\n\tff = false\n"
456 				+ "[branch \"side\"]\n\tmergeoptions = --ff-only --no-commit\n");
457 		mergeConfig = c.get(MergeConfig.getParser("side"));
458 		assertSame(FastForwardMode.FF_ONLY, mergeConfig.getFastForwardMode());
459 		assertFalse(mergeConfig.isCommit());
460 		assertFalse(mergeConfig.isSquash());
461 	}
462 
463 	@Test
464 	public void testSetFastForwardMerge() {
465 		final Config c = new Config();
466 		c.setEnum("merge", null, "ff",
467 				FastForwardMode.Merge.valueOf(FastForwardMode.FF));
468 		assertEquals("[merge]\n\tff = true\n", c.toText());
469 		c.setEnum("merge", null, "ff",
470 				FastForwardMode.Merge.valueOf(FastForwardMode.FF_ONLY));
471 		assertEquals("[merge]\n\tff = only\n", c.toText());
472 		c.setEnum("merge", null, "ff",
473 				FastForwardMode.Merge.valueOf(FastForwardMode.NO_FF));
474 		assertEquals("[merge]\n\tff = false\n", c.toText());
475 	}
476 
477 	@Test
478 	public void testReadLong() throws ConfigInvalidException {
479 		assertReadLong(1L);
480 		assertReadLong(-1L);
481 		assertReadLong(Long.MIN_VALUE);
482 		assertReadLong(Long.MAX_VALUE);
483 		assertReadLong(4L * 1024 * 1024 * 1024, "4g");
484 		assertReadLong(3L * 1024 * 1024, "3 m");
485 		assertReadLong(8L * 1024, "8 k");
486 
487 		try {
488 			assertReadLong(-1, "1.5g");
489 			fail("incorrectly accepted 1.5g");
490 		} catch (IllegalArgumentException e) {
491 			assertEquals("Invalid integer value: s.a=1.5g", e.getMessage());
492 		}
493 	}
494 
495 	@Test
496 	public void testBooleanWithNoValue() throws ConfigInvalidException {
497 		Config c = parse("[my]\n\tempty\n");
498 		assertEquals("", c.getString("my", null, "empty"));
499 		assertEquals(1, c.getStringList("my", null, "empty").length);
500 		assertEquals("", c.getStringList("my", null, "empty")[0]);
501 		assertTrue(c.getBoolean("my", "empty", false));
502 		assertEquals("[my]\n\tempty\n", c.toText());
503 	}
504 
505 	@Test
506 	public void testEmptyString() throws ConfigInvalidException {
507 		Config c = parse("[my]\n\tempty =\n");
508 		assertNull(c.getString("my", null, "empty"));
509 
510 		String[] values = c.getStringList("my", null, "empty");
511 		assertNotNull(values);
512 		assertEquals(1, values.length);
513 		assertNull(values[0]);
514 
515 		// always matches the default, because its non-boolean
516 		assertTrue(c.getBoolean("my", "empty", true));
517 		assertFalse(c.getBoolean("my", "empty", false));
518 
519 		assertEquals("[my]\n\tempty =\n", c.toText());
520 
521 		c = new Config();
522 		c.setStringList("my", null, "empty", Arrays.asList(values));
523 		assertEquals("[my]\n\tempty =\n", c.toText());
524 	}
525 
526 	@Test
527 	public void testUnsetBranchSection() throws ConfigInvalidException {
528 		Config c = parse("" //
529 				+ "[branch \"keep\"]\n"
530 				+ "  merge = master.branch.to.keep.in.the.file\n"
531 				+ "\n"
532 				+ "[branch \"remove\"]\n"
533 				+ "  merge = this.will.get.deleted\n"
534 				+ "  remote = origin-for-some-long-gone-place\n"
535 				+ "\n"
536 				+ "[core-section-not-to-remove-in-test]\n"
537 				+ "  packedGitLimit = 14\n");
538 		c.unsetSection("branch", "does.not.exist");
539 		c.unsetSection("branch", "remove");
540 		assertEquals("" //
541 				+ "[branch \"keep\"]\n"
542 				+ "  merge = master.branch.to.keep.in.the.file\n"
543 				+ "\n"
544 				+ "[core-section-not-to-remove-in-test]\n"
545 				+ "  packedGitLimit = 14\n", c.toText());
546 	}
547 
548 	@Test
549 	public void testUnsetSingleSection() throws ConfigInvalidException {
550 		Config c = parse("" //
551 				+ "[branch \"keep\"]\n"
552 				+ "  merge = master.branch.to.keep.in.the.file\n"
553 				+ "\n"
554 				+ "[single]\n"
555 				+ "  merge = this.will.get.deleted\n"
556 				+ "  remote = origin-for-some-long-gone-place\n"
557 				+ "\n"
558 				+ "[core-section-not-to-remove-in-test]\n"
559 				+ "  packedGitLimit = 14\n");
560 		c.unsetSection("single", null);
561 		assertEquals("" //
562 				+ "[branch \"keep\"]\n"
563 				+ "  merge = master.branch.to.keep.in.the.file\n"
564 				+ "\n"
565 				+ "[core-section-not-to-remove-in-test]\n"
566 				+ "  packedGitLimit = 14\n", c.toText());
567 	}
568 
569 	@Test
570 	public void test008_readSectionNames() throws ConfigInvalidException {
571 		final Config c = parse("[a]\n [B]\n");
572 		Set<String> sections = c.getSections();
573 		assertTrue("Sections should contain \"a\"", sections.contains("a"));
574 		assertTrue("Sections should contain \"b\"", sections.contains("b"));
575 	}
576 
577 	@Test
578 	public void test009_readNamesInSection() throws ConfigInvalidException {
579 		String configString = "[core]\n" + "repositoryFormatVersion = 0\n"
580 				+ "filemode = false\n" + "logAllRefUpdates = true\n";
581 		final Config c = parse(configString);
582 		Set<String> names = c.getNames("core");
583 		assertEquals("Core section size", 3, names.size());
584 		assertTrue("Core section should contain \"filemode\"", names
585 				.contains("filemode"));
586 
587 		assertTrue("Core section should contain \"repositoryFormatVersion\"",
588 				names.contains("repositoryFormatVersion"));
589 
590 		assertTrue("Core section should contain \"repositoryformatversion\"",
591 				names.contains("repositoryformatversion"));
592 
593 		Iterator<String> itr = names.iterator();
594 		assertEquals("filemode", itr.next());
595 		assertEquals("logAllRefUpdates", itr.next());
596 		assertEquals("repositoryFormatVersion", itr.next());
597 		assertFalse(itr.hasNext());
598 	}
599 
600 	@Test
601 	public void test_ReadNamesInSectionRecursive()
602 			throws ConfigInvalidException {
603 		String baseConfigString = "[core]\n" + "logAllRefUpdates = true\n";
604 		String configString = "[core]\n" + "repositoryFormatVersion = 0\n"
605 				+ "filemode = false\n";
606 		final Config c = parse(configString, parse(baseConfigString));
607 		Set<String> names = c.getNames("core", true);
608 		assertEquals("Core section size", 3, names.size());
609 		assertTrue("Core section should contain \"filemode\"",
610 				names.contains("filemode"));
611 		assertTrue("Core section should contain \"repositoryFormatVersion\"",
612 				names.contains("repositoryFormatVersion"));
613 		assertTrue("Core section should contain \"logAllRefUpdates\"",
614 				names.contains("logAllRefUpdates"));
615 		assertTrue("Core section should contain \"logallrefupdates\"",
616 				names.contains("logallrefupdates"));
617 
618 		Iterator<String> itr = names.iterator();
619 		assertEquals("filemode", itr.next());
620 		assertEquals("repositoryFormatVersion", itr.next());
621 		assertEquals("logAllRefUpdates", itr.next());
622 		assertFalse(itr.hasNext());
623 	}
624 
625 	@Test
626 	public void test010_readNamesInSubSection() throws ConfigInvalidException {
627 		String configString = "[a \"sub1\"]\n"//
628 				+ "x = 0\n" //
629 				+ "y = false\n"//
630 				+ "z = true\n"//
631 				+ "[a \"sub2\"]\n"//
632 				+ "a=0\n"//
633 				+ "b=1\n";
634 		final Config c = parse(configString);
635 		Set<String> names = c.getNames("a", "sub1");
636 		assertEquals("Subsection size", 3, names.size());
637 		assertTrue("Subsection should contain \"x\"", names.contains("x"));
638 		assertTrue("Subsection should contain \"y\"", names.contains("y"));
639 		assertTrue("Subsection should contain \"z\"", names.contains("z"));
640 		names = c.getNames("a", "sub2");
641 		assertEquals("Subsection size", 2, names.size());
642 		assertTrue("Subsection should contain \"a\"", names.contains("a"));
643 		assertTrue("Subsection should contain \"b\"", names.contains("b"));
644 	}
645 
646 	@Test
647 	public void readNamesInSubSectionRecursive() throws ConfigInvalidException {
648 		String baseConfigString = "[a \"sub1\"]\n"//
649 				+ "x = 0\n" //
650 				+ "y = false\n"//
651 				+ "[a \"sub2\"]\n"//
652 				+ "A=0\n";//
653 		String configString = "[a \"sub1\"]\n"//
654 				+ "z = true\n"//
655 				+ "[a \"sub2\"]\n"//
656 				+ "B=1\n";
657 		final Config c = parse(configString, parse(baseConfigString));
658 		Set<String> names = c.getNames("a", "sub1", true);
659 		assertEquals("Subsection size", 3, names.size());
660 		assertTrue("Subsection should contain \"x\"", names.contains("x"));
661 		assertTrue("Subsection should contain \"y\"", names.contains("y"));
662 		assertTrue("Subsection should contain \"z\"", names.contains("z"));
663 		names = c.getNames("a", "sub2", true);
664 		assertEquals("Subsection size", 2, names.size());
665 		assertTrue("Subsection should contain \"A\"", names.contains("A"));
666 		assertTrue("Subsection should contain \"a\"", names.contains("a"));
667 		assertTrue("Subsection should contain \"B\"", names.contains("B"));
668 	}
669 
670 	@Test
671 	public void testQuotingForSubSectionNames() {
672 		String resultPattern = "[testsection \"{0}\"]\n\ttestname = testvalue\n";
673 		String result;
674 
675 		Config config = new Config();
676 		config.setString("testsection", "testsubsection", "testname",
677 				"testvalue");
678 
679 		result = MessageFormat.format(resultPattern, "testsubsection");
680 		assertEquals(result, config.toText());
681 		config.clear();
682 
683 		config.setString("testsection", "#quotable", "testname", "testvalue");
684 		result = MessageFormat.format(resultPattern, "#quotable");
685 		assertEquals(result, config.toText());
686 		config.clear();
687 
688 		config.setString("testsection", "with\"quote", "testname", "testvalue");
689 		result = MessageFormat.format(resultPattern, "with\\\"quote");
690 		assertEquals(result, config.toText());
691 	}
692 
693 	@Test
694 	public void testNoFinalNewline() throws ConfigInvalidException {
695 		Config c = parse("[a]\n"
696 				+ "x = 0\n"
697 				+ "y = 1");
698 		assertEquals("0", c.getString("a", null, "x"));
699 		assertEquals("1", c.getString("a", null, "y"));
700 	}
701 
702 	private static void assertReadLong(long exp) throws ConfigInvalidException {
703 		assertReadLong(exp, String.valueOf(exp));
704 	}
705 
706 	private static void assertReadLong(long exp, String act)
707 			throws ConfigInvalidException {
708 		final Config c = parse("[s]\na = " + act + "\n");
709 		assertEquals(exp, c.getLong("s", null, "a", 0L));
710 	}
711 
712 	private static Config parse(final String content)
713 			throws ConfigInvalidException {
714 		return parse(content, null);
715 	}
716 
717 	private static Config parse(final String content, Config baseConfig)
718 			throws ConfigInvalidException {
719 		final Config c = new Config(baseConfig);
720 		c.fromText(content);
721 		return c;
722 	}
723 }