View Javadoc
1   /*
2    * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3    * Copyright (C) 2007-2010, Robin Rosenberg <robin.rosenberg@dewire.com>
4    * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
5    * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> and others
6    *
7    * This program and the accompanying materials are made available under the
8    * terms of the Eclipse Distribution License v. 1.0 which is available at
9    * https://www.eclipse.org/org/documents/edl-v10.php.
10   *
11   * SPDX-License-Identifier: BSD-3-Clause
12   */
13  
14  package org.eclipse.jgit.internal.storage.file;
15  
16  import static java.nio.charset.StandardCharsets.ISO_8859_1;
17  import static java.nio.charset.StandardCharsets.UTF_8;
18  import static org.junit.Assert.assertEquals;
19  import static org.junit.Assert.assertFalse;
20  import static org.junit.Assert.assertNotNull;
21  import static org.junit.Assert.assertNotSame;
22  import static org.junit.Assert.assertThrows;
23  import static org.junit.Assert.assertTrue;
24  import static org.junit.Assert.fail;
25  
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.IOException;
29  import java.io.UnsupportedEncodingException;
30  import java.time.Instant;
31  
32  import org.eclipse.jgit.errors.ConfigInvalidException;
33  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
34  import org.eclipse.jgit.errors.MissingObjectException;
35  import org.eclipse.jgit.internal.JGitText;
36  import org.eclipse.jgit.lib.AnyObjectId;
37  import org.eclipse.jgit.lib.CommitBuilder;
38  import org.eclipse.jgit.lib.Constants;
39  import org.eclipse.jgit.lib.FileMode;
40  import org.eclipse.jgit.lib.ObjectDatabase;
41  import org.eclipse.jgit.lib.ObjectId;
42  import org.eclipse.jgit.lib.ObjectInserter;
43  import org.eclipse.jgit.lib.PersonIdent;
44  import org.eclipse.jgit.lib.RefUpdate;
45  import org.eclipse.jgit.lib.Repository;
46  import org.eclipse.jgit.lib.TagBuilder;
47  import org.eclipse.jgit.lib.TreeFormatter;
48  import org.eclipse.jgit.revwalk.RevCommit;
49  import org.eclipse.jgit.revwalk.RevTag;
50  import org.eclipse.jgit.revwalk.RevWalk;
51  import org.eclipse.jgit.storage.file.FileBasedConfig;
52  import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
53  import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
54  import org.eclipse.jgit.util.FS;
55  import org.eclipse.jgit.util.FileUtils;
56  import org.eclipse.jgit.util.IO;
57  import org.junit.Test;
58  
59  public class T0003_BasicTest extends SampleDataRepositoryTestCase {
60  
61  	@Test
62  	public void test001_Initalize() {
63  		final File gitdir = new File(trash, Constants.DOT_GIT);
64  		final File hooks = new File(gitdir, "hooks");
65  		final File objects = new File(gitdir, Constants.OBJECTS);
66  		final File objects_pack = new File(objects, "pack");
67  		final File objects_info = new File(objects, "info");
68  		final File refs = new File(gitdir, "refs");
69  		final File refs_heads = new File(refs, "heads");
70  		final File refs_tags = new File(refs, "tags");
71  		final File HEAD = new File(gitdir, "HEAD");
72  
73  		assertTrue("Exists " + trash, trash.isDirectory());
74  		assertTrue("Exists " + hooks, hooks.isDirectory());
75  		assertTrue("Exists " + objects, objects.isDirectory());
76  		assertTrue("Exists " + objects_pack, objects_pack.isDirectory());
77  		assertTrue("Exists " + objects_info, objects_info.isDirectory());
78  		assertEquals(2L, objects.listFiles().length);
79  		assertTrue("Exists " + refs, refs.isDirectory());
80  		assertTrue("Exists " + refs_heads, refs_heads.isDirectory());
81  		assertTrue("Exists " + refs_tags, refs_tags.isDirectory());
82  		assertTrue("Exists " + HEAD, HEAD.isFile());
83  		assertEquals(23, HEAD.length());
84  	}
85  
86  	@Test
87  	public void test000_openRepoBadArgs() throws IOException {
88  		try {
89  			new FileRepositoryBuilder().build();
90  			fail("Must pass either GIT_DIR or GIT_WORK_TREE");
91  		} catch (IllegalArgumentException e) {
92  			assertEquals(JGitText.get().eitherGitDirOrWorkTreeRequired, e
93  					.getMessage());
94  		}
95  	}
96  
97  	/**
98  	 * Check the default rules for looking up directories and files within a
99  	 * repo when the gitDir is given.
100 	 *
101 	 * @throws IOException
102 	 */
103 	@Test
104 	public void test000_openrepo_default_gitDirSet() throws IOException {
105 		File repo1Parent = new File(trash.getParentFile(), "r1");
106 		try (Repository repo1initial = new FileRepository(
107 				new File(repo1Parent, Constants.DOT_GIT))) {
108 			repo1initial.create();
109 		}
110 
111 		File theDir = new File(repo1Parent, Constants.DOT_GIT);
112 		try (FileRepository r = (FileRepository) new FileRepositoryBuilder()
113 				.setGitDir(theDir).build()) {
114 			assertEqualsPath(theDir, r.getDirectory());
115 			assertEqualsPath(repo1Parent, r.getWorkTree());
116 			assertEqualsPath(new File(theDir, "index"), r.getIndexFile());
117 			assertEqualsPath(new File(theDir, Constants.OBJECTS),
118 					r.getObjectDatabase().getDirectory());
119 		}
120 	}
121 
122 	/**
123 	 * Check that we can pass both a git directory and a work tree repo when the
124 	 * gitDir is given.
125 	 *
126 	 * @throws IOException
127 	 */
128 	@Test
129 	public void test000_openrepo_default_gitDirAndWorkTreeSet()
130 			throws IOException {
131 		File repo1Parent = new File(trash.getParentFile(), "r1");
132 		try (Repository repo1initial = new FileRepository(
133 				new File(repo1Parent, Constants.DOT_GIT))) {
134 			repo1initial.create();
135 		}
136 
137 		File theDir = new File(repo1Parent, Constants.DOT_GIT);
138 		try (FileRepository r = (FileRepository) new FileRepositoryBuilder()
139 				.setGitDir(theDir).setWorkTree(repo1Parent.getParentFile())
140 				.build()) {
141 			assertEqualsPath(theDir, r.getDirectory());
142 			assertEqualsPath(repo1Parent.getParentFile(), r.getWorkTree());
143 			assertEqualsPath(new File(theDir, "index"), r.getIndexFile());
144 			assertEqualsPath(new File(theDir, Constants.OBJECTS),
145 					r.getObjectDatabase().getDirectory());
146 		}
147 	}
148 
149 	/**
150 	 * Check the default rules for looking up directories and files within a
151 	 * repo when the workTree is given.
152 	 *
153 	 * @throws IOException
154 	 */
155 	@Test
156 	public void test000_openrepo_default_workDirSet() throws IOException {
157 		File repo1Parent = new File(trash.getParentFile(), "r1");
158 		try (Repository repo1initial = new FileRepository(
159 				new File(repo1Parent, Constants.DOT_GIT))) {
160 			repo1initial.create();
161 		}
162 
163 		File theDir = new File(repo1Parent, Constants.DOT_GIT);
164 		try (FileRepository r = (FileRepository) new FileRepositoryBuilder()
165 				.setWorkTree(repo1Parent).build()) {
166 			assertEqualsPath(theDir, r.getDirectory());
167 			assertEqualsPath(repo1Parent, r.getWorkTree());
168 			assertEqualsPath(new File(theDir, "index"), r.getIndexFile());
169 			assertEqualsPath(new File(theDir, Constants.OBJECTS),
170 					r.getObjectDatabase().getDirectory());
171 		}
172 	}
173 
174 	/**
175 	 * Check that worktree config has an effect, given absolute path.
176 	 *
177 	 * @throws IOException
178 	 */
179 	@Test
180 	public void test000_openrepo_default_absolute_workdirconfig()
181 			throws IOException {
182 		File repo1Parent = new File(trash.getParentFile(), "r1");
183 		File workdir = new File(trash.getParentFile(), "rw");
184 		FileUtils.mkdir(workdir);
185 		try (FileRepository repo1initial = new FileRepository(
186 				new File(repo1Parent, Constants.DOT_GIT))) {
187 			repo1initial.create();
188 			final FileBasedConfig cfg = repo1initial.getConfig();
189 			cfg.setString("core", null, "worktree", workdir.getAbsolutePath());
190 			cfg.save();
191 		}
192 
193 		File theDir = new File(repo1Parent, Constants.DOT_GIT);
194 		try (FileRepository r = (FileRepository) new FileRepositoryBuilder()
195 				.setGitDir(theDir).build()) {
196 			assertEqualsPath(theDir, r.getDirectory());
197 			assertEqualsPath(workdir, r.getWorkTree());
198 			assertEqualsPath(new File(theDir, "index"), r.getIndexFile());
199 			assertEqualsPath(new File(theDir, Constants.OBJECTS),
200 					r.getObjectDatabase().getDirectory());
201 		}
202 	}
203 
204 	/**
205 	 * Check that worktree config has an effect, given a relative path.
206 	 *
207 	 * @throws IOException
208 	 */
209 	@Test
210 	public void test000_openrepo_default_relative_workdirconfig()
211 			throws IOException {
212 		File repo1Parent = new File(trash.getParentFile(), "r1");
213 		File workdir = new File(trash.getParentFile(), "rw");
214 		FileUtils.mkdir(workdir);
215 		try (FileRepository repo1initial = new FileRepository(
216 				new File(repo1Parent, Constants.DOT_GIT))) {
217 			repo1initial.create();
218 			final FileBasedConfig cfg = repo1initial.getConfig();
219 			cfg.setString("core", null, "worktree", "../../rw");
220 			cfg.save();
221 		}
222 
223 		File theDir = new File(repo1Parent, Constants.DOT_GIT);
224 		try (FileRepository r = (FileRepository) new FileRepositoryBuilder()
225 				.setGitDir(theDir).build()) {
226 			assertEqualsPath(theDir, r.getDirectory());
227 			assertEqualsPath(workdir, r.getWorkTree());
228 			assertEqualsPath(new File(theDir, "index"), r.getIndexFile());
229 			assertEqualsPath(new File(theDir, Constants.OBJECTS),
230 					r.getObjectDatabase().getDirectory());
231 		}
232 	}
233 
234 	/**
235 	 * Check that the given index file is honored and the alternate object
236 	 * directories too
237 	 *
238 	 * @throws IOException
239 	 */
240 	@Test
241 	public void test000_openrepo_alternate_index_file_and_objdirs()
242 			throws IOException {
243 		File repo1Parent = new File(trash.getParentFile(), "r1");
244 		File indexFile = new File(trash, "idx");
245 		File objDir = new File(trash, "../obj");
246 		File altObjDir = db.getObjectDatabase().getDirectory();
247 		try (Repository repo1initial = new FileRepository(
248 				new File(repo1Parent, Constants.DOT_GIT))) {
249 			repo1initial.create();
250 		}
251 
252 		File theDir = new File(repo1Parent, Constants.DOT_GIT);
253 		try (FileRepository r = (FileRepository) new FileRepositoryBuilder() //
254 				.setGitDir(theDir).setObjectDirectory(objDir) //
255 				.addAlternateObjectDirectory(altObjDir) //
256 				.setIndexFile(indexFile) //
257 				.build()) {
258 			assertEqualsPath(theDir, r.getDirectory());
259 			assertEqualsPath(theDir.getParentFile(), r.getWorkTree());
260 			assertEqualsPath(indexFile, r.getIndexFile());
261 			assertEqualsPath(objDir, r.getObjectDatabase().getDirectory());
262 			assertNotNull(r.open(ObjectId
263 					.fromString("6db9c2ebf75590eef973081736730a9ea169a0c4")));
264 		}
265 	}
266 
267 	protected void assertEqualsPath(File expected, File actual)
268 			throws IOException {
269 		assertEquals(expected.getCanonicalPath(), actual.getCanonicalPath());
270 	}
271 
272 	@Test
273 	public void test002_WriteEmptyTree() throws IOException {
274 		// One of our test packs contains the empty tree object. If the pack is
275 		// open when we create it we won't write the object file out as a loose
276 		// object (as it already exists in the pack).
277 		//
278 		try (Repository newdb = createBareRepository()) {
279 			try (ObjectInserter oi = newdb.newObjectInserter()) {
280 				final ObjectId treeId = oi.insert(new TreeFormatter());
281 				assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904",
282 						treeId.name());
283 			}
284 
285 			final File o = new File(
286 					new File(new File(newdb.getDirectory(), Constants.OBJECTS),
287 							"4b"),
288 					"825dc642cb6eb9a060e54bf8d69288fbee4904");
289 			assertTrue("Exists " + o, o.isFile());
290 			assertTrue("Read-only " + o, !o.canWrite());
291 		}
292 	}
293 
294 	@Test
295 	public void test002_WriteEmptyTree2() throws IOException {
296 		// File shouldn't exist as it is in a test pack.
297 		//
298 		final ObjectId treeId = insertTree(new TreeFormatter());
299 		assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904", treeId.name());
300 		final File o = new File(new File(
301 				new File(db.getDirectory(), Constants.OBJECTS), "4b"),
302 				"825dc642cb6eb9a060e54bf8d69288fbee4904");
303 		assertFalse("Exists " + o, o.isFile());
304 	}
305 
306 	@Test
307 	public void test002_CreateBadTree() throws Exception {
308 		// We won't create a tree entry with an empty filename
309 		//
310 		final TreeFormatter formatter = new TreeFormatter();
311 		assertThrows(JGitText.get().invalidTreeZeroLengthName,
312 				IllegalArgumentException.class,
313 				() -> formatter.append("", FileMode.TREE, ObjectId.fromString(
314 						"4b825dc642cb6eb9a060e54bf8d69288fbee4904")));
315 	}
316 
317 	@Test
318 	public void test006_ReadUglyConfig() throws IOException,
319 			ConfigInvalidException {
320 		final File cfg = new File(db.getDirectory(), Constants.CONFIG);
321 		final FileBasedConfig c = new FileBasedConfig(cfg, db.getFS());
322 		final String configStr = "  [core];comment\n\tfilemode = yes\n"
323 				+ "[user]\n"
324 				+ "  email = A U Thor <thor@example.com> # Just an example...\n"
325 				+ " name = \"A  Thor \\\\ \\\"\\t \"\n"
326 				+ "    defaultCheckInComment = a many line\\n\\\ncomment\\n\\\n"
327 				+ " to test\n";
328 		write(cfg, configStr);
329 		c.load();
330 		assertEquals("yes", c.getString("core", null, "filemode"));
331 		assertEquals("A U Thor <thor@example.com>", c.getString("user", null,
332 				"email"));
333 		assertEquals("A  Thor \\ \"\t ", c.getString("user", null, "name"));
334 		assertEquals("a many line\ncomment\n to test", c.getString("user",
335 				null, "defaultCheckInComment"));
336 		c.save();
337 
338 		// Saving normalizes out the weird "\\n\\\n" to a single escaped newline,
339 		// and quotes the whole string.
340 		final String expectedStr = "  [core];comment\n\tfilemode = yes\n"
341 				+ "[user]\n"
342 				+ "  email = A U Thor <thor@example.com> # Just an example...\n"
343 				+ " name = \"A  Thor \\\\ \\\"\\t \"\n"
344 				+ "    defaultCheckInComment = a many line\\ncomment\\n to test\n";
345 		assertEquals(expectedStr, new String(IO.readFully(cfg), UTF_8));
346 	}
347 
348 	@Test
349 	public void test007_Open() throws IOException {
350 		try (FileRepository db2 = new FileRepository(db.getDirectory())) {
351 			assertEquals(db.getDirectory(), db2.getDirectory());
352 			assertEquals(db.getObjectDatabase().getDirectory(), db2
353 					.getObjectDatabase().getDirectory());
354 			assertNotSame(db.getConfig(), db2.getConfig());
355 		}
356 	}
357 
358 	@Test
359 	public void test008_FailOnWrongVersion() throws IOException {
360 		final File cfg = new File(db.getDirectory(), Constants.CONFIG);
361 		final String badvers = "ihopethisisneveraversion";
362 		final String configStr = "[core]\n" + "\trepositoryFormatVersion="
363 				+ badvers + "\n";
364 		write(cfg, configStr);
365 
366 		try (FileRepository unused = new FileRepository(db.getDirectory())) {
367 			fail("incorrectly opened a bad repository");
368 		} catch (IllegalArgumentException ioe) {
369 			assertNotNull(ioe.getMessage());
370 		}
371 	}
372 
373 	@Test
374 	public void test009_CreateCommitOldFormat() throws IOException {
375 		final ObjectId treeId = insertTree(new TreeFormatter());
376 		final CommitBuilder c = new CommitBuilder();
377 		c.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60));
378 		c.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60));
379 		c.setMessage("A Commit\n");
380 		c.setTreeId(treeId);
381 		assertEquals(treeId, c.getTreeId());
382 
383 		ObjectId actid = insertCommit(c);
384 
385 		final ObjectId cmtid = ObjectId
386 				.fromString("9208b2459ea6609a5af68627cc031796d0d9329b");
387 		assertEquals(cmtid, actid);
388 
389 		// Verify the commit we just wrote is in the correct format.
390 		ObjectDatabase odb = db.getObjectDatabase();
391 		assertTrue("is ObjectDirectory", odb instanceof ObjectDirectory);
392 		try (XInputStream xis = new XInputStream(
393 				new FileInputStream(((ObjectDirectory) odb).fileFor(cmtid)))) {
394 			assertEquals(0x78, xis.readUInt8());
395 			assertEquals(0x9c, xis.readUInt8());
396 			assertEquals(0, 0x789c % 31);
397 		}
398 
399 		// Verify we can read it.
400 		RevCommit c2 = parseCommit(actid);
401 		assertNotNull(c2);
402 		assertEquals(c.getMessage(), c2.getFullMessage());
403 		assertEquals(c.getTreeId(), c2.getTree());
404 		assertEquals(c.getAuthor(), c2.getAuthorIdent());
405 		assertEquals(c.getCommitter(), c2.getCommitterIdent());
406 	}
407 
408 	@Test
409 	public void test020_createBlobTag() throws IOException {
410 		final ObjectId emptyId = insertEmptyBlob();
411 		final TagBuilder t = new TagBuilder();
412 		t.setObjectId(emptyId, Constants.OBJ_BLOB);
413 		t.setTag("test020");
414 		t.setTagger(new PersonIdent(author, 1154236443000L, -4 * 60));
415 		t.setMessage("test020 tagged\n");
416 		ObjectId actid = insertTag(t);
417 		assertEquals("6759556b09fbb4fd8ae5e315134481cc25d46954", actid.name());
418 
419 		RevTag mapTag = parseTag(actid);
420 		assertEquals(Constants.OBJ_BLOB, mapTag.getObject().getType());
421 		assertEquals("test020 tagged\n", mapTag.getFullMessage());
422 		assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag
423 				.getTaggerIdent());
424 		assertEquals("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", mapTag
425 				.getObject().getId().name());
426 	}
427 
428 	@Test
429 	public void test021_createTreeTag() throws IOException {
430 		final ObjectId emptyId = insertEmptyBlob();
431 		TreeFormatter almostEmptyTree = new TreeFormatter();
432 		almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId);
433 		final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree);
434 		final TagBuilder t = new TagBuilder();
435 		t.setObjectId(almostEmptyTreeId, Constants.OBJ_TREE);
436 		t.setTag("test021");
437 		t.setTagger(new PersonIdent(author, 1154236443000L, -4 * 60));
438 		t.setMessage("test021 tagged\n");
439 		ObjectId actid = insertTag(t);
440 		assertEquals("b0517bc8dbe2096b419d42424cd7030733f4abe5", actid.name());
441 
442 		RevTag mapTag = parseTag(actid);
443 		assertEquals(Constants.OBJ_TREE, mapTag.getObject().getType());
444 		assertEquals("test021 tagged\n", mapTag.getFullMessage());
445 		assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag
446 				.getTaggerIdent());
447 		assertEquals("417c01c8795a35b8e835113a85a5c0c1c77f67fb", mapTag
448 				.getObject().getId().name());
449 	}
450 
451 	@Test
452 	public void test022_createCommitTag() throws IOException {
453 		final ObjectId emptyId = insertEmptyBlob();
454 		TreeFormatter almostEmptyTree = new TreeFormatter();
455 		almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId);
456 		final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree);
457 		final CommitBuilder almostEmptyCommit = new CommitBuilder();
458 		almostEmptyCommit.setAuthor(new PersonIdent(author, 1154236443000L,
459 				-2 * 60)); // not exactly the same
460 		almostEmptyCommit.setCommitter(new PersonIdent(author, 1154236443000L,
461 				-2 * 60));
462 		almostEmptyCommit.setMessage("test022\n");
463 		almostEmptyCommit.setTreeId(almostEmptyTreeId);
464 		ObjectId almostEmptyCommitId = insertCommit(almostEmptyCommit);
465 		final TagBuilder t = new TagBuilder();
466 		t.setObjectId(almostEmptyCommitId, Constants.OBJ_COMMIT);
467 		t.setTag("test022");
468 		t.setTagger(new PersonIdent(author, 1154236443000L, -4 * 60));
469 		t.setMessage("test022 tagged\n");
470 		ObjectId actid = insertTag(t);
471 		assertEquals("0ce2ebdb36076ef0b38adbe077a07d43b43e3807", actid.name());
472 
473 		RevTag mapTag = parseTag(actid);
474 		assertEquals(Constants.OBJ_COMMIT, mapTag.getObject().getType());
475 		assertEquals("test022 tagged\n", mapTag.getFullMessage());
476 		assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag
477 				.getTaggerIdent());
478 		assertEquals("b5d3b45a96b340441f5abb9080411705c51cc86c", mapTag
479 				.getObject().getId().name());
480 	}
481 
482 	@Test
483 	public void test023_createCommitNonAnullii() throws IOException {
484 		final ObjectId emptyId = insertEmptyBlob();
485 		TreeFormatter almostEmptyTree = new TreeFormatter();
486 		almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId);
487 		final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree);
488 		CommitBuilder commit = new CommitBuilder();
489 		commit.setTreeId(almostEmptyTreeId);
490 		commit.setAuthor(new PersonIdent("Joe H\u00e4cker", "joe@example.com",
491 				4294967295000L, 60));
492 		commit.setCommitter(new PersonIdent("Joe Hacker", "joe2@example.com",
493 				4294967295000L, 60));
494 		commit.setEncoding(UTF_8);
495 		commit.setMessage("\u00dcbergeeks");
496 		ObjectId cid = insertCommit(commit);
497 		assertEquals("4680908112778718f37e686cbebcc912730b3154", cid.name());
498 
499 		RevCommit loadedCommit = parseCommit(cid);
500 		assertEquals(commit.getMessage(), loadedCommit.getFullMessage());
501 	}
502 
503 	@Test
504 	public void test024_createCommitNonAscii() throws IOException {
505 		final ObjectId emptyId = insertEmptyBlob();
506 		TreeFormatter almostEmptyTree = new TreeFormatter();
507 		almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId);
508 		final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree);
509 		CommitBuilder commit = new CommitBuilder();
510 		commit.setTreeId(almostEmptyTreeId);
511 		commit.setAuthor(new PersonIdent("Joe H\u00e4cker", "joe@example.com",
512 				4294967295000L, 60));
513 		commit.setCommitter(new PersonIdent("Joe Hacker", "joe2@example.com",
514 				4294967295000L, 60));
515 		commit.setEncoding(ISO_8859_1);
516 		commit.setMessage("\u00dcbergeeks");
517 		ObjectId cid = insertCommit(commit);
518 		assertEquals("2979b39d385014b33287054b87f77bcb3ecb5ebf", cid.name());
519 	}
520 
521 	@Test
522 	public void test025_computeSha1NoStore() {
523 		byte[] data = "test025 some data, more than 16 bytes to get good coverage"
524 				.getBytes(ISO_8859_1);
525 		try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) {
526 			final ObjectId id = formatter.idFor(Constants.OBJ_BLOB, data);
527 			assertEquals("4f561df5ecf0dfbd53a0dc0f37262fef075d9dde", id.name());
528 		}
529 	}
530 
531 	@Test
532 	public void test026_CreateCommitMultipleparents() throws IOException {
533 		final ObjectId treeId;
534 		try (ObjectInserter oi = db.newObjectInserter()) {
535 			final ObjectId blobId = oi.insert(Constants.OBJ_BLOB,
536 					"and this is the data in me\n".getBytes(UTF_8
537 							.name()));
538 			TreeFormatter fmt = new TreeFormatter();
539 			fmt.append("i-am-a-file", FileMode.REGULAR_FILE, blobId);
540 			treeId = oi.insert(fmt);
541 			oi.flush();
542 		}
543 		assertEquals(ObjectId
544 				.fromString("00b1f73724f493096d1ffa0b0f1f1482dbb8c936"), treeId);
545 
546 		final CommitBuilder c1 = new CommitBuilder();
547 		c1.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60));
548 		c1.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60));
549 		c1.setMessage("A Commit\n");
550 		c1.setTreeId(treeId);
551 		assertEquals(treeId, c1.getTreeId());
552 		ObjectId actid1 = insertCommit(c1);
553 		final ObjectId cmtid1 = ObjectId
554 				.fromString("803aec4aba175e8ab1d666873c984c0308179099");
555 		assertEquals(cmtid1, actid1);
556 
557 		final CommitBuilder c2 = new CommitBuilder();
558 		c2.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60));
559 		c2.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60));
560 		c2.setMessage("A Commit 2\n");
561 		c2.setTreeId(treeId);
562 		assertEquals(treeId, c2.getTreeId());
563 		c2.setParentIds(actid1);
564 		ObjectId actid2 = insertCommit(c2);
565 		final ObjectId cmtid2 = ObjectId
566 				.fromString("95d068687c91c5c044fb8c77c5154d5247901553");
567 		assertEquals(cmtid2, actid2);
568 
569 		RevCommit rm2 = parseCommit(cmtid2);
570 		assertNotSame(c2, rm2); // assert the parsed objects is not from the
571 		// cache
572 		assertEquals(c2.getAuthor(), rm2.getAuthorIdent());
573 		assertEquals(actid2, rm2.getId());
574 		assertEquals(c2.getMessage(), rm2.getFullMessage());
575 		assertEquals(c2.getTreeId(), rm2.getTree().getId());
576 		assertEquals(1, rm2.getParentCount());
577 		assertEquals(actid1, rm2.getParent(0));
578 
579 		final CommitBuilder c3 = new CommitBuilder();
580 		c3.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60));
581 		c3.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60));
582 		c3.setMessage("A Commit 3\n");
583 		c3.setTreeId(treeId);
584 		assertEquals(treeId, c3.getTreeId());
585 		c3.setParentIds(actid1, actid2);
586 		ObjectId actid3 = insertCommit(c3);
587 		final ObjectId cmtid3 = ObjectId
588 				.fromString("ce6e1ce48fbeeb15a83f628dc8dc2debefa066f4");
589 		assertEquals(cmtid3, actid3);
590 
591 		RevCommit rm3 = parseCommit(cmtid3);
592 		assertNotSame(c3, rm3); // assert the parsed objects is not from the
593 		// cache
594 		assertEquals(c3.getAuthor(), rm3.getAuthorIdent());
595 		assertEquals(actid3, rm3.getId());
596 		assertEquals(c3.getMessage(), rm3.getFullMessage());
597 		assertEquals(c3.getTreeId(), rm3.getTree().getId());
598 		assertEquals(2, rm3.getParentCount());
599 		assertEquals(actid1, rm3.getParent(0));
600 		assertEquals(actid2, rm3.getParent(1));
601 
602 		final CommitBuilder c4 = new CommitBuilder();
603 		c4.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60));
604 		c4.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60));
605 		c4.setMessage("A Commit 4\n");
606 		c4.setTreeId(treeId);
607 		assertEquals(treeId, c3.getTreeId());
608 		c4.setParentIds(actid1, actid2, actid3);
609 		ObjectId actid4 = insertCommit(c4);
610 		final ObjectId cmtid4 = ObjectId
611 				.fromString("d1fca9fe3fef54e5212eb67902c8ed3e79736e27");
612 		assertEquals(cmtid4, actid4);
613 
614 		RevCommit rm4 = parseCommit(cmtid4);
615 		assertNotSame(c4, rm3); // assert the parsed objects is not from the
616 		// cache
617 		assertEquals(c4.getAuthor(), rm4.getAuthorIdent());
618 		assertEquals(actid4, rm4.getId());
619 		assertEquals(c4.getMessage(), rm4.getFullMessage());
620 		assertEquals(c4.getTreeId(), rm4.getTree().getId());
621 		assertEquals(3, rm4.getParentCount());
622 		assertEquals(actid1, rm4.getParent(0));
623 		assertEquals(actid2, rm4.getParent(1));
624 		assertEquals(actid3, rm4.getParent(2));
625 	}
626 
627 	@Test
628 	public void test027_UnpackedRefHigherPriorityThanPacked()
629 			throws IOException {
630 		String unpackedId = "7f822839a2fe9760f386cbbbcb3f92c5fe81def7";
631 		write(new File(db.getDirectory(), "refs/heads/a"), unpackedId + "\n");
632 
633 		ObjectId resolved = db.resolve("refs/heads/a");
634 		assertEquals(unpackedId, resolved.name());
635 	}
636 
637 	@Test
638 	public void test028_LockPackedRef() throws IOException {
639 		ObjectId id1;
640 		ObjectId id2;
641 		try (ObjectInserter ins = db.newObjectInserter()) {
642 			id1 = ins.insert(
643 					Constants.OBJ_BLOB, "contents1".getBytes(UTF_8));
644 			id2 = ins.insert(
645 					Constants.OBJ_BLOB, "contents2".getBytes(UTF_8));
646 			ins.flush();
647 		}
648 
649 		writeTrashFile(".git/packed-refs",
650 				id1.name() + " refs/heads/foobar");
651 		writeTrashFile(".git/HEAD", "ref: refs/heads/foobar\n");
652 		BUG_WorkAroundRacyGitIssues("packed-refs");
653 		BUG_WorkAroundRacyGitIssues("HEAD");
654 
655 		ObjectId resolve = db.resolve("HEAD");
656 		assertEquals(id1, resolve);
657 
658 		RefUpdate lockRef = db.updateRef("HEAD");
659 		lockRef.setNewObjectId(id2);
660 		assertEquals(RefUpdate.Result.FORCED, lockRef.forceUpdate());
661 
662 		assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists());
663 		assertEquals(id2, db.resolve("refs/heads/foobar"));
664 
665 		// Again. The ref already exists
666 		RefUpdate lockRef2 = db.updateRef("HEAD");
667 		lockRef2.setNewObjectId(id1);
668 		assertEquals(RefUpdate.Result.FORCED, lockRef2.forceUpdate());
669 
670 		assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists());
671 		assertEquals(id1, db.resolve("refs/heads/foobar"));
672 	}
673 
674 	@Test
675 	public void test30_stripWorkDir() {
676 		File relCwd = new File(".");
677 		File absCwd = relCwd.getAbsoluteFile();
678 		File absBase = new File(new File(absCwd, "repo"), "workdir");
679 		File relBase = new File(new File(relCwd, "repo"), "workdir");
680 		assertEquals(absBase.getAbsolutePath(), relBase.getAbsolutePath());
681 
682 		File relBaseFile = new File(new File(relBase, "other"), "module.c");
683 		File absBaseFile = new File(new File(absBase, "other"), "module.c");
684 		assertEquals("other/module.c", Repository.stripWorkDir(relBase,
685 				relBaseFile));
686 		assertEquals("other/module.c", Repository.stripWorkDir(relBase,
687 				absBaseFile));
688 		assertEquals("other/module.c", Repository.stripWorkDir(absBase,
689 				relBaseFile));
690 		assertEquals("other/module.c", Repository.stripWorkDir(absBase,
691 				absBaseFile));
692 
693 		File relNonFile = new File(new File(relCwd, "not-repo"), ".gitignore");
694 		File absNonFile = new File(new File(absCwd, "not-repo"), ".gitignore");
695 		assertEquals("", Repository.stripWorkDir(relBase, relNonFile));
696 		assertEquals("", Repository.stripWorkDir(absBase, absNonFile));
697 
698 		assertEquals("", Repository.stripWorkDir(db.getWorkTree(), db
699 				.getWorkTree()));
700 
701 		File file = new File(new File(db.getWorkTree(), "subdir"), "File.java");
702 		assertEquals("subdir/File.java", Repository.stripWorkDir(db
703 				.getWorkTree(), file));
704 
705 	}
706 
707 	private ObjectId insertEmptyBlob() throws IOException {
708 		final ObjectId emptyId;
709 		try (ObjectInserter oi = db.newObjectInserter()) {
710 			emptyId = oi.insert(Constants.OBJ_BLOB, new byte[] {});
711 			oi.flush();
712 		}
713 		return emptyId;
714 	}
715 
716 	private ObjectId insertTree(TreeFormatter tree) throws IOException {
717 		try (ObjectInserter oi = db.newObjectInserter()) {
718 			ObjectId id = oi.insert(tree);
719 			oi.flush();
720 			return id;
721 		}
722 	}
723 
724 	private ObjectId insertCommit(CommitBuilder builder)
725 			throws IOException, UnsupportedEncodingException {
726 		try (ObjectInserter oi = db.newObjectInserter()) {
727 			ObjectId id = oi.insert(builder);
728 			oi.flush();
729 			return id;
730 		}
731 	}
732 
733 	private RevCommit parseCommit(AnyObjectId id)
734 			throws MissingObjectException, IncorrectObjectTypeException,
735 			IOException {
736 		try (RevWalk rw = new RevWalk(db)) {
737 			return rw.parseCommit(id);
738 		}
739 	}
740 
741 	private ObjectId insertTag(TagBuilder tag) throws IOException,
742 			UnsupportedEncodingException {
743 		try (ObjectInserter oi = db.newObjectInserter()) {
744 			ObjectId id = oi.insert(tag);
745 			oi.flush();
746 			return id;
747 		}
748 	}
749 
750 	private RevTag parseTag(AnyObjectId id) throws MissingObjectException,
751 			IncorrectObjectTypeException, IOException {
752 		try (RevWalk rw = new RevWalk(db)) {
753 			return rw.parseTag(id);
754 		}
755 	}
756 
757 	/**
758 	 * Kick the timestamp of a local file.
759 	 * <p>
760 	 * We shouldn't have to make these method calls. The cache is using file
761 	 * system timestamps, and on many systems unit tests run faster than the
762 	 * modification clock. Dumping the cache after we make an edit behind
763 	 * RefDirectory's back allows the tests to pass.
764 	 *
765 	 * @param name
766 	 *            the file in the repository to force a time change on.
767 	 * @throws IOException
768 	 */
769 	private void BUG_WorkAroundRacyGitIssues(String name) throws IOException {
770 		File path = new File(db.getDirectory(), name);
771 		FS fs = db.getFS();
772 		Instant old = fs.lastModifiedInstant(path);
773 		long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009
774 		fs.setLastModified(path.toPath(), Instant.ofEpochMilli(set));
775 		assertFalse("time changed", old.equals(fs.lastModifiedInstant(path)));
776 	}
777 }