View Javadoc
1   /*
2    * Copyright (C) 2012, Marc Strapetz and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.storage.file;
11  
12  import static java.nio.charset.StandardCharsets.UTF_8;
13  import static org.eclipse.jgit.util.FileUtils.pathToString;
14  import static org.junit.Assert.assertArrayEquals;
15  import static org.junit.Assert.assertEquals;
16  import static org.junit.Assert.assertNotNull;
17  import static org.junit.Assert.assertFalse;
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.IOException;
21  import java.io.OutputStream;
22  import java.nio.charset.StandardCharsets;
23  import java.nio.file.Files;
24  import java.nio.file.Path;
25  import java.nio.file.StandardOpenOption;
26  import java.util.StringTokenizer;
27  import java.util.concurrent.TimeUnit;
28  import java.util.concurrent.atomic.AtomicBoolean;
29  
30  import org.eclipse.jgit.errors.ConfigInvalidException;
31  import org.eclipse.jgit.junit.MockSystemReader;
32  import org.eclipse.jgit.util.FS;
33  import org.eclipse.jgit.util.FileUtils;
34  import org.eclipse.jgit.util.IO;
35  import org.eclipse.jgit.util.SystemReader;
36  import org.junit.After;
37  import org.junit.Before;
38  import org.junit.Test;
39  
40  public class FileBasedConfigTest {
41  
42  	private static final String USER = "user";
43  
44  	private static final String NAME = "name";
45  
46  	private static final String EMAIL = "email";
47  
48  	private static final String ALICE = "Alice";
49  
50  	private static final String BOB = "Bob";
51  
52  	private static final String ALICE_EMAIL = "alice@home";
53  
54  	private static final String CONTENT1 = "[" + USER + "]\n\t" + NAME + " = "
55  			+ ALICE + "\n";
56  
57  	private static final String CONTENT2 = "[" + USER + "]\n\t" + NAME + " = "
58  			+ BOB + "\n";
59  
60  	private static final String CONTENT3 = "[" + USER + "]\n\t" + NAME + " = "
61  			+ ALICE + "\n" + "[" + USER + "]\n\t" + EMAIL + " = " + ALICE_EMAIL;
62  
63  	private Path trash;
64  
65  	private MockSystemReader mockSystemReader;
66  
67  	@Before
68  	public void setUp() throws Exception {
69  		mockSystemReader = new MockSystemReader();
70  		SystemReader.setInstance(mockSystemReader);
71  		trash = Files.createTempDirectory("tmp_");
72  	}
73  
74  	@After
75  	public void tearDown() throws Exception {
76  		FileUtils.delete(trash.toFile(),
77  				FileUtils.RECURSIVE | FileUtils.SKIP_MISSING | FileUtils.RETRY);
78  	}
79  
80  	@Test
81  	public void testSystemEncoding() throws IOException, ConfigInvalidException {
82  		final Path file = createFile(CONTENT1.getBytes(UTF_8));
83  		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
84  				FS.DETECTED);
85  		config.load();
86  		assertEquals(ALICE, config.getString(USER, null, NAME));
87  
88  		config.setString(USER, null, NAME, BOB);
89  		config.save();
90  		assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file.toFile()));
91  	}
92  
93  	@Test
94  	public void testUTF8withoutBOM() throws IOException, ConfigInvalidException {
95  		final Path file = createFile(CONTENT1.getBytes(UTF_8));
96  		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
97  				FS.DETECTED);
98  		config.load();
99  		assertEquals(ALICE, config.getString(USER, null, NAME));
100 
101 		config.setString(USER, null, NAME, BOB);
102 		config.save();
103 		assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file.toFile()));
104 	}
105 
106 	@Test
107 	public void testUTF8withBOM() throws IOException, ConfigInvalidException {
108 		final ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
109 		bos1.write(0xEF);
110 		bos1.write(0xBB);
111 		bos1.write(0xBF);
112 		bos1.write(CONTENT1.getBytes(UTF_8));
113 
114 		final Path file = createFile(bos1.toByteArray());
115 		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
116 				FS.DETECTED);
117 		config.load();
118 		assertEquals(ALICE, config.getString(USER, null, NAME));
119 
120 		config.setString(USER, null, NAME, BOB);
121 		config.save();
122 
123 		final ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
124 		bos2.write(0xEF);
125 		bos2.write(0xBB);
126 		bos2.write(0xBF);
127 		bos2.write(CONTENT2.getBytes(UTF_8));
128 		assertArrayEquals(bos2.toByteArray(), IO.readFully(file.toFile()));
129 	}
130 
131 	@Test
132 	public void testLeadingWhitespaces() throws IOException, ConfigInvalidException {
133 		final ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
134 		bos1.write(" \n\t".getBytes(UTF_8));
135 		bos1.write(CONTENT1.getBytes(UTF_8));
136 
137 		final Path file = createFile(bos1.toByteArray());
138 		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
139 				FS.DETECTED);
140 		config.load();
141 		assertEquals(ALICE, config.getString(USER, null, NAME));
142 
143 		config.setString(USER, null, NAME, BOB);
144 		config.save();
145 
146 		final ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
147 		bos2.write(" \n\t".getBytes(UTF_8));
148 		bos2.write(CONTENT2.getBytes(UTF_8));
149 		assertArrayEquals(bos2.toByteArray(), IO.readFully(file.toFile()));
150 	}
151 
152 	@Test
153 	public void testIncludeAbsolute()
154 			throws IOException, ConfigInvalidException {
155 		final Path includedFile = createFile(CONTENT1.getBytes(UTF_8));
156 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
157 		bos.write("[include]\npath=".getBytes(UTF_8));
158 		bos.write(pathToString(includedFile.toFile()).getBytes(UTF_8));
159 
160 		final Path file = createFile(bos.toByteArray());
161 		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
162 				FS.DETECTED);
163 		config.load();
164 		assertEquals(ALICE, config.getString(USER, null, NAME));
165 	}
166 
167 	@Test
168 	public void testIncludeRelativeDot()
169 			throws IOException, ConfigInvalidException {
170 		final Path includedFile = createFile(CONTENT1.getBytes(UTF_8), "dir1");
171 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
172 		bos.write("[include]\npath=".getBytes(UTF_8));
173 		bos.write(("./" + includedFile.getFileName()).getBytes(UTF_8));
174 
175 		final Path file = createFile(bos.toByteArray(), "dir1");
176 		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
177 				FS.DETECTED);
178 		config.load();
179 		assertEquals(ALICE, config.getString(USER, null, NAME));
180 	}
181 
182 	@Test
183 	public void testIncludeRelativeDotDot()
184 			throws IOException, ConfigInvalidException {
185 		final Path includedFile = createFile(CONTENT1.getBytes(UTF_8), "dir1");
186 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
187 		bos.write("[include]\npath=".getBytes(UTF_8));
188 		bos.write(("../" + parent(includedFile).getFileName() + "/"
189 				+ includedFile.getFileName()).getBytes(UTF_8));
190 
191 		final Path file = createFile(bos.toByteArray(), "dir2");
192 		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
193 				FS.DETECTED);
194 		config.load();
195 		assertEquals(ALICE, config.getString(USER, null, NAME));
196 	}
197 
198 	@Test
199 	public void testIncludeRelativeDotDotNotFound()
200 			throws IOException, ConfigInvalidException {
201 		final Path includedFile = createFile(CONTENT1.getBytes(UTF_8));
202 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
203 		bos.write("[include]\npath=".getBytes(UTF_8));
204 		bos.write(("../" + includedFile.getFileName()).getBytes(UTF_8));
205 
206 		final Path file = createFile(bos.toByteArray());
207 		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
208 				FS.DETECTED);
209 		config.load();
210 		assertEquals(null, config.getString(USER, null, NAME));
211 	}
212 
213 	@Test
214 	public void testIncludeWithTilde()
215 			throws IOException, ConfigInvalidException {
216 		final Path includedFile = createFile(CONTENT1.getBytes(UTF_8), "home");
217 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
218 		bos.write("[include]\npath=".getBytes(UTF_8));
219 		bos.write(("~/" + includedFile.getFileName()).getBytes(UTF_8));
220 
221 		final Path file = createFile(bos.toByteArray(), "repo");
222 		final FS fs = FS.DETECTED.newInstance();
223 		fs.setUserHome(parent(includedFile).toFile());
224 
225 		final FileBasedConfig config = new FileBasedConfig(file.toFile(), fs);
226 		config.load();
227 		assertEquals(ALICE, config.getString(USER, null, NAME));
228 	}
229 
230 	@Test
231 	public void testIncludeDontInlineIncludedLinesOnSave()
232 			throws IOException, ConfigInvalidException {
233 		// use a content with multiple sections and multiple key/value pairs
234 		// because code for first line works different than for subsequent lines
235 		final Path includedFile = createFile(CONTENT3.getBytes(UTF_8), "dir1");
236 
237 		final Path file = createFile(new byte[0], "dir2");
238 		FileBasedConfig config = new FileBasedConfig(file.toFile(),
239 				FS.DETECTED);
240 		config.setString("include", null, "path",
241 				("../" + parent(includedFile).getFileName() + "/"
242 						+ includedFile.getFileName()));
243 
244 		// just by setting the include.path, it won't be included
245 		assertEquals(null, config.getString(USER, null, NAME));
246 		assertEquals(null, config.getString(USER, null, EMAIL));
247 		config.save();
248 
249 		// and it won't be included after saving
250 		assertEquals(null, config.getString(USER, null, NAME));
251 		assertEquals(null, config.getString(USER, null, EMAIL));
252 
253 		final String expectedText = config.toText();
254 		assertEquals(2,
255 				new StringTokenizer(expectedText, "\n", false).countTokens());
256 
257 		config = new FileBasedConfig(file.toFile(), FS.DETECTED);
258 		config.load();
259 
260 		String actualText = config.toText();
261 		assertEquals(expectedText, actualText);
262 		// but it will be included after (re)loading
263 		assertEquals(ALICE, config.getString(USER, null, NAME));
264 		assertEquals(ALICE_EMAIL, config.getString(USER, null, EMAIL));
265 
266 		config.save();
267 
268 		actualText = config.toText();
269 		assertEquals(expectedText, actualText);
270 		// and of course preserved after saving
271 		assertEquals(ALICE, config.getString(USER, null, NAME));
272 		assertEquals(ALICE_EMAIL, config.getString(USER, null, EMAIL));
273 	}
274 
275 	@Test
276 	public void testSavedConfigFileShouldNotReadUserGitConfig()
277 			throws IOException {
278 		AtomicBoolean userConfigTimeRead = new AtomicBoolean(false);
279 
280 		Path userConfigFile = createFile(
281 				CONTENT1.getBytes(StandardCharsets.UTF_8), "home");
282 		mockSystemReader.setUserGitConfig(
283 				new FileBasedConfig(userConfigFile.toFile(), FS.DETECTED) {
284 
285 					@Override
286 					public long getTimeUnit(String section, String subsection,
287 							String name, long defaultValue, TimeUnit wantUnit) {
288 						userConfigTimeRead.set(true);
289 						return super.getTimeUnit(section, subsection, name,
290 								defaultValue, wantUnit);
291 					}
292 				});
293 
294 		Path file = createFile(CONTENT2.getBytes(StandardCharsets.UTF_8),
295 				"repo");
296 		FileBasedConfig fileBasedConfig = new FileBasedConfig(file.toFile(),
297 				FS.DETECTED);
298 		fileBasedConfig.save();
299 
300 		// Needed to trigger the read of FileSnapshot filesystem settings
301 		fileBasedConfig.isOutdated();
302 		assertFalse(
303 				"User config should not be read when accessing config files "
304 						+ "for avoiding deadlocks",
305 				userConfigTimeRead.get());
306 	}
307 
308 	private Path createFile(byte[] content) throws IOException {
309 		return createFile(content, null);
310 	}
311 
312 	private Path createFile(byte[] content, String subdir) throws IOException {
313 		Path dir = subdir != null ? trash.resolve(subdir) : trash;
314 		Files.createDirectories(dir);
315 
316 		Path f = Files.createTempFile(dir, getClass().getName(), null);
317 		try (OutputStream os = Files.newOutputStream(f,
318 				StandardOpenOption.APPEND)) {
319 			os.write(content);
320 		}
321 		return f;
322 	}
323 
324 	private Path parent(Path file) {
325 		Path parent = file.getParent();
326 		assertNotNull(parent);
327 		return parent;
328 	}
329 }