View Javadoc
1   /*
2    * Copyright (C) 2009-2010, Google Inc. 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  
11  package org.eclipse.jgit.junit;
12  
13  import static java.nio.charset.StandardCharsets.UTF_8;
14  import static org.junit.Assert.assertEquals;
15  import static org.junit.Assert.fail;
16  
17  import java.io.BufferedOutputStream;
18  import java.io.File;
19  import java.io.FileOutputStream;
20  import java.io.IOException;
21  import java.io.OutputStream;
22  import java.security.MessageDigest;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collections;
26  import java.util.Date;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Set;
30  import java.util.TimeZone;
31  
32  import org.eclipse.jgit.api.Git;
33  import org.eclipse.jgit.dircache.DirCache;
34  import org.eclipse.jgit.dircache.DirCacheBuilder;
35  import org.eclipse.jgit.dircache.DirCacheEditor;
36  import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
37  import org.eclipse.jgit.dircache.DirCacheEditor.DeleteTree;
38  import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
39  import org.eclipse.jgit.dircache.DirCacheEntry;
40  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
41  import org.eclipse.jgit.errors.MissingObjectException;
42  import org.eclipse.jgit.errors.ObjectWritingException;
43  import org.eclipse.jgit.internal.storage.file.FileRepository;
44  import org.eclipse.jgit.internal.storage.file.LockFile;
45  import org.eclipse.jgit.internal.storage.file.ObjectDirectory;
46  import org.eclipse.jgit.internal.storage.file.PackFile;
47  import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
48  import org.eclipse.jgit.internal.storage.pack.PackWriter;
49  import org.eclipse.jgit.lib.AnyObjectId;
50  import org.eclipse.jgit.lib.Constants;
51  import org.eclipse.jgit.lib.FileMode;
52  import org.eclipse.jgit.lib.NullProgressMonitor;
53  import org.eclipse.jgit.lib.ObjectChecker;
54  import org.eclipse.jgit.lib.ObjectId;
55  import org.eclipse.jgit.lib.ObjectInserter;
56  import org.eclipse.jgit.lib.PersonIdent;
57  import org.eclipse.jgit.lib.Ref;
58  import org.eclipse.jgit.lib.RefUpdate;
59  import org.eclipse.jgit.lib.RefWriter;
60  import org.eclipse.jgit.lib.Repository;
61  import org.eclipse.jgit.lib.TagBuilder;
62  import org.eclipse.jgit.merge.MergeStrategy;
63  import org.eclipse.jgit.merge.ThreeWayMerger;
64  import org.eclipse.jgit.revwalk.ObjectWalk;
65  import org.eclipse.jgit.revwalk.RevBlob;
66  import org.eclipse.jgit.revwalk.RevCommit;
67  import org.eclipse.jgit.revwalk.RevObject;
68  import org.eclipse.jgit.revwalk.RevTag;
69  import org.eclipse.jgit.revwalk.RevTree;
70  import org.eclipse.jgit.revwalk.RevWalk;
71  import org.eclipse.jgit.treewalk.TreeWalk;
72  import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
73  import org.eclipse.jgit.util.ChangeIdUtil;
74  import org.eclipse.jgit.util.FileUtils;
75  
76  /**
77   * Wrapper to make creating test data easier.
78   *
79   * @param <R>
80   *            type of Repository the test data is stored on.
81   */
82  public class TestRepository<R extends Repository> implements AutoCloseable {
83  
84  	/** Constant <code>AUTHOR="J. Author"</code> */
85  	public static final String AUTHOR = "J. Author";
86  
87  	/** Constant <code>AUTHOR_EMAIL="jauthor@example.com"</code> */
88  	public static final String AUTHOR_EMAIL = "jauthor@example.com";
89  
90  	/** Constant <code>COMMITTER="J. Committer"</code> */
91  	public static final String COMMITTER = "J. Committer";
92  
93  	/** Constant <code>COMMITTER_EMAIL="jcommitter@example.com"</code> */
94  	public static final String COMMITTER_EMAIL = "jcommitter@example.com";
95  
96  	private final PersonIdent defaultAuthor;
97  
98  	private final PersonIdent defaultCommitter;
99  
100 	private final R db;
101 
102 	private final Git git;
103 
104 	private final RevWalk pool;
105 
106 	private final ObjectInserter inserter;
107 
108 	private final MockSystemReader mockSystemReader;
109 
110 	/**
111 	 * Wrap a repository with test building tools.
112 	 *
113 	 * @param db
114 	 *            the test repository to write into.
115 	 * @throws IOException
116 	 */
117 	public TestRepository(R db) throws IOException {
118 		this(db, new RevWalk(db), new MockSystemReader());
119 	}
120 
121 	/**
122 	 * Wrap a repository with test building tools.
123 	 *
124 	 * @param db
125 	 *            the test repository to write into.
126 	 * @param rw
127 	 *            the RevObject pool to use for object lookup.
128 	 * @throws IOException
129 	 */
130 	public TestRepository(R db, RevWalk rw) throws IOException {
131 		this(db, rw, new MockSystemReader());
132 	}
133 
134 	/**
135 	 * Wrap a repository with test building tools.
136 	 *
137 	 * @param db
138 	 *            the test repository to write into.
139 	 * @param rw
140 	 *            the RevObject pool to use for object lookup.
141 	 * @param reader
142 	 *            the MockSystemReader to use for clock and other system
143 	 *            operations.
144 	 * @throws IOException
145 	 * @since 4.2
146 	 */
147 	public TestRepository(R db, RevWalk rw, MockSystemReader reader)
148 			throws IOException {
149 		this.db = db;
150 		this.git = Git.wrap(db);
151 		this.pool = rw;
152 		this.inserter = db.newObjectInserter();
153 		this.mockSystemReader = reader;
154 		long now = mockSystemReader.getCurrentTime();
155 		int tz = mockSystemReader.getTimezone(now);
156 		defaultAuthor = new PersonIdent(AUTHOR, AUTHOR_EMAIL, now, tz);
157 		defaultCommitter = new PersonIdent(COMMITTER, COMMITTER_EMAIL, now, tz);
158 	}
159 
160 	/**
161 	 * Get repository
162 	 *
163 	 * @return the repository this helper class operates against.
164 	 */
165 	public R getRepository() {
166 		return db;
167 	}
168 
169 	/**
170 	 * Get RevWalk
171 	 *
172 	 * @return get the RevWalk pool all objects are allocated through.
173 	 */
174 	public RevWalk getRevWalk() {
175 		return pool;
176 	}
177 
178 	/**
179 	 * Return Git API wrapper
180 	 *
181 	 * @return an API wrapper for the underlying repository. This wrapper does
182 	 *         not allocate any new resources and need not be closed (but
183 	 *         closing it is harmless).
184 	 */
185 	public Git git() {
186 		return git;
187 	}
188 
189 	/**
190 	 * Get date
191 	 *
192 	 * @return current date.
193 	 * @since 4.2
194 	 */
195 	public Date getDate() {
196 		return new Date(mockSystemReader.getCurrentTime());
197 	}
198 
199 	/**
200 	 * Get timezone
201 	 *
202 	 * @return timezone used for default identities.
203 	 */
204 	public TimeZone getTimeZone() {
205 		return mockSystemReader.getTimeZone();
206 	}
207 
208 	/**
209 	 * Adjust the current time that will used by the next commit.
210 	 *
211 	 * @param secDelta
212 	 *            number of seconds to add to the current time.
213 	 */
214 	public void tick(int secDelta) {
215 		mockSystemReader.tick(secDelta);
216 	}
217 
218 	/**
219 	 * Set the author and committer using {@link #getDate()}.
220 	 *
221 	 * @param c
222 	 *            the commit builder to store.
223 	 */
224 	public void setAuthorAndCommitter(org.eclipse.jgit.lib.CommitBuilder c) {
225 		c.setAuthor(new PersonIdent(defaultAuthor, getDate()));
226 		c.setCommitter(new PersonIdent(defaultCommitter, getDate()));
227 	}
228 
229 	/**
230 	 * Create a new blob object in the repository.
231 	 *
232 	 * @param content
233 	 *            file content, will be UTF-8 encoded.
234 	 * @return reference to the blob.
235 	 * @throws Exception
236 	 */
237 	public RevBlob blob(String content) throws Exception {
238 		return blob(content.getBytes(UTF_8));
239 	}
240 
241 	/**
242 	 * Create a new blob object in the repository.
243 	 *
244 	 * @param content
245 	 *            binary file content.
246 	 * @return the new, fully parsed blob.
247 	 * @throws Exception
248 	 */
249 	public RevBlob blob(byte[] content) throws Exception {
250 		ObjectId id;
251 		try (ObjectInserter ins = inserter) {
252 			id = ins.insert(Constants.OBJ_BLOB, content);
253 			ins.flush();
254 		}
255 		return (RevBlob) pool.parseAny(id);
256 	}
257 
258 	/**
259 	 * Construct a regular file mode tree entry.
260 	 *
261 	 * @param path
262 	 *            path of the file.
263 	 * @param blob
264 	 *            a blob, previously constructed in the repository.
265 	 * @return the entry.
266 	 * @throws Exception
267 	 */
268 	public DirCacheEntry file(String path, RevBlob blob)
269 			throws Exception {
270 		final DirCacheEntry e = new DirCacheEntry(path);
271 		e.setFileMode(FileMode.REGULAR_FILE);
272 		e.setObjectId(blob);
273 		return e;
274 	}
275 
276 	/**
277 	 * Construct a tree from a specific listing of file entries.
278 	 *
279 	 * @param entries
280 	 *            the files to include in the tree. The collection does not need
281 	 *            to be sorted properly and may be empty.
282 	 * @return the new, fully parsed tree specified by the entry list.
283 	 * @throws Exception
284 	 */
285 	public RevTree tree(DirCacheEntry... entries) throws Exception {
286 		final DirCache dc = DirCache.newInCore();
287 		final DirCacheBuilder b = dc.builder();
288 		for (DirCacheEntry e : entries) {
289 			b.add(e);
290 		}
291 		b.finish();
292 		ObjectId root;
293 		try (ObjectInserter ins = inserter) {
294 			root = dc.writeTree(ins);
295 			ins.flush();
296 		}
297 		return pool.parseTree(root);
298 	}
299 
300 	/**
301 	 * Lookup an entry stored in a tree, failing if not present.
302 	 *
303 	 * @param tree
304 	 *            the tree to search.
305 	 * @param path
306 	 *            the path to find the entry of.
307 	 * @return the parsed object entry at this path, never null.
308 	 * @throws Exception
309 	 */
310 	public RevObject get(RevTree tree, String path)
311 			throws Exception {
312 		try (TreeWalk tw = new TreeWalk(pool.getObjectReader())) {
313 			tw.setFilter(PathFilterGroup.createFromStrings(Collections
314 					.singleton(path)));
315 			tw.reset(tree);
316 			while (tw.next()) {
317 				if (tw.isSubtree() && !path.equals(tw.getPathString())) {
318 					tw.enterSubtree();
319 					continue;
320 				}
321 				final ObjectId entid = tw.getObjectId(0);
322 				final FileMode entmode = tw.getFileMode(0);
323 				return pool.lookupAny(entid, entmode.getObjectType());
324 			}
325 		}
326 		fail("Can't find " + path + " in tree " + tree.name());
327 		return null; // never reached.
328 	}
329 
330 	/**
331 	 * Create a new, unparsed commit.
332 	 * <p>
333 	 * See {@link #unparsedCommit(int, RevTree, ObjectId...)}. The tree is the
334 	 * empty tree (no files or subdirectories).
335 	 *
336 	 * @param parents
337 	 *            zero or more IDs of the commit's parents.
338 	 * @return the ID of the new commit.
339 	 * @throws Exception
340 	 */
341 	public ObjectId unparsedCommit(ObjectId... parents) throws Exception {
342 		return unparsedCommit(1, tree(), parents);
343 	}
344 
345 	/**
346 	 * Create a new commit.
347 	 * <p>
348 	 * See {@link #commit(int, RevTree, RevCommit...)}. The tree is the empty
349 	 * tree (no files or subdirectories).
350 	 *
351 	 * @param parents
352 	 *            zero or more parents of the commit.
353 	 * @return the new commit.
354 	 * @throws Exception
355 	 */
356 	public RevCommit commit(RevCommit... parents) throws Exception {
357 		return commit(1, tree(), parents);
358 	}
359 
360 	/**
361 	 * Create a new commit.
362 	 * <p>
363 	 * See {@link #commit(int, RevTree, RevCommit...)}.
364 	 *
365 	 * @param tree
366 	 *            the root tree for the commit.
367 	 * @param parents
368 	 *            zero or more parents of the commit.
369 	 * @return the new commit.
370 	 * @throws Exception
371 	 */
372 	public RevCommit commit(RevTree tree, RevCommit... parents)
373 			throws Exception {
374 		return commit(1, tree, parents);
375 	}
376 
377 	/**
378 	 * Create a new commit.
379 	 * <p>
380 	 * See {@link #commit(int, RevTree, RevCommit...)}. The tree is the empty
381 	 * tree (no files or subdirectories).
382 	 *
383 	 * @param secDelta
384 	 *            number of seconds to advance {@link #tick(int)} by.
385 	 * @param parents
386 	 *            zero or more parents of the commit.
387 	 * @return the new commit.
388 	 * @throws Exception
389 	 */
390 	public RevCommit commit(int secDelta, RevCommit... parents)
391 			throws Exception {
392 		return commit(secDelta, tree(), parents);
393 	}
394 
395 	/**
396 	 * Create a new commit.
397 	 * <p>
398 	 * The author and committer identities are stored using the current
399 	 * timestamp, after being incremented by {@code secDelta}. The message body
400 	 * is empty.
401 	 *
402 	 * @param secDelta
403 	 *            number of seconds to advance {@link #tick(int)} by.
404 	 * @param tree
405 	 *            the root tree for the commit.
406 	 * @param parents
407 	 *            zero or more parents of the commit.
408 	 * @return the new, fully parsed commit.
409 	 * @throws Exception
410 	 */
411 	public RevCommit commit(final int secDelta, final RevTree tree,
412 			final RevCommit... parents) throws Exception {
413 		ObjectId id = unparsedCommit(secDelta, tree, parents);
414 		return pool.parseCommit(id);
415 	}
416 
417 	/**
418 	 * Create a new, unparsed commit.
419 	 * <p>
420 	 * The author and committer identities are stored using the current
421 	 * timestamp, after being incremented by {@code secDelta}. The message body
422 	 * is empty.
423 	 *
424 	 * @param secDelta
425 	 *            number of seconds to advance {@link #tick(int)} by.
426 	 * @param tree
427 	 *            the root tree for the commit.
428 	 * @param parents
429 	 *            zero or more IDs of the commit's parents.
430 	 * @return the ID of the new commit.
431 	 * @throws Exception
432 	 */
433 	public ObjectId unparsedCommit(final int secDelta, final RevTree tree,
434 			final ObjectId... parents) throws Exception {
435 		tick(secDelta);
436 
437 		final org.eclipse.jgit.lib.CommitBuilder c;
438 
439 		c = new org.eclipse.jgit.lib.CommitBuilder();
440 		c.setTreeId(tree);
441 		c.setParentIds(parents);
442 		c.setAuthor(new PersonIdent(defaultAuthor, getDate()));
443 		c.setCommitter(new PersonIdent(defaultCommitter, getDate()));
444 		c.setMessage("");
445 		ObjectId id;
446 		try (ObjectInserter ins = inserter) {
447 			id = ins.insert(c);
448 			ins.flush();
449 		}
450 		return id;
451 	}
452 
453 	/**
454 	 * Create commit builder
455 	 *
456 	 * @return a new commit builder.
457 	 */
458 	public CommitBuilder commit() {
459 		return new CommitBuilder();
460 	}
461 
462 	/**
463 	 * Construct an annotated tag object pointing at another object.
464 	 * <p>
465 	 * The tagger is the committer identity, at the current time as specified by
466 	 * {@link #tick(int)}. The time is not increased.
467 	 * <p>
468 	 * The tag message is empty.
469 	 *
470 	 * @param name
471 	 *            name of the tag. Traditionally a tag name should not start
472 	 *            with {@code refs/tags/}.
473 	 * @param dst
474 	 *            object the tag should be pointed at.
475 	 * @return the new, fully parsed annotated tag object.
476 	 * @throws Exception
477 	 */
478 	public RevTag tag(String name, RevObject dst) throws Exception {
479 		final TagBuilder t = new TagBuilder();
480 		t.setObjectId(dst);
481 		t.setTag(name);
482 		t.setTagger(new PersonIdent(defaultCommitter, getDate()));
483 		t.setMessage("");
484 		ObjectId id;
485 		try (ObjectInserter ins = inserter) {
486 			id = ins.insert(t);
487 			ins.flush();
488 		}
489 		return pool.parseTag(id);
490 	}
491 
492 	/**
493 	 * Update a reference to point to an object.
494 	 *
495 	 * @param ref
496 	 *            the name of the reference to update to. If {@code ref} does
497 	 *            not start with {@code refs/} and is not the magic names
498 	 *            {@code HEAD} {@code FETCH_HEAD} or {@code MERGE_HEAD}, then
499 	 *            {@code refs/heads/} will be prefixed in front of the given
500 	 *            name, thereby assuming it is a branch.
501 	 * @param to
502 	 *            the target object.
503 	 * @return the target object.
504 	 * @throws Exception
505 	 */
506 	public RevCommit update(String ref, CommitBuilder to) throws Exception {
507 		return update(ref, to.create());
508 	}
509 
510 	/**
511 	 * Amend an existing ref.
512 	 *
513 	 * @param ref
514 	 *            the name of the reference to amend, which must already exist.
515 	 *            If {@code ref} does not start with {@code refs/} and is not the
516 	 *            magic names {@code HEAD} {@code FETCH_HEAD} or {@code
517 	 *            MERGE_HEAD}, then {@code refs/heads/} will be prefixed in front
518 	 *            of the given name, thereby assuming it is a branch.
519 	 * @return commit builder that amends the branch on commit.
520 	 * @throws Exception
521 	 */
522 	public CommitBuilder amendRef(String ref) throws Exception {
523 		String name = normalizeRef(ref);
524 		Ref r = db.exactRef(name);
525 		if (r == null)
526 			throw new IOException("Not a ref: " + ref);
527 		return amend(pool.parseCommit(r.getObjectId()), branch(name).commit());
528 	}
529 
530 	/**
531 	 * Amend an existing commit.
532 	 *
533 	 * @param id
534 	 *            the id of the commit to amend.
535 	 * @return commit builder.
536 	 * @throws Exception
537 	 */
538 	public CommitBuilder amend(AnyObjectId id) throws Exception {
539 		return amend(pool.parseCommit(id), commit());
540 	}
541 
542 	private CommitBuilder amend(RevCommit old, CommitBuilder b) throws Exception {
543 		pool.parseBody(old);
544 		b.author(old.getAuthorIdent());
545 		b.committer(old.getCommitterIdent());
546 		b.message(old.getFullMessage());
547 		// Use the committer name from the old commit, but update it after ticking
548 		// the clock in CommitBuilder#create().
549 		b.updateCommitterTime = true;
550 
551 		// Reset parents to original parents.
552 		b.noParents();
553 		for (int i = 0; i < old.getParentCount(); i++)
554 			b.parent(old.getParent(i));
555 
556 		// Reset tree to original tree; resetting parents reset tree contents to the
557 		// first parent.
558 		b.tree.clear();
559 		try (TreeWalk tw = new TreeWalk(db)) {
560 			tw.reset(old.getTree());
561 			tw.setRecursive(true);
562 			while (tw.next()) {
563 				b.edit(new PathEdit(tw.getPathString()) {
564 					@Override
565 					public void apply(DirCacheEntry ent) {
566 						ent.setFileMode(tw.getFileMode(0));
567 						ent.setObjectId(tw.getObjectId(0));
568 					}
569 				});
570 			}
571 		}
572 
573 		return b;
574 	}
575 
576 	/**
577 	 * Update a reference to point to an object.
578 	 *
579 	 * @param <T>
580 	 *            type of the target object.
581 	 * @param ref
582 	 *            the name of the reference to update to. If {@code ref} does
583 	 *            not start with {@code refs/} and is not the magic names
584 	 *            {@code HEAD} {@code FETCH_HEAD} or {@code MERGE_HEAD}, then
585 	 *            {@code refs/heads/} will be prefixed in front of the given
586 	 *            name, thereby assuming it is a branch.
587 	 * @param obj
588 	 *            the target object.
589 	 * @return the target object.
590 	 * @throws Exception
591 	 */
592 	public <T extends AnyObjectId> T update(String ref, T obj) throws Exception {
593 		ref = normalizeRef(ref);
594 		RefUpdate u = db.updateRef(ref);
595 		u.setNewObjectId(obj);
596 		switch (u.forceUpdate()) {
597 		case FAST_FORWARD:
598 		case FORCED:
599 		case NEW:
600 		case NO_CHANGE:
601 			updateServerInfo();
602 			return obj;
603 
604 		default:
605 			throw new IOException("Cannot write " + ref + " " + u.getResult());
606 		}
607 	}
608 
609 	/**
610 	 * Delete a reference.
611 	 *
612 	 * @param ref
613 	 *	      the name of the reference to delete. This is normalized
614 	 *	      in the same way as {@link #update(String, AnyObjectId)}.
615 	 * @throws Exception
616 	 * @since 4.4
617 	 */
618 	public void delete(String ref) throws Exception {
619 		ref = normalizeRef(ref);
620 		RefUpdate u = db.updateRef(ref);
621 		u.setForceUpdate(true);
622 		switch (u.delete()) {
623 		case FAST_FORWARD:
624 		case FORCED:
625 		case NEW:
626 		case NO_CHANGE:
627 			updateServerInfo();
628 			return;
629 
630 		default:
631 			throw new IOException("Cannot delete " + ref + " " + u.getResult());
632 		}
633 	}
634 
635 	private static String normalizeRef(String ref) {
636 		if (Constants.HEAD.equals(ref)) {
637 			// nothing
638 		} else if ("FETCH_HEAD".equals(ref)) {
639 			// nothing
640 		} else if ("MERGE_HEAD".equals(ref)) {
641 			// nothing
642 		} else if (ref.startsWith(Constants.R_REFS)) {
643 			// nothing
644 		} else
645 			ref = Constants.R_HEADS + ref;
646 		return ref;
647 	}
648 
649 	/**
650 	 * Soft-reset HEAD to a detached state.
651 	 *
652 	 * @param id
653 	 *            ID of detached head.
654 	 * @throws Exception
655 	 * @see #reset(String)
656 	 */
657 	public void reset(AnyObjectId id) throws Exception {
658 		RefUpdate ru = db.updateRef(Constants.HEAD, true);
659 		ru.setNewObjectId(id);
660 		RefUpdate.Result result = ru.forceUpdate();
661 		switch (result) {
662 			case FAST_FORWARD:
663 			case FORCED:
664 			case NEW:
665 			case NO_CHANGE:
666 				break;
667 			default:
668 				throw new IOException(String.format(
669 						"Checkout \"%s\" failed: %s", id.name(), result));
670 		}
671 	}
672 
673 	/**
674 	 * Soft-reset HEAD to a different commit.
675 	 * <p>
676 	 * This is equivalent to {@code git reset --soft} in that it modifies HEAD but
677 	 * not the index or the working tree of a non-bare repository.
678 	 *
679 	 * @param name
680 	 *            revision string; either an existing ref name, or something that
681 	 *            can be parsed to an object ID.
682 	 * @throws Exception
683 	 */
684 	public void reset(String name) throws Exception {
685 		RefUpdate.Result result;
686 		ObjectId id = db.resolve(name);
687 		if (id == null)
688 			throw new IOException("Not a revision: " + name);
689 		RefUpdate ru = db.updateRef(Constants.HEAD, false);
690 		ru.setNewObjectId(id);
691 		result = ru.forceUpdate();
692 		switch (result) {
693 			case FAST_FORWARD:
694 			case FORCED:
695 			case NEW:
696 			case NO_CHANGE:
697 				break;
698 			default:
699 				throw new IOException(String.format(
700 						"Checkout \"%s\" failed: %s", name, result));
701 		}
702 	}
703 
704 	/**
705 	 * Cherry-pick a commit onto HEAD.
706 	 * <p>
707 	 * This differs from {@code git cherry-pick} in that it works in a bare
708 	 * repository. As a result, any merge failure results in an exception, as
709 	 * there is no way to recover.
710 	 *
711 	 * @param id
712 	 *            commit-ish to cherry-pick.
713 	 * @return the new, fully parsed commit, or null if no work was done due to
714 	 *         the resulting tree being identical.
715 	 * @throws Exception
716 	 */
717 	public RevCommit cherryPick(AnyObjectId id) throws Exception {
718 		RevCommit commit = pool.parseCommit(id);
719 		pool.parseBody(commit);
720 		if (commit.getParentCount() != 1)
721 			throw new IOException(String.format(
722 					"Expected 1 parent for %s, found: %s",
723 					id.name(), Arrays.asList(commit.getParents())));
724 		RevCommit parent = commit.getParent(0);
725 		pool.parseHeaders(parent);
726 
727 		Ref headRef = db.exactRef(Constants.HEAD);
728 		if (headRef == null)
729 			throw new IOException("Missing HEAD");
730 		RevCommit head = pool.parseCommit(headRef.getObjectId());
731 
732 		ThreeWayMerger merger = MergeStrategy.RECURSIVE.newMerger(db, true);
733 		merger.setBase(parent.getTree());
734 		if (merger.merge(head, commit)) {
735 			if (AnyObjectId.isEqual(head.getTree(), merger.getResultTreeId()))
736 				return null;
737 			tick(1);
738 			org.eclipse.jgit.lib.CommitBuilder b =
739 					new org.eclipse.jgit.lib.CommitBuilder();
740 			b.setParentId(head);
741 			b.setTreeId(merger.getResultTreeId());
742 			b.setAuthor(commit.getAuthorIdent());
743 			b.setCommitter(new PersonIdent(defaultCommitter, getDate()));
744 			b.setMessage(commit.getFullMessage());
745 			ObjectId result;
746 			try (ObjectInserter ins = inserter) {
747 				result = ins.insert(b);
748 				ins.flush();
749 			}
750 			update(Constants.HEAD, result);
751 			return pool.parseCommit(result);
752 		}
753 		throw new IOException("Merge conflict");
754 	}
755 
756 	/**
757 	 * Update the dumb client server info files.
758 	 *
759 	 * @throws Exception
760 	 */
761 	public void updateServerInfo() throws Exception {
762 		if (db instanceof FileRepository) {
763 			final FileRepository fr = (FileRepository) db;
764 			RefWriter rw = new RefWriter(fr.getRefDatabase().getRefs()) {
765 				@Override
766 				protected void writeFile(String name, byte[] bin)
767 						throws IOException {
768 					File path = new File(fr.getDirectory(), name);
769 					TestRepository.this.writeFile(path, bin);
770 				}
771 			};
772 			rw.writePackedRefs();
773 			rw.writeInfoRefs();
774 
775 			final StringBuilder w = new StringBuilder();
776 			for (PackFile p : fr.getObjectDatabase().getPacks()) {
777 				w.append("P ");
778 				w.append(p.getPackFile().getName());
779 				w.append('\n');
780 			}
781 			writeFile(new File(new File(fr.getObjectDatabase().getDirectory(),
782 					"info"), "packs"), Constants.encodeASCII(w.toString()));
783 		}
784 	}
785 
786 	/**
787 	 * Ensure the body of the given object has been parsed.
788 	 *
789 	 * @param <T>
790 	 *            type of object, e.g. {@link org.eclipse.jgit.revwalk.RevTag}
791 	 *            or {@link org.eclipse.jgit.revwalk.RevCommit}.
792 	 * @param object
793 	 *            reference to the (possibly unparsed) object to force body
794 	 *            parsing of.
795 	 * @return {@code object}
796 	 * @throws Exception
797 	 */
798 	public <T extends RevObject> T parseBody(T object) throws Exception {
799 		pool.parseBody(object);
800 		return object;
801 	}
802 
803 	/**
804 	 * Create a new branch builder for this repository.
805 	 *
806 	 * @param ref
807 	 *            name of the branch to be constructed. If {@code ref} does not
808 	 *            start with {@code refs/} the prefix {@code refs/heads/} will
809 	 *            be added.
810 	 * @return builder for the named branch.
811 	 */
812 	public BranchBuilder branch(String ref) {
813 		if (Constants.HEAD.equals(ref)) {
814 			// nothing
815 		} else if (ref.startsWith(Constants.R_REFS)) {
816 			// nothing
817 		} else
818 			ref = Constants.R_HEADS + ref;
819 		return new BranchBuilder(ref);
820 	}
821 
822 	/**
823 	 * Tag an object using a lightweight tag.
824 	 *
825 	 * @param name
826 	 *            the tag name. The /refs/tags/ prefix will be added if the name
827 	 *            doesn't start with it
828 	 * @param obj
829 	 *            the object to tag
830 	 * @return the tagged object
831 	 * @throws Exception
832 	 */
833 	public ObjectId lightweightTag(String name, ObjectId obj) throws Exception {
834 		if (!name.startsWith(Constants.R_TAGS))
835 			name = Constants.R_TAGS + name;
836 		return update(name, obj);
837 	}
838 
839 	/**
840 	 * Run consistency checks against the object database.
841 	 * <p>
842 	 * This method completes silently if the checks pass. A temporary revision
843 	 * pool is constructed during the checking.
844 	 *
845 	 * @param tips
846 	 *            the tips to start checking from; if not supplied the refs of
847 	 *            the repository are used instead.
848 	 * @throws MissingObjectException
849 	 * @throws IncorrectObjectTypeException
850 	 * @throws IOException
851 	 */
852 	public void fsck(RevObject... tips) throws MissingObjectException,
853 			IncorrectObjectTypeException, IOException {
854 		try (ObjectWalk ow = new ObjectWalk(db)) {
855 			if (tips.length != 0) {
856 				for (RevObject o : tips)
857 					ow.markStart(ow.parseAny(o));
858 			} else {
859 				for (Ref r : db.getRefDatabase().getRefs())
860 					ow.markStart(ow.parseAny(r.getObjectId()));
861 			}
862 
863 			ObjectChecker oc = new ObjectChecker();
864 			for (;;) {
865 				final RevCommit o = ow.next();
866 				if (o == null)
867 					break;
868 
869 				final byte[] bin = db.open(o, o.getType()).getCachedBytes();
870 				oc.checkCommit(o, bin);
871 				assertHash(o, bin);
872 			}
873 
874 			for (;;) {
875 				final RevObject o = ow.nextObject();
876 				if (o == null)
877 					break;
878 
879 				final byte[] bin = db.open(o, o.getType()).getCachedBytes();
880 				oc.check(o, o.getType(), bin);
881 				assertHash(o, bin);
882 			}
883 		}
884 	}
885 
886 	private static void assertHash(RevObject id, byte[] bin) {
887 		MessageDigest md = Constants.newMessageDigest();
888 		md.update(Constants.encodedTypeString(id.getType()));
889 		md.update((byte) ' ');
890 		md.update(Constants.encodeASCII(bin.length));
891 		md.update((byte) 0);
892 		md.update(bin);
893 		assertEquals(id, ObjectId.fromRaw(md.digest()));
894 	}
895 
896 	/**
897 	 * Pack all reachable objects in the repository into a single pack file.
898 	 * <p>
899 	 * All loose objects are automatically pruned. Existing packs however are
900 	 * not removed.
901 	 *
902 	 * @throws Exception
903 	 */
904 	public void packAndPrune() throws Exception {
905 		if (db.getObjectDatabase() instanceof ObjectDirectory) {
906 			ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase();
907 			NullProgressMonitor m = NullProgressMonitor.INSTANCE;
908 
909 			final File pack, idx;
910 			try (PackWriter pw = new PackWriter(db)) {
911 				Set<ObjectId> all = new HashSet<>();
912 				for (Ref r : db.getRefDatabase().getRefs())
913 					all.add(r.getObjectId());
914 				pw.preparePack(m, all, PackWriter.NONE);
915 
916 				final ObjectId name = pw.computeName();
917 
918 				pack = nameFor(odb, name, ".pack");
919 				try (OutputStream out =
920 						new BufferedOutputStream(new FileOutputStream(pack))) {
921 					pw.writePack(m, m, out);
922 				}
923 				pack.setReadOnly();
924 
925 				idx = nameFor(odb, name, ".idx");
926 				try (OutputStream out =
927 						new BufferedOutputStream(new FileOutputStream(idx))) {
928 					pw.writeIndex(out);
929 				}
930 				idx.setReadOnly();
931 			}
932 
933 			odb.openPack(pack);
934 			updateServerInfo();
935 			prunePacked(odb);
936 		}
937 	}
938 
939 	/**
940 	 * Closes the underlying {@link Repository} object and any other internal
941 	 * resources.
942 	 * <p>
943 	 * {@link AutoCloseable} resources that may escape this object, such as
944 	 * those returned by the {@link #git} and {@link #getRevWalk()} methods are
945 	 * not closed.
946 	 */
947 	@Override
948 	public void close() {
949 		try {
950 			inserter.close();
951 		} finally {
952 			db.close();
953 		}
954 	}
955 
956 	private static void prunePacked(ObjectDirectory odb) throws IOException {
957 		for (PackFile p : odb.getPacks()) {
958 			for (MutableEntry e : p)
959 				FileUtils.delete(odb.fileFor(e.toObjectId()));
960 		}
961 	}
962 
963 	private static File nameFor(ObjectDirectory odb, ObjectId name, String t) {
964 		File packdir = odb.getPackDirectory();
965 		return new File(packdir, "pack-" + name.name() + t);
966 	}
967 
968 	private void writeFile(File p, byte[] bin) throws IOException,
969 			ObjectWritingException {
970 		final LockFile lck = new LockFile(p);
971 		if (!lck.lock())
972 			throw new ObjectWritingException("Can't write " + p);
973 		try {
974 			lck.write(bin);
975 		} catch (IOException ioe) {
976 			throw new ObjectWritingException("Can't write " + p, ioe);
977 		}
978 		if (!lck.commit())
979 			throw new ObjectWritingException("Can't write " + p);
980 	}
981 
982 	/** Helper to build a branch with one or more commits */
983 	public class BranchBuilder {
984 		private final String ref;
985 
986 		BranchBuilder(String ref) {
987 			this.ref = ref;
988 		}
989 
990 		/**
991 		 * @return construct a new commit builder that updates this branch. If
992 		 *         the branch already exists, the commit builder will have its
993 		 *         first parent as the current commit and its tree will be
994 		 *         initialized to the current files.
995 		 * @throws Exception
996 		 *             the commit builder can't read the current branch state
997 		 */
998 		public CommitBuilder commit() throws Exception {
999 			return new CommitBuilder(this);
1000 		}
1001 
1002 		/**
1003 		 * Forcefully update this branch to a particular commit.
1004 		 *
1005 		 * @param to
1006 		 *            the commit to update to.
1007 		 * @return {@code to}.
1008 		 * @throws Exception
1009 		 */
1010 		public RevCommit update(CommitBuilder to) throws Exception {
1011 			return update(to.create());
1012 		}
1013 
1014 		/**
1015 		 * Forcefully update this branch to a particular commit.
1016 		 *
1017 		 * @param to
1018 		 *            the commit to update to.
1019 		 * @return {@code to}.
1020 		 * @throws Exception
1021 		 */
1022 		public RevCommit update(RevCommit to) throws Exception {
1023 			return TestRepository.this.update(ref, to);
1024 		}
1025 
1026 		/**
1027 		 * Delete this branch.
1028 		 * @throws Exception
1029 		 * @since 4.4
1030 		 */
1031 		public void delete() throws Exception {
1032 			TestRepository.this.delete(ref);
1033 		}
1034 	}
1035 
1036 	/** Helper to generate a commit. */
1037 	public class CommitBuilder {
1038 		private final BranchBuilder branch;
1039 
1040 		private final DirCache tree = DirCache.newInCore();
1041 
1042 		private ObjectId topLevelTree;
1043 
1044 		private final List<RevCommit> parents = new ArrayList<>(2);
1045 
1046 		private int tick = 1;
1047 
1048 		private String message = "";
1049 
1050 		private RevCommit self;
1051 
1052 		private PersonIdent author;
1053 		private PersonIdent committer;
1054 
1055 		private String changeId;
1056 
1057 		private boolean updateCommitterTime;
1058 
1059 		CommitBuilder() {
1060 			branch = null;
1061 		}
1062 
1063 		CommitBuilder(BranchBuilder b) throws Exception {
1064 			branch = b;
1065 
1066 			Ref ref = db.exactRef(branch.ref);
1067 			if (ref != null && ref.getObjectId() != null)
1068 				parent(pool.parseCommit(ref.getObjectId()));
1069 		}
1070 
1071 		CommitBuilder(CommitBuilder prior) throws Exception {
1072 			branch = prior.branch;
1073 
1074 			DirCacheBuilder b = tree.builder();
1075 			for (int i = 0; i < prior.tree.getEntryCount(); i++)
1076 				b.add(prior.tree.getEntry(i));
1077 			b.finish();
1078 
1079 			parents.add(prior.create());
1080 		}
1081 
1082 		/**
1083 		 * set parent commit
1084 		 *
1085 		 * @param p
1086 		 *            parent commit
1087 		 * @return this commit builder
1088 		 * @throws Exception
1089 		 */
1090 		public CommitBuilder parent(RevCommit p) throws Exception {
1091 			if (parents.isEmpty()) {
1092 				DirCacheBuilder b = tree.builder();
1093 				parseBody(p);
1094 				b.addTree(new byte[0], DirCacheEntry.STAGE_0, pool
1095 						.getObjectReader(), p.getTree());
1096 				b.finish();
1097 			}
1098 			parents.add(p);
1099 			return this;
1100 		}
1101 
1102 		/**
1103 		 * Get parent commits
1104 		 *
1105 		 * @return parent commits
1106 		 */
1107 		public List<RevCommit> parents() {
1108 			return Collections.unmodifiableList(parents);
1109 		}
1110 
1111 		/**
1112 		 * Remove parent commits
1113 		 *
1114 		 * @return this commit builder
1115 		 */
1116 		public CommitBuilder noParents() {
1117 			parents.clear();
1118 			return this;
1119 		}
1120 
1121 		/**
1122 		 * Remove files
1123 		 *
1124 		 * @return this commit builder
1125 		 */
1126 		public CommitBuilder noFiles() {
1127 			tree.clear();
1128 			return this;
1129 		}
1130 
1131 		/**
1132 		 * Set top level tree
1133 		 *
1134 		 * @param treeId
1135 		 *            the top level tree
1136 		 * @return this commit builder
1137 		 */
1138 		public CommitBuilder setTopLevelTree(ObjectId treeId) {
1139 			topLevelTree = treeId;
1140 			return this;
1141 		}
1142 
1143 		/**
1144 		 * Add file with given content
1145 		 *
1146 		 * @param path
1147 		 *            path of the file
1148 		 * @param content
1149 		 *            the file content
1150 		 * @return this commit builder
1151 		 * @throws Exception
1152 		 */
1153 		public CommitBuilder add(String path, String content) throws Exception {
1154 			return add(path, blob(content));
1155 		}
1156 
1157 		/**
1158 		 * Add file with given path and blob
1159 		 *
1160 		 * @param path
1161 		 *            path of the file
1162 		 * @param id
1163 		 *            blob for this file
1164 		 * @return this commit builder
1165 		 * @throws Exception
1166 		 */
1167 		public CommitBuilder add(String path, RevBlob id)
1168 				throws Exception {
1169 			return edit(new PathEdit(path) {
1170 				@Override
1171 				public void apply(DirCacheEntry ent) {
1172 					ent.setFileMode(FileMode.REGULAR_FILE);
1173 					ent.setObjectId(id);
1174 				}
1175 			});
1176 		}
1177 
1178 		/**
1179 		 * Edit the index
1180 		 *
1181 		 * @param edit
1182 		 *            the index record update
1183 		 * @return this commit builder
1184 		 */
1185 		public CommitBuilder edit(PathEdit edit) {
1186 			DirCacheEditor e = tree.editor();
1187 			e.add(edit);
1188 			e.finish();
1189 			return this;
1190 		}
1191 
1192 		/**
1193 		 * Remove a file
1194 		 *
1195 		 * @param path
1196 		 *            path of the file
1197 		 * @return this commit builder
1198 		 */
1199 		public CommitBuilder rm(String path) {
1200 			DirCacheEditor e = tree.editor();
1201 			e.add(new DeletePath(path));
1202 			e.add(new DeleteTree(path));
1203 			e.finish();
1204 			return this;
1205 		}
1206 
1207 		/**
1208 		 * Set commit message
1209 		 *
1210 		 * @param m
1211 		 *            the message
1212 		 * @return this commit builder
1213 		 */
1214 		public CommitBuilder message(String m) {
1215 			message = m;
1216 			return this;
1217 		}
1218 
1219 		/**
1220 		 * Get the commit message
1221 		 *
1222 		 * @return the commit message
1223 		 */
1224 		public String message() {
1225 			return message;
1226 		}
1227 
1228 		/**
1229 		 * Tick the clock
1230 		 *
1231 		 * @param secs
1232 		 *            number of seconds
1233 		 * @return this commit builder
1234 		 */
1235 		public CommitBuilder tick(int secs) {
1236 			tick = secs;
1237 			return this;
1238 		}
1239 
1240 		/**
1241 		 * Set author and committer identity
1242 		 *
1243 		 * @param ident
1244 		 *            identity to set
1245 		 * @return this commit builder
1246 		 */
1247 		public CommitBuilder ident(PersonIdent ident) {
1248 			author = ident;
1249 			committer = ident;
1250 			return this;
1251 		}
1252 
1253 		/**
1254 		 * Set the author identity
1255 		 *
1256 		 * @param a
1257 		 *            the author's identity
1258 		 * @return this commit builder
1259 		 */
1260 		public CommitBuilder author(PersonIdent a) {
1261 			author = a;
1262 			return this;
1263 		}
1264 
1265 		/**
1266 		 * Get the author identity
1267 		 *
1268 		 * @return the author identity
1269 		 */
1270 		public PersonIdent author() {
1271 			return author;
1272 		}
1273 
1274 		/**
1275 		 * Set the committer identity
1276 		 *
1277 		 * @param c
1278 		 *            the committer identity
1279 		 * @return this commit builder
1280 		 */
1281 		public CommitBuilder committer(PersonIdent c) {
1282 			committer = c;
1283 			return this;
1284 		}
1285 
1286 		/**
1287 		 * Get the committer identity
1288 		 *
1289 		 * @return the committer identity
1290 		 */
1291 		public PersonIdent committer() {
1292 			return committer;
1293 		}
1294 
1295 		/**
1296 		 * Insert changeId
1297 		 *
1298 		 * @return this commit builder
1299 		 */
1300 		public CommitBuilder insertChangeId() {
1301 			changeId = "";
1302 			return this;
1303 		}
1304 
1305 		/**
1306 		 * Insert given changeId
1307 		 *
1308 		 * @param c
1309 		 *            changeId
1310 		 * @return this commit builder
1311 		 */
1312 		public CommitBuilder insertChangeId(String c) {
1313 			// Validate, but store as a string so we can use "" as a sentinel.
1314 			ObjectId.fromString(c);
1315 			changeId = c;
1316 			return this;
1317 		}
1318 
1319 		/**
1320 		 * Create the commit
1321 		 *
1322 		 * @return the new commit
1323 		 * @throws Exception
1324 		 *             if creation failed
1325 		 */
1326 		public RevCommit create() throws Exception {
1327 			if (self == null) {
1328 				TestRepository.this.tick(tick);
1329 
1330 				final org.eclipse.jgit.lib.CommitBuilder c;
1331 
1332 				c = new org.eclipse.jgit.lib.CommitBuilder();
1333 				c.setParentIds(parents);
1334 				setAuthorAndCommitter(c);
1335 				if (author != null)
1336 					c.setAuthor(author);
1337 				if (committer != null) {
1338 					if (updateCommitterTime)
1339 						committer = new PersonIdent(committer, getDate());
1340 					c.setCommitter(committer);
1341 				}
1342 
1343 				ObjectId commitId;
1344 				try (ObjectInserter ins = inserter) {
1345 					if (topLevelTree != null)
1346 						c.setTreeId(topLevelTree);
1347 					else
1348 						c.setTreeId(tree.writeTree(ins));
1349 					insertChangeId(c);
1350 					c.setMessage(message);
1351 					commitId = ins.insert(c);
1352 					ins.flush();
1353 				}
1354 				self = pool.parseCommit(commitId);
1355 
1356 				if (branch != null)
1357 					branch.update(self);
1358 			}
1359 			return self;
1360 		}
1361 
1362 		private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c) {
1363 			if (changeId == null)
1364 				return;
1365 			int idx = ChangeIdUtil.indexOfChangeId(message, "\n");
1366 			if (idx >= 0)
1367 				return;
1368 
1369 			ObjectId firstParentId = null;
1370 			if (!parents.isEmpty())
1371 				firstParentId = parents.get(0);
1372 
1373 			ObjectId cid;
1374 			if (changeId.isEmpty())
1375 				cid = ChangeIdUtil.computeChangeId(c.getTreeId(), firstParentId,
1376 						c.getAuthor(), c.getCommitter(), message);
1377 			else
1378 				cid = ObjectId.fromString(changeId);
1379 			message = ChangeIdUtil.insertId(message, cid);
1380 			if (cid != null)
1381 				message = message.replaceAll("\nChange-Id: I" //$NON-NLS-1$
1382 						+ ObjectId.zeroId().getName() + "\n", "\nChange-Id: I" //$NON-NLS-1$ //$NON-NLS-2$
1383 						+ cid.getName() + "\n"); //$NON-NLS-1$
1384 		}
1385 
1386 		/**
1387 		 * Create child commit builder
1388 		 *
1389 		 * @return child commit builder
1390 		 * @throws Exception
1391 		 */
1392 		public CommitBuilder child() throws Exception {
1393 			return new CommitBuilder(this);
1394 		}
1395 	}
1396 }