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