xref: /JGit/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java (revision 2a6b2eddcfac17ef5ff3b6b28dfaadd83e34956a)
1f945c424SShawn O. Pearce /*
25c5f7c6bSMatthias Sohn  * Copyright (C) 2009-2010, Google Inc. and others
3f945c424SShawn O. Pearce  *
45c5f7c6bSMatthias Sohn  * This program and the accompanying materials are made available under the
55c5f7c6bSMatthias Sohn  * terms of the Eclipse Distribution License v. 1.0 which is available at
65c5f7c6bSMatthias Sohn  * https://www.eclipse.org/org/documents/edl-v10.php.
7f945c424SShawn O. Pearce  *
85c5f7c6bSMatthias Sohn  * SPDX-License-Identifier: BSD-3-Clause
9f945c424SShawn O. Pearce  */
10f945c424SShawn O. Pearce 
11f945c424SShawn O. Pearce package org.eclipse.jgit.junit;
12f945c424SShawn O. Pearce 
1330c6c754SDavid Pursehouse import static java.nio.charset.StandardCharsets.UTF_8;
14e875c905SRobin Rosenberg import static org.junit.Assert.assertEquals;
15e875c905SRobin Rosenberg import static org.junit.Assert.fail;
16d9e07a57SRobin Rosenberg 
178208da2fSShawn Pearce import java.io.BufferedOutputStream;
18f945c424SShawn O. Pearce import java.io.File;
19f5eb0d93SShawn O. Pearce import java.io.FileOutputStream;
20f945c424SShawn O. Pearce import java.io.IOException;
212156aa89SShawn O. Pearce import java.io.OutputStream;
22f5eb0d93SShawn O. Pearce import java.security.MessageDigest;
23f945c424SShawn O. Pearce import java.util.ArrayList;
24f1a15f7eSDave Borowitz import java.util.Arrays;
25f945c424SShawn O. Pearce import java.util.Collections;
26f945c424SShawn O. Pearce import java.util.Date;
27f5eb0d93SShawn O. Pearce import java.util.HashSet;
28f945c424SShawn O. Pearce import java.util.List;
29f5eb0d93SShawn O. Pearce import java.util.Set;
30f1a15f7eSDave Borowitz import java.util.TimeZone;
31f945c424SShawn O. Pearce 
32be08dcb6SDave Borowitz import org.eclipse.jgit.api.Git;
33f945c424SShawn O. Pearce import org.eclipse.jgit.dircache.DirCache;
34f945c424SShawn O. Pearce import org.eclipse.jgit.dircache.DirCacheBuilder;
35f945c424SShawn O. Pearce import org.eclipse.jgit.dircache.DirCacheEditor;
36f945c424SShawn O. Pearce import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
37f945c424SShawn O. Pearce import org.eclipse.jgit.dircache.DirCacheEditor.DeleteTree;
38f945c424SShawn O. Pearce import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
39e875c905SRobin Rosenberg import org.eclipse.jgit.dircache.DirCacheEntry;
40f5eb0d93SShawn O. Pearce import org.eclipse.jgit.errors.IncorrectObjectTypeException;
41f5eb0d93SShawn O. Pearce import org.eclipse.jgit.errors.MissingObjectException;
42f945c424SShawn O. Pearce import org.eclipse.jgit.errors.ObjectWritingException;
43f32b8612SShawn Pearce import org.eclipse.jgit.internal.storage.file.FileRepository;
44f32b8612SShawn Pearce import org.eclipse.jgit.internal.storage.file.LockFile;
45f32b8612SShawn Pearce import org.eclipse.jgit.internal.storage.file.ObjectDirectory;
46efb154fcSNasser Grainawi import org.eclipse.jgit.internal.storage.file.Pack;
47971dafd3SNasser Grainawi import org.eclipse.jgit.internal.storage.file.PackFile;
48f32b8612SShawn Pearce import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
49971dafd3SNasser Grainawi import org.eclipse.jgit.internal.storage.pack.PackExt;
50f32b8612SShawn Pearce import org.eclipse.jgit.internal.storage.pack.PackWriter;
51f945c424SShawn O. Pearce import org.eclipse.jgit.lib.AnyObjectId;
52f945c424SShawn O. Pearce import org.eclipse.jgit.lib.Constants;
53f945c424SShawn O. Pearce import org.eclipse.jgit.lib.FileMode;
54f5eb0d93SShawn O. Pearce import org.eclipse.jgit.lib.NullProgressMonitor;
55f5eb0d93SShawn O. Pearce import org.eclipse.jgit.lib.ObjectChecker;
56f945c424SShawn O. Pearce import org.eclipse.jgit.lib.ObjectId;
5788530a17SShawn O. Pearce import org.eclipse.jgit.lib.ObjectInserter;
58f945c424SShawn O. Pearce import org.eclipse.jgit.lib.PersonIdent;
59f945c424SShawn O. Pearce import org.eclipse.jgit.lib.Ref;
60f945c424SShawn O. Pearce import org.eclipse.jgit.lib.RefUpdate;
61f945c424SShawn O. Pearce import org.eclipse.jgit.lib.RefWriter;
62f945c424SShawn O. Pearce import org.eclipse.jgit.lib.Repository;
6322b28569SShawn O. Pearce import org.eclipse.jgit.lib.TagBuilder;
64f1a15f7eSDave Borowitz import org.eclipse.jgit.merge.MergeStrategy;
65f1a15f7eSDave Borowitz import org.eclipse.jgit.merge.ThreeWayMerger;
66f5eb0d93SShawn O. Pearce import org.eclipse.jgit.revwalk.ObjectWalk;
67f945c424SShawn O. Pearce import org.eclipse.jgit.revwalk.RevBlob;
68f945c424SShawn O. Pearce import org.eclipse.jgit.revwalk.RevCommit;
69f945c424SShawn O. Pearce import org.eclipse.jgit.revwalk.RevObject;
70f945c424SShawn O. Pearce import org.eclipse.jgit.revwalk.RevTag;
71f945c424SShawn O. Pearce import org.eclipse.jgit.revwalk.RevTree;
72f945c424SShawn O. Pearce import org.eclipse.jgit.revwalk.RevWalk;
73f945c424SShawn O. Pearce import org.eclipse.jgit.treewalk.TreeWalk;
74f945c424SShawn O. Pearce import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
75c1d40caaSDave Borowitz import org.eclipse.jgit.util.ChangeIdUtil;
7645731756SMatthias Sohn import org.eclipse.jgit.util.FileUtils;
77f945c424SShawn O. Pearce 
7889d4a737SShawn O. Pearce /**
7989d4a737SShawn O. Pearce  * Wrapper to make creating test data easier.
8089d4a737SShawn O. Pearce  *
8189d4a737SShawn O. Pearce  * @param <R>
8289d4a737SShawn O. Pearce  *            type of Repository the test data is stored on.
8389d4a737SShawn O. Pearce  */
848ed59c51SJackson Toeniskoetter public class TestRepository<R extends Repository> implements AutoCloseable {
85f945c424SShawn O. Pearce 
86a90b75b4SMatthias Sohn 	/** Constant <code>AUTHOR="J. Author"</code> */
8710135580STerry Parker 	public static final String AUTHOR = "J. Author";
88f945c424SShawn O. Pearce 
89a90b75b4SMatthias Sohn 	/** Constant <code>AUTHOR_EMAIL="jauthor@example.com"</code> */
9010135580STerry Parker 	public static final String AUTHOR_EMAIL = "jauthor@example.com";
91f945c424SShawn O. Pearce 
92a90b75b4SMatthias Sohn 	/** Constant <code>COMMITTER="J. Committer"</code> */
9310135580STerry Parker 	public static final String COMMITTER = "J. Committer";
94f945c424SShawn O. Pearce 
95a90b75b4SMatthias Sohn 	/** Constant <code>COMMITTER_EMAIL="jcommitter@example.com"</code> */
9610135580STerry Parker 	public static final String COMMITTER_EMAIL = "jcommitter@example.com";
9710135580STerry Parker 
9810135580STerry Parker 	private final PersonIdent defaultAuthor;
9910135580STerry Parker 
10010135580STerry Parker 	private final PersonIdent defaultCommitter;
101f945c424SShawn O. Pearce 
10289d4a737SShawn O. Pearce 	private final R db;
103f945c424SShawn O. Pearce 
104be08dcb6SDave Borowitz 	private final Git git;
105be08dcb6SDave Borowitz 
106f945c424SShawn O. Pearce 	private final RevWalk pool;
107f945c424SShawn O. Pearce 
10888530a17SShawn O. Pearce 	private final ObjectInserter inserter;
109f945c424SShawn O. Pearce 
110069040ddSTerry Parker 	private final MockSystemReader mockSystemReader;
111f945c424SShawn O. Pearce 
112f945c424SShawn O. Pearce 	/**
113f945c424SShawn O. Pearce 	 * Wrap a repository with test building tools.
114f945c424SShawn O. Pearce 	 *
115f945c424SShawn O. Pearce 	 * @param db
116f945c424SShawn O. Pearce 	 *            the test repository to write into.
11789d4a737SShawn O. Pearce 	 * @throws IOException
118f945c424SShawn O. Pearce 	 */
TestRepository(R db)11989d4a737SShawn O. Pearce 	public TestRepository(R db) throws IOException {
120069040ddSTerry Parker 		this(db, new RevWalk(db), new MockSystemReader());
121f945c424SShawn O. Pearce 	}
122f945c424SShawn O. Pearce 
123f945c424SShawn O. Pearce 	/**
124f945c424SShawn O. Pearce 	 * Wrap a repository with test building tools.
125f945c424SShawn O. Pearce 	 *
126f945c424SShawn O. Pearce 	 * @param db
127f945c424SShawn O. Pearce 	 *            the test repository to write into.
128f945c424SShawn O. Pearce 	 * @param rw
129f945c424SShawn O. Pearce 	 *            the RevObject pool to use for object lookup.
13089d4a737SShawn O. Pearce 	 * @throws IOException
131f945c424SShawn O. Pearce 	 */
TestRepository(R db, RevWalk rw)13289d4a737SShawn O. Pearce 	public TestRepository(R db, RevWalk rw) throws IOException {
133069040ddSTerry Parker 		this(db, rw, new MockSystemReader());
134069040ddSTerry Parker 	}
135069040ddSTerry Parker 
136069040ddSTerry Parker 	/**
137069040ddSTerry Parker 	 * Wrap a repository with test building tools.
138069040ddSTerry Parker 	 *
139069040ddSTerry Parker 	 * @param db
140069040ddSTerry Parker 	 *            the test repository to write into.
141069040ddSTerry Parker 	 * @param rw
142069040ddSTerry Parker 	 *            the RevObject pool to use for object lookup.
143069040ddSTerry Parker 	 * @param reader
144069040ddSTerry Parker 	 *            the MockSystemReader to use for clock and other system
145069040ddSTerry Parker 	 *            operations.
146069040ddSTerry Parker 	 * @throws IOException
1472cdc130dSMatthias Sohn 	 * @since 4.2
148069040ddSTerry Parker 	 */
TestRepository(R db, RevWalk rw, MockSystemReader reader)149069040ddSTerry Parker 	public TestRepository(R db, RevWalk rw, MockSystemReader reader)
150069040ddSTerry Parker 			throws IOException {
151f945c424SShawn O. Pearce 		this.db = db;
152be08dcb6SDave Borowitz 		this.git = Git.wrap(db);
153f945c424SShawn O. Pearce 		this.pool = rw;
15488530a17SShawn O. Pearce 		this.inserter = db.newObjectInserter();
155069040ddSTerry Parker 		this.mockSystemReader = reader;
15610135580STerry Parker 		long now = mockSystemReader.getCurrentTime();
15710135580STerry Parker 		int tz = mockSystemReader.getTimezone(now);
15810135580STerry Parker 		defaultAuthor = new PersonIdent(AUTHOR, AUTHOR_EMAIL, now, tz);
15910135580STerry Parker 		defaultCommitter = new PersonIdent(COMMITTER, COMMITTER_EMAIL, now, tz);
160f945c424SShawn O. Pearce 	}
161f945c424SShawn O. Pearce 
162a90b75b4SMatthias Sohn 	/**
163a90b75b4SMatthias Sohn 	 * Get repository
164a90b75b4SMatthias Sohn 	 *
165a90b75b4SMatthias Sohn 	 * @return the repository this helper class operates against.
166a90b75b4SMatthias Sohn 	 */
getRepository()16789d4a737SShawn O. Pearce 	public R getRepository() {
168f945c424SShawn O. Pearce 		return db;
169f945c424SShawn O. Pearce 	}
170f945c424SShawn O. Pearce 
171a90b75b4SMatthias Sohn 	/**
172a90b75b4SMatthias Sohn 	 * Get RevWalk
173a90b75b4SMatthias Sohn 	 *
174a90b75b4SMatthias Sohn 	 * @return get the RevWalk pool all objects are allocated through.
175a90b75b4SMatthias Sohn 	 */
getRevWalk()176f945c424SShawn O. Pearce 	public RevWalk getRevWalk() {
177f945c424SShawn O. Pearce 		return pool;
178f945c424SShawn O. Pearce 	}
179f945c424SShawn O. Pearce 
180be08dcb6SDave Borowitz 	/**
181a90b75b4SMatthias Sohn 	 * Return Git API wrapper
182a90b75b4SMatthias Sohn 	 *
183be08dcb6SDave Borowitz 	 * @return an API wrapper for the underlying repository. This wrapper does
184a90b75b4SMatthias Sohn 	 *         not allocate any new resources and need not be closed (but
185a90b75b4SMatthias Sohn 	 *         closing it is harmless).
186a90b75b4SMatthias Sohn 	 */
git()187be08dcb6SDave Borowitz 	public Git git() {
188be08dcb6SDave Borowitz 		return git;
189be08dcb6SDave Borowitz 	}
190be08dcb6SDave Borowitz 
1912cdc130dSMatthias Sohn 	/**
192a90b75b4SMatthias Sohn 	 * Get date
193a90b75b4SMatthias Sohn 	 *
1942cdc130dSMatthias Sohn 	 * @return current date.
1952cdc130dSMatthias Sohn 	 * @since 4.2
1962cdc130dSMatthias Sohn 	 */
getDate()197069040ddSTerry Parker 	public Date getDate() {
198069040ddSTerry Parker 		return new Date(mockSystemReader.getCurrentTime());
199f945c424SShawn O. Pearce 	}
200f945c424SShawn O. Pearce 
201a90b75b4SMatthias Sohn 	/**
202a90b75b4SMatthias Sohn 	 * Get timezone
203a90b75b4SMatthias Sohn 	 *
204a90b75b4SMatthias Sohn 	 * @return timezone used for default identities.
205a90b75b4SMatthias Sohn 	 */
getTimeZone()206f1a15f7eSDave Borowitz 	public TimeZone getTimeZone() {
207069040ddSTerry Parker 		return mockSystemReader.getTimeZone();
208f1a15f7eSDave Borowitz 	}
209f1a15f7eSDave Borowitz 
210f945c424SShawn O. Pearce 	/**
211f945c424SShawn O. Pearce 	 * Adjust the current time that will used by the next commit.
212f945c424SShawn O. Pearce 	 *
213f945c424SShawn O. Pearce 	 * @param secDelta
214f945c424SShawn O. Pearce 	 *            number of seconds to add to the current time.
215f945c424SShawn O. Pearce 	 */
tick(int secDelta)2166d370d83SHan-Wen Nienhuys 	public void tick(int secDelta) {
217069040ddSTerry Parker 		mockSystemReader.tick(secDelta);
218f945c424SShawn O. Pearce 	}
219f945c424SShawn O. Pearce 
220f945c424SShawn O. Pearce 	/**
221069040ddSTerry Parker 	 * Set the author and committer using {@link #getDate()}.
2223e2b9b69SShawn O. Pearce 	 *
2233e2b9b69SShawn O. Pearce 	 * @param c
2243e2b9b69SShawn O. Pearce 	 *            the commit builder to store.
2253e2b9b69SShawn O. Pearce 	 */
setAuthorAndCommitter(org.eclipse.jgit.lib.CommitBuilder c)2263e2b9b69SShawn O. Pearce 	public void setAuthorAndCommitter(org.eclipse.jgit.lib.CommitBuilder c) {
227069040ddSTerry Parker 		c.setAuthor(new PersonIdent(defaultAuthor, getDate()));
228069040ddSTerry Parker 		c.setCommitter(new PersonIdent(defaultCommitter, getDate()));
2293e2b9b69SShawn O. Pearce 	}
2303e2b9b69SShawn O. Pearce 
2313e2b9b69SShawn O. Pearce 	/**
232f945c424SShawn O. Pearce 	 * Create a new blob object in the repository.
233f945c424SShawn O. Pearce 	 *
234f945c424SShawn O. Pearce 	 * @param content
235f945c424SShawn O. Pearce 	 *            file content, will be UTF-8 encoded.
236f945c424SShawn O. Pearce 	 * @return reference to the blob.
237f945c424SShawn O. Pearce 	 * @throws Exception
238f945c424SShawn O. Pearce 	 */
blob(String content)2396d370d83SHan-Wen Nienhuys 	public RevBlob blob(String content) throws Exception {
24030c6c754SDavid Pursehouse 		return blob(content.getBytes(UTF_8));
241f945c424SShawn O. Pearce 	}
242f945c424SShawn O. Pearce 
243f945c424SShawn O. Pearce 	/**
244f945c424SShawn O. Pearce 	 * Create a new blob object in the repository.
245f945c424SShawn O. Pearce 	 *
246f945c424SShawn O. Pearce 	 * @param content
247f945c424SShawn O. Pearce 	 *            binary file content.
24800e51e35STerry Parker 	 * @return the new, fully parsed blob.
249f945c424SShawn O. Pearce 	 * @throws Exception
250f945c424SShawn O. Pearce 	 */
blob(byte[] content)2516d370d83SHan-Wen Nienhuys 	public RevBlob blob(byte[] content) throws Exception {
25288530a17SShawn O. Pearce 		ObjectId id;
2536599111dSDave Borowitz 		try (ObjectInserter ins = inserter) {
2546599111dSDave Borowitz 			id = ins.insert(Constants.OBJ_BLOB, content);
2556599111dSDave Borowitz 			ins.flush();
25688530a17SShawn O. Pearce 		}
25700e51e35STerry Parker 		return (RevBlob) pool.parseAny(id);
258f945c424SShawn O. Pearce 	}
259f945c424SShawn O. Pearce 
260f945c424SShawn O. Pearce 	/**
261f945c424SShawn O. Pearce 	 * Construct a regular file mode tree entry.
262f945c424SShawn O. Pearce 	 *
263f945c424SShawn O. Pearce 	 * @param path
264f945c424SShawn O. Pearce 	 *            path of the file.
265f945c424SShawn O. Pearce 	 * @param blob
266f945c424SShawn O. Pearce 	 *            a blob, previously constructed in the repository.
267f945c424SShawn O. Pearce 	 * @return the entry.
268f945c424SShawn O. Pearce 	 * @throws Exception
269f945c424SShawn O. Pearce 	 */
file(String path, RevBlob blob)2706d370d83SHan-Wen Nienhuys 	public DirCacheEntry file(String path, RevBlob blob)
271f945c424SShawn O. Pearce 			throws Exception {
272f945c424SShawn O. Pearce 		final DirCacheEntry e = new DirCacheEntry(path);
273f945c424SShawn O. Pearce 		e.setFileMode(FileMode.REGULAR_FILE);
274f945c424SShawn O. Pearce 		e.setObjectId(blob);
275f945c424SShawn O. Pearce 		return e;
276f945c424SShawn O. Pearce 	}
277f945c424SShawn O. Pearce 
278f945c424SShawn O. Pearce 	/**
279f945c424SShawn O. Pearce 	 * Construct a tree from a specific listing of file entries.
280f945c424SShawn O. Pearce 	 *
281f945c424SShawn O. Pearce 	 * @param entries
282f945c424SShawn O. Pearce 	 *            the files to include in the tree. The collection does not need
283f945c424SShawn O. Pearce 	 *            to be sorted properly and may be empty.
28400e51e35STerry Parker 	 * @return the new, fully parsed tree specified by the entry list.
285f945c424SShawn O. Pearce 	 * @throws Exception
286f945c424SShawn O. Pearce 	 */
tree(DirCacheEntry... entries)2876d370d83SHan-Wen Nienhuys 	public RevTree tree(DirCacheEntry... entries) throws Exception {
288f945c424SShawn O. Pearce 		final DirCache dc = DirCache.newInCore();
289f945c424SShawn O. Pearce 		final DirCacheBuilder b = dc.builder();
29000e51e35STerry Parker 		for (DirCacheEntry e : entries) {
291f945c424SShawn O. Pearce 			b.add(e);
29200e51e35STerry Parker 		}
293f945c424SShawn O. Pearce 		b.finish();
29488530a17SShawn O. Pearce 		ObjectId root;
2956599111dSDave Borowitz 		try (ObjectInserter ins = inserter) {
2966599111dSDave Borowitz 			root = dc.writeTree(ins);
2976599111dSDave Borowitz 			ins.flush();
29888530a17SShawn O. Pearce 		}
29900e51e35STerry Parker 		return pool.parseTree(root);
300f945c424SShawn O. Pearce 	}
301f945c424SShawn O. Pearce 
302f945c424SShawn O. Pearce 	/**
303f945c424SShawn O. Pearce 	 * Lookup an entry stored in a tree, failing if not present.
304f945c424SShawn O. Pearce 	 *
305f945c424SShawn O. Pearce 	 * @param tree
306f945c424SShawn O. Pearce 	 *            the tree to search.
307f945c424SShawn O. Pearce 	 * @param path
308f945c424SShawn O. Pearce 	 *            the path to find the entry of.
309f945c424SShawn O. Pearce 	 * @return the parsed object entry at this path, never null.
310f945c424SShawn O. Pearce 	 * @throws Exception
311f945c424SShawn O. Pearce 	 */
get(RevTree tree, String path)3126d370d83SHan-Wen Nienhuys 	public RevObject get(RevTree tree, String path)
313d9e07a57SRobin Rosenberg 			throws Exception {
3146599111dSDave Borowitz 		try (TreeWalk tw = new TreeWalk(pool.getObjectReader())) {
315f945c424SShawn O. Pearce 			tw.setFilter(PathFilterGroup.createFromStrings(Collections
316f945c424SShawn O. Pearce 					.singleton(path)));
317f945c424SShawn O. Pearce 			tw.reset(tree);
318f945c424SShawn O. Pearce 			while (tw.next()) {
319f945c424SShawn O. Pearce 				if (tw.isSubtree() && !path.equals(tw.getPathString())) {
320f945c424SShawn O. Pearce 					tw.enterSubtree();
321f945c424SShawn O. Pearce 					continue;
322f945c424SShawn O. Pearce 				}
323f945c424SShawn O. Pearce 				final ObjectId entid = tw.getObjectId(0);
324f945c424SShawn O. Pearce 				final FileMode entmode = tw.getFileMode(0);
325f945c424SShawn O. Pearce 				return pool.lookupAny(entid, entmode.getObjectType());
326f945c424SShawn O. Pearce 			}
3276599111dSDave Borowitz 		}
328d9e07a57SRobin Rosenberg 		fail("Can't find " + path + " in tree " + tree.name());
329f945c424SShawn O. Pearce 		return null; // never reached.
330f945c424SShawn O. Pearce 	}
331f945c424SShawn O. Pearce 
332f945c424SShawn O. Pearce 	/**
333a80df538SAlex Spradlin 	 * Create a new, unparsed commit.
334a80df538SAlex Spradlin 	 * <p>
335a80df538SAlex Spradlin 	 * See {@link #unparsedCommit(int, RevTree, ObjectId...)}. The tree is the
336a80df538SAlex Spradlin 	 * empty tree (no files or subdirectories).
337a80df538SAlex Spradlin 	 *
338a80df538SAlex Spradlin 	 * @param parents
339a80df538SAlex Spradlin 	 *            zero or more IDs of the commit's parents.
340a80df538SAlex Spradlin 	 * @return the ID of the new commit.
341a80df538SAlex Spradlin 	 * @throws Exception
342a80df538SAlex Spradlin 	 */
unparsedCommit(ObjectId... parents)343a80df538SAlex Spradlin 	public ObjectId unparsedCommit(ObjectId... parents) throws Exception {
344a80df538SAlex Spradlin 		return unparsedCommit(1, tree(), parents);
345a80df538SAlex Spradlin 	}
346a80df538SAlex Spradlin 
347a80df538SAlex Spradlin 	/**
348f945c424SShawn O. Pearce 	 * Create a new commit.
349f945c424SShawn O. Pearce 	 * <p>
350f945c424SShawn O. Pearce 	 * See {@link #commit(int, RevTree, RevCommit...)}. The tree is the empty
351f945c424SShawn O. Pearce 	 * tree (no files or subdirectories).
352f945c424SShawn O. Pearce 	 *
353f945c424SShawn O. Pearce 	 * @param parents
354f945c424SShawn O. Pearce 	 *            zero or more parents of the commit.
355f945c424SShawn O. Pearce 	 * @return the new commit.
356f945c424SShawn O. Pearce 	 * @throws Exception
357f945c424SShawn O. Pearce 	 */
commit(RevCommit... parents)3586d370d83SHan-Wen Nienhuys 	public RevCommit commit(RevCommit... parents) throws Exception {
359f945c424SShawn O. Pearce 		return commit(1, tree(), parents);
360f945c424SShawn O. Pearce 	}
361f945c424SShawn O. Pearce 
362f945c424SShawn O. Pearce 	/**
363f945c424SShawn O. Pearce 	 * Create a new commit.
364f945c424SShawn O. Pearce 	 * <p>
365f945c424SShawn O. Pearce 	 * See {@link #commit(int, RevTree, RevCommit...)}.
366f945c424SShawn O. Pearce 	 *
367f945c424SShawn O. Pearce 	 * @param tree
368f945c424SShawn O. Pearce 	 *            the root tree for the commit.
369f945c424SShawn O. Pearce 	 * @param parents
370f945c424SShawn O. Pearce 	 *            zero or more parents of the commit.
371f945c424SShawn O. Pearce 	 * @return the new commit.
372f945c424SShawn O. Pearce 	 * @throws Exception
373f945c424SShawn O. Pearce 	 */
commit(RevTree tree, RevCommit... parents)3746d370d83SHan-Wen Nienhuys 	public RevCommit commit(RevTree tree, RevCommit... parents)
375f945c424SShawn O. Pearce 			throws Exception {
376f945c424SShawn O. Pearce 		return commit(1, tree, parents);
377f945c424SShawn O. Pearce 	}
378f945c424SShawn O. Pearce 
379f945c424SShawn O. Pearce 	/**
380f945c424SShawn O. Pearce 	 * Create a new commit.
381f945c424SShawn O. Pearce 	 * <p>
382f945c424SShawn O. Pearce 	 * See {@link #commit(int, RevTree, RevCommit...)}. The tree is the empty
383f945c424SShawn O. Pearce 	 * tree (no files or subdirectories).
384f945c424SShawn O. Pearce 	 *
385f945c424SShawn O. Pearce 	 * @param secDelta
386f945c424SShawn O. Pearce 	 *            number of seconds to advance {@link #tick(int)} by.
387f945c424SShawn O. Pearce 	 * @param parents
388f945c424SShawn O. Pearce 	 *            zero or more parents of the commit.
389f945c424SShawn O. Pearce 	 * @return the new commit.
390f945c424SShawn O. Pearce 	 * @throws Exception
391f945c424SShawn O. Pearce 	 */
commit(int secDelta, RevCommit... parents)3926d370d83SHan-Wen Nienhuys 	public RevCommit commit(int secDelta, RevCommit... parents)
393f945c424SShawn O. Pearce 			throws Exception {
394f945c424SShawn O. Pearce 		return commit(secDelta, tree(), parents);
395f945c424SShawn O. Pearce 	}
396f945c424SShawn O. Pearce 
397f945c424SShawn O. Pearce 	/**
398f945c424SShawn O. Pearce 	 * Create a new commit.
399f945c424SShawn O. Pearce 	 * <p>
400f945c424SShawn O. Pearce 	 * The author and committer identities are stored using the current
401f945c424SShawn O. Pearce 	 * timestamp, after being incremented by {@code secDelta}. The message body
402f945c424SShawn O. Pearce 	 * is empty.
403f945c424SShawn O. Pearce 	 *
404f945c424SShawn O. Pearce 	 * @param secDelta
405f945c424SShawn O. Pearce 	 *            number of seconds to advance {@link #tick(int)} by.
406f945c424SShawn O. Pearce 	 * @param tree
407f945c424SShawn O. Pearce 	 *            the root tree for the commit.
408f945c424SShawn O. Pearce 	 * @param parents
409f945c424SShawn O. Pearce 	 *            zero or more parents of the commit.
41000e51e35STerry Parker 	 * @return the new, fully parsed commit.
411f945c424SShawn O. Pearce 	 * @throws Exception
412f945c424SShawn O. Pearce 	 */
commit(final int secDelta, final RevTree tree, final RevCommit... parents)413f945c424SShawn O. Pearce 	public RevCommit commit(final int secDelta, final RevTree tree,
414f945c424SShawn O. Pearce 			final RevCommit... parents) throws Exception {
415a80df538SAlex Spradlin 		ObjectId id = unparsedCommit(secDelta, tree, parents);
416a80df538SAlex Spradlin 		return pool.parseCommit(id);
417a80df538SAlex Spradlin 	}
418a80df538SAlex Spradlin 
419a80df538SAlex Spradlin 	/**
420a80df538SAlex Spradlin 	 * Create a new, unparsed commit.
421a80df538SAlex Spradlin 	 * <p>
422a80df538SAlex Spradlin 	 * The author and committer identities are stored using the current
423a80df538SAlex Spradlin 	 * timestamp, after being incremented by {@code secDelta}. The message body
424a80df538SAlex Spradlin 	 * is empty.
425a80df538SAlex Spradlin 	 *
426a80df538SAlex Spradlin 	 * @param secDelta
427a80df538SAlex Spradlin 	 *            number of seconds to advance {@link #tick(int)} by.
428a80df538SAlex Spradlin 	 * @param tree
429a80df538SAlex Spradlin 	 *            the root tree for the commit.
430a80df538SAlex Spradlin 	 * @param parents
431a80df538SAlex Spradlin 	 *            zero or more IDs of the commit's parents.
432a80df538SAlex Spradlin 	 * @return the ID of the new commit.
433a80df538SAlex Spradlin 	 * @throws Exception
434a80df538SAlex Spradlin 	 */
unparsedCommit(final int secDelta, final RevTree tree, final ObjectId... parents)435a80df538SAlex Spradlin 	public ObjectId unparsedCommit(final int secDelta, final RevTree tree,
436a80df538SAlex Spradlin 			final ObjectId... parents) throws Exception {
437f945c424SShawn O. Pearce 		tick(secDelta);
438f945c424SShawn O. Pearce 
43922b28569SShawn O. Pearce 		final org.eclipse.jgit.lib.CommitBuilder c;
44022b28569SShawn O. Pearce 
44122b28569SShawn O. Pearce 		c = new org.eclipse.jgit.lib.CommitBuilder();
442f945c424SShawn O. Pearce 		c.setTreeId(tree);
443f945c424SShawn O. Pearce 		c.setParentIds(parents);
444069040ddSTerry Parker 		c.setAuthor(new PersonIdent(defaultAuthor, getDate()));
445069040ddSTerry Parker 		c.setCommitter(new PersonIdent(defaultCommitter, getDate()));
446f945c424SShawn O. Pearce 		c.setMessage("");
44788530a17SShawn O. Pearce 		ObjectId id;
4486599111dSDave Borowitz 		try (ObjectInserter ins = inserter) {
4496599111dSDave Borowitz 			id = ins.insert(c);
4506599111dSDave Borowitz 			ins.flush();
45188530a17SShawn O. Pearce 		}
452a80df538SAlex Spradlin 		return id;
453f945c424SShawn O. Pearce 	}
454f945c424SShawn O. Pearce 
455a90b75b4SMatthias Sohn 	/**
456a90b75b4SMatthias Sohn 	 * Create commit builder
457a90b75b4SMatthias Sohn 	 *
458a90b75b4SMatthias Sohn 	 * @return a new commit builder.
459a90b75b4SMatthias Sohn 	 */
commit()460f945c424SShawn O. Pearce 	public CommitBuilder commit() {
461f945c424SShawn O. Pearce 		return new CommitBuilder();
462f945c424SShawn O. Pearce 	}
463f945c424SShawn O. Pearce 
464f945c424SShawn O. Pearce 	/**
465f945c424SShawn O. Pearce 	 * Construct an annotated tag object pointing at another object.
466f945c424SShawn O. Pearce 	 * <p>
467f945c424SShawn O. Pearce 	 * The tagger is the committer identity, at the current time as specified by
468f945c424SShawn O. Pearce 	 * {@link #tick(int)}. The time is not increased.
469f945c424SShawn O. Pearce 	 * <p>
470f945c424SShawn O. Pearce 	 * The tag message is empty.
471f945c424SShawn O. Pearce 	 *
472f945c424SShawn O. Pearce 	 * @param name
473f945c424SShawn O. Pearce 	 *            name of the tag. Traditionally a tag name should not start
474f945c424SShawn O. Pearce 	 *            with {@code refs/tags/}.
475f945c424SShawn O. Pearce 	 * @param dst
476f945c424SShawn O. Pearce 	 *            object the tag should be pointed at.
47700e51e35STerry Parker 	 * @return the new, fully parsed annotated tag object.
478f945c424SShawn O. Pearce 	 * @throws Exception
479f945c424SShawn O. Pearce 	 */
tag(String name, RevObject dst)4806d370d83SHan-Wen Nienhuys 	public RevTag tag(String name, RevObject dst) throws Exception {
48122b28569SShawn O. Pearce 		final TagBuilder t = new TagBuilder();
482707912b3SShawn O. Pearce 		t.setObjectId(dst);
483f945c424SShawn O. Pearce 		t.setTag(name);
484069040ddSTerry Parker 		t.setTagger(new PersonIdent(defaultCommitter, getDate()));
485f945c424SShawn O. Pearce 		t.setMessage("");
48688530a17SShawn O. Pearce 		ObjectId id;
4876599111dSDave Borowitz 		try (ObjectInserter ins = inserter) {
4886599111dSDave Borowitz 			id = ins.insert(t);
4896599111dSDave Borowitz 			ins.flush();
49088530a17SShawn O. Pearce 		}
49100e51e35STerry Parker 		return pool.parseTag(id);
492f945c424SShawn O. Pearce 	}
493f945c424SShawn O. Pearce 
494f945c424SShawn O. Pearce 	/**
495f945c424SShawn O. Pearce 	 * Update a reference to point to an object.
496f945c424SShawn O. Pearce 	 *
497f945c424SShawn O. Pearce 	 * @param ref
498f945c424SShawn O. Pearce 	 *            the name of the reference to update to. If {@code ref} does
499f945c424SShawn O. Pearce 	 *            not start with {@code refs/} and is not the magic names
500f945c424SShawn O. Pearce 	 *            {@code HEAD} {@code FETCH_HEAD} or {@code MERGE_HEAD}, then
501f945c424SShawn O. Pearce 	 *            {@code refs/heads/} will be prefixed in front of the given
502f945c424SShawn O. Pearce 	 *            name, thereby assuming it is a branch.
503f945c424SShawn O. Pearce 	 * @param to
504f945c424SShawn O. Pearce 	 *            the target object.
505f945c424SShawn O. Pearce 	 * @return the target object.
506f945c424SShawn O. Pearce 	 * @throws Exception
507f945c424SShawn O. Pearce 	 */
update(String ref, CommitBuilder to)508f945c424SShawn O. Pearce 	public RevCommit update(String ref, CommitBuilder to) throws Exception {
509f945c424SShawn O. Pearce 		return update(ref, to.create());
510f945c424SShawn O. Pearce 	}
511f945c424SShawn O. Pearce 
512f945c424SShawn O. Pearce 	/**
513da85ca73SDave Borowitz 	 * Amend an existing ref.
514da85ca73SDave Borowitz 	 *
515da85ca73SDave Borowitz 	 * @param ref
516da85ca73SDave Borowitz 	 *            the name of the reference to amend, which must already exist.
517da85ca73SDave Borowitz 	 *            If {@code ref} does not start with {@code refs/} and is not the
518da85ca73SDave Borowitz 	 *            magic names {@code HEAD} {@code FETCH_HEAD} or {@code
519da85ca73SDave Borowitz 	 *            MERGE_HEAD}, then {@code refs/heads/} will be prefixed in front
520da85ca73SDave Borowitz 	 *            of the given name, thereby assuming it is a branch.
521da85ca73SDave Borowitz 	 * @return commit builder that amends the branch on commit.
522da85ca73SDave Borowitz 	 * @throws Exception
523da85ca73SDave Borowitz 	 */
amendRef(String ref)524da85ca73SDave Borowitz 	public CommitBuilder amendRef(String ref) throws Exception {
525da85ca73SDave Borowitz 		String name = normalizeRef(ref);
5264c236ff4SMatthias Sohn 		Ref r = db.exactRef(name);
527da85ca73SDave Borowitz 		if (r == null)
528da85ca73SDave Borowitz 			throw new IOException("Not a ref: " + ref);
529da85ca73SDave Borowitz 		return amend(pool.parseCommit(r.getObjectId()), branch(name).commit());
530da85ca73SDave Borowitz 	}
531da85ca73SDave Borowitz 
532da85ca73SDave Borowitz 	/**
533da85ca73SDave Borowitz 	 * Amend an existing commit.
534da85ca73SDave Borowitz 	 *
535da85ca73SDave Borowitz 	 * @param id
536da85ca73SDave Borowitz 	 *            the id of the commit to amend.
537da85ca73SDave Borowitz 	 * @return commit builder.
538da85ca73SDave Borowitz 	 * @throws Exception
539da85ca73SDave Borowitz 	 */
amend(AnyObjectId id)540da85ca73SDave Borowitz 	public CommitBuilder amend(AnyObjectId id) throws Exception {
541da85ca73SDave Borowitz 		return amend(pool.parseCommit(id), commit());
542da85ca73SDave Borowitz 	}
543da85ca73SDave Borowitz 
amend(RevCommit old, CommitBuilder b)544da85ca73SDave Borowitz 	private CommitBuilder amend(RevCommit old, CommitBuilder b) throws Exception {
545da85ca73SDave Borowitz 		pool.parseBody(old);
546da85ca73SDave Borowitz 		b.author(old.getAuthorIdent());
547da85ca73SDave Borowitz 		b.committer(old.getCommitterIdent());
548da85ca73SDave Borowitz 		b.message(old.getFullMessage());
549da85ca73SDave Borowitz 		// Use the committer name from the old commit, but update it after ticking
550da85ca73SDave Borowitz 		// the clock in CommitBuilder#create().
551da85ca73SDave Borowitz 		b.updateCommitterTime = true;
552da85ca73SDave Borowitz 
553da85ca73SDave Borowitz 		// Reset parents to original parents.
554da85ca73SDave Borowitz 		b.noParents();
555da85ca73SDave Borowitz 		for (int i = 0; i < old.getParentCount(); i++)
556da85ca73SDave Borowitz 			b.parent(old.getParent(i));
557da85ca73SDave Borowitz 
558da85ca73SDave Borowitz 		// Reset tree to original tree; resetting parents reset tree contents to the
559da85ca73SDave Borowitz 		// first parent.
560da85ca73SDave Borowitz 		b.tree.clear();
561da85ca73SDave Borowitz 		try (TreeWalk tw = new TreeWalk(db)) {
562da85ca73SDave Borowitz 			tw.reset(old.getTree());
563da85ca73SDave Borowitz 			tw.setRecursive(true);
564da85ca73SDave Borowitz 			while (tw.next()) {
565da85ca73SDave Borowitz 				b.edit(new PathEdit(tw.getPathString()) {
566da85ca73SDave Borowitz 					@Override
567da85ca73SDave Borowitz 					public void apply(DirCacheEntry ent) {
568da85ca73SDave Borowitz 						ent.setFileMode(tw.getFileMode(0));
569da85ca73SDave Borowitz 						ent.setObjectId(tw.getObjectId(0));
570da85ca73SDave Borowitz 					}
571da85ca73SDave Borowitz 				});
572da85ca73SDave Borowitz 			}
573da85ca73SDave Borowitz 		}
574da85ca73SDave Borowitz 
575da85ca73SDave Borowitz 		return b;
576da85ca73SDave Borowitz 	}
577da85ca73SDave Borowitz 
578da85ca73SDave Borowitz 	/**
579f945c424SShawn O. Pearce 	 * Update a reference to point to an object.
580f945c424SShawn O. Pearce 	 *
581f945c424SShawn O. Pearce 	 * @param <T>
582f945c424SShawn O. Pearce 	 *            type of the target object.
583f945c424SShawn O. Pearce 	 * @param ref
584f945c424SShawn O. Pearce 	 *            the name of the reference to update to. If {@code ref} does
585f945c424SShawn O. Pearce 	 *            not start with {@code refs/} and is not the magic names
586f945c424SShawn O. Pearce 	 *            {@code HEAD} {@code FETCH_HEAD} or {@code MERGE_HEAD}, then
587f945c424SShawn O. Pearce 	 *            {@code refs/heads/} will be prefixed in front of the given
588f945c424SShawn O. Pearce 	 *            name, thereby assuming it is a branch.
589f945c424SShawn O. Pearce 	 * @param obj
590f945c424SShawn O. Pearce 	 *            the target object.
591f945c424SShawn O. Pearce 	 * @return the target object.
592f945c424SShawn O. Pearce 	 * @throws Exception
593f945c424SShawn O. Pearce 	 */
update(String ref, T obj)594f945c424SShawn O. Pearce 	public <T extends AnyObjectId> T update(String ref, T obj) throws Exception {
595da85ca73SDave Borowitz 		ref = normalizeRef(ref);
596f945c424SShawn O. Pearce 		RefUpdate u = db.updateRef(ref);
597f945c424SShawn O. Pearce 		u.setNewObjectId(obj);
598f945c424SShawn O. Pearce 		switch (u.forceUpdate()) {
599f945c424SShawn O. Pearce 		case FAST_FORWARD:
600f945c424SShawn O. Pearce 		case FORCED:
601f945c424SShawn O. Pearce 		case NEW:
602f945c424SShawn O. Pearce 		case NO_CHANGE:
603f945c424SShawn O. Pearce 			updateServerInfo();
604f945c424SShawn O. Pearce 			return obj;
605f945c424SShawn O. Pearce 
606f945c424SShawn O. Pearce 		default:
607f945c424SShawn O. Pearce 			throw new IOException("Cannot write " + ref + " " + u.getResult());
608f945c424SShawn O. Pearce 		}
609f945c424SShawn O. Pearce 	}
610f945c424SShawn O. Pearce 
611bc9df9c6SJonathan Nieder 	/**
612bc9df9c6SJonathan Nieder 	 * Delete a reference.
613bc9df9c6SJonathan Nieder 	 *
614bc9df9c6SJonathan Nieder 	 * @param ref
615bc9df9c6SJonathan Nieder 	 *	      the name of the reference to delete. This is normalized
6160c72f60fSMatthias Sohn 	 *	      in the same way as {@link #update(String, AnyObjectId)}.
617bc9df9c6SJonathan Nieder 	 * @throws Exception
618bc9df9c6SJonathan Nieder 	 * @since 4.4
619bc9df9c6SJonathan Nieder 	 */
delete(String ref)620bc9df9c6SJonathan Nieder 	public void delete(String ref) throws Exception {
621bc9df9c6SJonathan Nieder 		ref = normalizeRef(ref);
622bc9df9c6SJonathan Nieder 		RefUpdate u = db.updateRef(ref);
62315a189e4SMinh Thai 		u.setForceUpdate(true);
624bc9df9c6SJonathan Nieder 		switch (u.delete()) {
625bc9df9c6SJonathan Nieder 		case FAST_FORWARD:
626bc9df9c6SJonathan Nieder 		case FORCED:
627bc9df9c6SJonathan Nieder 		case NEW:
628bc9df9c6SJonathan Nieder 		case NO_CHANGE:
629bc9df9c6SJonathan Nieder 			updateServerInfo();
630bc9df9c6SJonathan Nieder 			return;
631bc9df9c6SJonathan Nieder 
632bc9df9c6SJonathan Nieder 		default:
633bc9df9c6SJonathan Nieder 			throw new IOException("Cannot delete " + ref + " " + u.getResult());
634bc9df9c6SJonathan Nieder 		}
635bc9df9c6SJonathan Nieder 	}
636bc9df9c6SJonathan Nieder 
normalizeRef(String ref)637da85ca73SDave Borowitz 	private static String normalizeRef(String ref) {
638da85ca73SDave Borowitz 		if (Constants.HEAD.equals(ref)) {
639da85ca73SDave Borowitz 			// nothing
640da85ca73SDave Borowitz 		} else if ("FETCH_HEAD".equals(ref)) {
641da85ca73SDave Borowitz 			// nothing
642da85ca73SDave Borowitz 		} else if ("MERGE_HEAD".equals(ref)) {
643da85ca73SDave Borowitz 			// nothing
644da85ca73SDave Borowitz 		} else if (ref.startsWith(Constants.R_REFS)) {
645da85ca73SDave Borowitz 			// nothing
646da85ca73SDave Borowitz 		} else
647da85ca73SDave Borowitz 			ref = Constants.R_HEADS + ref;
648da85ca73SDave Borowitz 		return ref;
649da85ca73SDave Borowitz 	}
650da85ca73SDave Borowitz 
651f945c424SShawn O. Pearce 	/**
652d79cadb3SDave Borowitz 	 * Soft-reset HEAD to a detached state.
653a90b75b4SMatthias Sohn 	 *
654d79cadb3SDave Borowitz 	 * @param id
655d79cadb3SDave Borowitz 	 *            ID of detached head.
656d79cadb3SDave Borowitz 	 * @throws Exception
657d79cadb3SDave Borowitz 	 * @see #reset(String)
658d79cadb3SDave Borowitz 	 */
reset(AnyObjectId id)659d79cadb3SDave Borowitz 	public void reset(AnyObjectId id) throws Exception {
660d79cadb3SDave Borowitz 		RefUpdate ru = db.updateRef(Constants.HEAD, true);
661d79cadb3SDave Borowitz 		ru.setNewObjectId(id);
662d79cadb3SDave Borowitz 		RefUpdate.Result result = ru.forceUpdate();
663d79cadb3SDave Borowitz 		switch (result) {
664d79cadb3SDave Borowitz 			case FAST_FORWARD:
665d79cadb3SDave Borowitz 			case FORCED:
666d79cadb3SDave Borowitz 			case NEW:
667d79cadb3SDave Borowitz 			case NO_CHANGE:
668d79cadb3SDave Borowitz 				break;
669d79cadb3SDave Borowitz 			default:
670d79cadb3SDave Borowitz 				throw new IOException(String.format(
671d79cadb3SDave Borowitz 						"Checkout \"%s\" failed: %s", id.name(), result));
672d79cadb3SDave Borowitz 		}
673d79cadb3SDave Borowitz 	}
674d79cadb3SDave Borowitz 
675d79cadb3SDave Borowitz 	/**
676d79cadb3SDave Borowitz 	 * Soft-reset HEAD to a different commit.
677d79cadb3SDave Borowitz 	 * <p>
678d79cadb3SDave Borowitz 	 * This is equivalent to {@code git reset --soft} in that it modifies HEAD but
679d79cadb3SDave Borowitz 	 * not the index or the working tree of a non-bare repository.
680d79cadb3SDave Borowitz 	 *
681d79cadb3SDave Borowitz 	 * @param name
682d79cadb3SDave Borowitz 	 *            revision string; either an existing ref name, or something that
683d79cadb3SDave Borowitz 	 *            can be parsed to an object ID.
684d79cadb3SDave Borowitz 	 * @throws Exception
685d79cadb3SDave Borowitz 	 */
reset(String name)686d79cadb3SDave Borowitz 	public void reset(String name) throws Exception {
687d79cadb3SDave Borowitz 		RefUpdate.Result result;
688d79cadb3SDave Borowitz 		ObjectId id = db.resolve(name);
689d79cadb3SDave Borowitz 		if (id == null)
690d79cadb3SDave Borowitz 			throw new IOException("Not a revision: " + name);
691d79cadb3SDave Borowitz 		RefUpdate ru = db.updateRef(Constants.HEAD, false);
692d79cadb3SDave Borowitz 		ru.setNewObjectId(id);
693d79cadb3SDave Borowitz 		result = ru.forceUpdate();
694d79cadb3SDave Borowitz 		switch (result) {
695d79cadb3SDave Borowitz 			case FAST_FORWARD:
696d79cadb3SDave Borowitz 			case FORCED:
697d79cadb3SDave Borowitz 			case NEW:
698d79cadb3SDave Borowitz 			case NO_CHANGE:
699d79cadb3SDave Borowitz 				break;
700d79cadb3SDave Borowitz 			default:
701d79cadb3SDave Borowitz 				throw new IOException(String.format(
702d79cadb3SDave Borowitz 						"Checkout \"%s\" failed: %s", name, result));
703d79cadb3SDave Borowitz 		}
704d79cadb3SDave Borowitz 	}
705d79cadb3SDave Borowitz 
706d79cadb3SDave Borowitz 	/**
707f1a15f7eSDave Borowitz 	 * Cherry-pick a commit onto HEAD.
708f1a15f7eSDave Borowitz 	 * <p>
709f1a15f7eSDave Borowitz 	 * This differs from {@code git cherry-pick} in that it works in a bare
710f1a15f7eSDave Borowitz 	 * repository. As a result, any merge failure results in an exception, as
711f1a15f7eSDave Borowitz 	 * there is no way to recover.
712f1a15f7eSDave Borowitz 	 *
713f1a15f7eSDave Borowitz 	 * @param id
714f1a15f7eSDave Borowitz 	 *            commit-ish to cherry-pick.
71500e51e35STerry Parker 	 * @return the new, fully parsed commit, or null if no work was done due to
71600e51e35STerry Parker 	 *         the resulting tree being identical.
717f1a15f7eSDave Borowitz 	 * @throws Exception
718f1a15f7eSDave Borowitz 	 */
cherryPick(AnyObjectId id)719f1a15f7eSDave Borowitz 	public RevCommit cherryPick(AnyObjectId id) throws Exception {
720f1a15f7eSDave Borowitz 		RevCommit commit = pool.parseCommit(id);
721f1a15f7eSDave Borowitz 		pool.parseBody(commit);
722f1a15f7eSDave Borowitz 		if (commit.getParentCount() != 1)
723f1a15f7eSDave Borowitz 			throw new IOException(String.format(
724f1a15f7eSDave Borowitz 					"Expected 1 parent for %s, found: %s",
725f1a15f7eSDave Borowitz 					id.name(), Arrays.asList(commit.getParents())));
726f1a15f7eSDave Borowitz 		RevCommit parent = commit.getParent(0);
727f1a15f7eSDave Borowitz 		pool.parseHeaders(parent);
728f1a15f7eSDave Borowitz 
7294c236ff4SMatthias Sohn 		Ref headRef = db.exactRef(Constants.HEAD);
730f1a15f7eSDave Borowitz 		if (headRef == null)
731f1a15f7eSDave Borowitz 			throw new IOException("Missing HEAD");
732f1a15f7eSDave Borowitz 		RevCommit head = pool.parseCommit(headRef.getObjectId());
733f1a15f7eSDave Borowitz 
734f1a15f7eSDave Borowitz 		ThreeWayMerger merger = MergeStrategy.RECURSIVE.newMerger(db, true);
735f1a15f7eSDave Borowitz 		merger.setBase(parent.getTree());
736f1a15f7eSDave Borowitz 		if (merger.merge(head, commit)) {
7378f7e8513SMatthias Sohn 			if (AnyObjectId.isEqual(head.getTree(), merger.getResultTreeId()))
738f1a15f7eSDave Borowitz 				return null;
739f1a15f7eSDave Borowitz 			tick(1);
740f1a15f7eSDave Borowitz 			org.eclipse.jgit.lib.CommitBuilder b =
741f1a15f7eSDave Borowitz 					new org.eclipse.jgit.lib.CommitBuilder();
742f1a15f7eSDave Borowitz 			b.setParentId(head);
743f1a15f7eSDave Borowitz 			b.setTreeId(merger.getResultTreeId());
744f1a15f7eSDave Borowitz 			b.setAuthor(commit.getAuthorIdent());
745069040ddSTerry Parker 			b.setCommitter(new PersonIdent(defaultCommitter, getDate()));
746f1a15f7eSDave Borowitz 			b.setMessage(commit.getFullMessage());
747f1a15f7eSDave Borowitz 			ObjectId result;
748f1a15f7eSDave Borowitz 			try (ObjectInserter ins = inserter) {
749f1a15f7eSDave Borowitz 				result = ins.insert(b);
750f1a15f7eSDave Borowitz 				ins.flush();
751f1a15f7eSDave Borowitz 			}
752f1a15f7eSDave Borowitz 			update(Constants.HEAD, result);
753f1a15f7eSDave Borowitz 			return pool.parseCommit(result);
754f1a15f7eSDave Borowitz 		}
75598cdca9bSDavid Pursehouse 		throw new IOException("Merge conflict");
756f1a15f7eSDave Borowitz 	}
757f1a15f7eSDave Borowitz 
758f1a15f7eSDave Borowitz 	/**
759f945c424SShawn O. Pearce 	 * Update the dumb client server info files.
760f945c424SShawn O. Pearce 	 *
761f945c424SShawn O. Pearce 	 * @throws Exception
762f945c424SShawn O. Pearce 	 */
updateServerInfo()763f945c424SShawn O. Pearce 	public void updateServerInfo() throws Exception {
76489d4a737SShawn O. Pearce 		if (db instanceof FileRepository) {
76589d4a737SShawn O. Pearce 			final FileRepository fr = (FileRepository) db;
7661f6d43a6SDavid Pursehouse 			RefWriter rw = new RefWriter(fr.getRefDatabase().getRefs()) {
767f945c424SShawn O. Pearce 				@Override
7686d370d83SHan-Wen Nienhuys 				protected void writeFile(String name, byte[] bin)
769f945c424SShawn O. Pearce 						throws IOException {
77089d4a737SShawn O. Pearce 					File path = new File(fr.getDirectory(), name);
77189d4a737SShawn O. Pearce 					TestRepository.this.writeFile(path, bin);
772f945c424SShawn O. Pearce 				}
773f945c424SShawn O. Pearce 			};
774f945c424SShawn O. Pearce 			rw.writePackedRefs();
775f945c424SShawn O. Pearce 			rw.writeInfoRefs();
776f5eb0d93SShawn O. Pearce 
777f5eb0d93SShawn O. Pearce 			final StringBuilder w = new StringBuilder();
778efb154fcSNasser Grainawi 			for (Pack p : fr.getObjectDatabase().getPacks()) {
779f5eb0d93SShawn O. Pearce 				w.append("P ");
780f5eb0d93SShawn O. Pearce 				w.append(p.getPackFile().getName());
781f5eb0d93SShawn O. Pearce 				w.append('\n');
782f5eb0d93SShawn O. Pearce 			}
78389d4a737SShawn O. Pearce 			writeFile(new File(new File(fr.getObjectDatabase().getDirectory(),
78489d4a737SShawn O. Pearce 					"info"), "packs"), Constants.encodeASCII(w.toString()));
785f945c424SShawn O. Pearce 		}
786f945c424SShawn O. Pearce 	}
787f945c424SShawn O. Pearce 
788f945c424SShawn O. Pearce 	/**
789f945c424SShawn O. Pearce 	 * Ensure the body of the given object has been parsed.
790f945c424SShawn O. Pearce 	 *
791f945c424SShawn O. Pearce 	 * @param <T>
792a90b75b4SMatthias Sohn 	 *            type of object, e.g. {@link org.eclipse.jgit.revwalk.RevTag}
793a90b75b4SMatthias Sohn 	 *            or {@link org.eclipse.jgit.revwalk.RevCommit}.
794f945c424SShawn O. Pearce 	 * @param object
795f945c424SShawn O. Pearce 	 *            reference to the (possibly unparsed) object to force body
796f945c424SShawn O. Pearce 	 *            parsing of.
797f945c424SShawn O. Pearce 	 * @return {@code object}
798f945c424SShawn O. Pearce 	 * @throws Exception
799f945c424SShawn O. Pearce 	 */
parseBody(T object)8006d370d83SHan-Wen Nienhuys 	public <T extends RevObject> T parseBody(T object) throws Exception {
801f945c424SShawn O. Pearce 		pool.parseBody(object);
802f945c424SShawn O. Pearce 		return object;
803f945c424SShawn O. Pearce 	}
804f945c424SShawn O. Pearce 
805f945c424SShawn O. Pearce 	/**
806f945c424SShawn O. Pearce 	 * Create a new branch builder for this repository.
807f945c424SShawn O. Pearce 	 *
808f945c424SShawn O. Pearce 	 * @param ref
809f945c424SShawn O. Pearce 	 *            name of the branch to be constructed. If {@code ref} does not
810f945c424SShawn O. Pearce 	 *            start with {@code refs/} the prefix {@code refs/heads/} will
811f945c424SShawn O. Pearce 	 *            be added.
812f945c424SShawn O. Pearce 	 * @return builder for the named branch.
813f945c424SShawn O. Pearce 	 */
branch(String ref)814f945c424SShawn O. Pearce 	public BranchBuilder branch(String ref) {
815f945c424SShawn O. Pearce 		if (Constants.HEAD.equals(ref)) {
816e2513558SRobin Rosenberg 			// nothing
817f945c424SShawn O. Pearce 		} else if (ref.startsWith(Constants.R_REFS)) {
818e2513558SRobin Rosenberg 			// nothing
819f945c424SShawn O. Pearce 		} else
820f945c424SShawn O. Pearce 			ref = Constants.R_HEADS + ref;
821f945c424SShawn O. Pearce 		return new BranchBuilder(ref);
822f945c424SShawn O. Pearce 	}
823f945c424SShawn O. Pearce 
824f5eb0d93SShawn O. Pearce 	/**
825a5514932SSasa Zivkov 	 * Tag an object using a lightweight tag.
826a5514932SSasa Zivkov 	 *
827a5514932SSasa Zivkov 	 * @param name
828a5514932SSasa Zivkov 	 *            the tag name. The /refs/tags/ prefix will be added if the name
829a5514932SSasa Zivkov 	 *            doesn't start with it
830a5514932SSasa Zivkov 	 * @param obj
831a5514932SSasa Zivkov 	 *            the object to tag
832a5514932SSasa Zivkov 	 * @return the tagged object
833a5514932SSasa Zivkov 	 * @throws Exception
834a5514932SSasa Zivkov 	 */
lightweightTag(String name, ObjectId obj)835a5514932SSasa Zivkov 	public ObjectId lightweightTag(String name, ObjectId obj) throws Exception {
836a5514932SSasa Zivkov 		if (!name.startsWith(Constants.R_TAGS))
837a5514932SSasa Zivkov 			name = Constants.R_TAGS + name;
838a5514932SSasa Zivkov 		return update(name, obj);
839a5514932SSasa Zivkov 	}
840a5514932SSasa Zivkov 
841a5514932SSasa Zivkov 	/**
842f5eb0d93SShawn O. Pearce 	 * Run consistency checks against the object database.
843f5eb0d93SShawn O. Pearce 	 * <p>
844f5eb0d93SShawn O. Pearce 	 * This method completes silently if the checks pass. A temporary revision
845f5eb0d93SShawn O. Pearce 	 * pool is constructed during the checking.
846f5eb0d93SShawn O. Pearce 	 *
847f5eb0d93SShawn O. Pearce 	 * @param tips
848f5eb0d93SShawn O. Pearce 	 *            the tips to start checking from; if not supplied the refs of
849f5eb0d93SShawn O. Pearce 	 *            the repository are used instead.
850f5eb0d93SShawn O. Pearce 	 * @throws MissingObjectException
851f5eb0d93SShawn O. Pearce 	 * @throws IncorrectObjectTypeException
852f5eb0d93SShawn O. Pearce 	 * @throws IOException
853f5eb0d93SShawn O. Pearce 	 */
fsck(RevObject... tips)854f5eb0d93SShawn O. Pearce 	public void fsck(RevObject... tips) throws MissingObjectException,
855f5eb0d93SShawn O. Pearce 			IncorrectObjectTypeException, IOException {
8566599111dSDave Borowitz 		try (ObjectWalk ow = new ObjectWalk(db)) {
857f5eb0d93SShawn O. Pearce 			if (tips.length != 0) {
858f5eb0d93SShawn O. Pearce 				for (RevObject o : tips)
859f5eb0d93SShawn O. Pearce 					ow.markStart(ow.parseAny(o));
860f5eb0d93SShawn O. Pearce 			} else {
8611f6d43a6SDavid Pursehouse 				for (Ref r : db.getRefDatabase().getRefs())
862f5eb0d93SShawn O. Pearce 					ow.markStart(ow.parseAny(r.getObjectId()));
863f5eb0d93SShawn O. Pearce 			}
864f5eb0d93SShawn O. Pearce 
865f5eb0d93SShawn O. Pearce 			ObjectChecker oc = new ObjectChecker();
866f5eb0d93SShawn O. Pearce 			for (;;) {
867f5eb0d93SShawn O. Pearce 				final RevCommit o = ow.next();
868f5eb0d93SShawn O. Pearce 				if (o == null)
869f5eb0d93SShawn O. Pearce 					break;
870f5eb0d93SShawn O. Pearce 
871acb7be2cSShawn O. Pearce 				final byte[] bin = db.open(o, o.getType()).getCachedBytes();
872fa7ce0e0SShawn Pearce 				oc.checkCommit(o, bin);
873f5eb0d93SShawn O. Pearce 				assertHash(o, bin);
874f5eb0d93SShawn O. Pearce 			}
875f5eb0d93SShawn O. Pearce 
876f5eb0d93SShawn O. Pearce 			for (;;) {
877f5eb0d93SShawn O. Pearce 				final RevObject o = ow.nextObject();
878f5eb0d93SShawn O. Pearce 				if (o == null)
879f5eb0d93SShawn O. Pearce 					break;
880f5eb0d93SShawn O. Pearce 
881acb7be2cSShawn O. Pearce 				final byte[] bin = db.open(o, o.getType()).getCachedBytes();
882fa7ce0e0SShawn Pearce 				oc.check(o, o.getType(), bin);
883f5eb0d93SShawn O. Pearce 				assertHash(o, bin);
884f5eb0d93SShawn O. Pearce 			}
885f5eb0d93SShawn O. Pearce 		}
8866599111dSDave Borowitz 	}
887f5eb0d93SShawn O. Pearce 
assertHash(RevObject id, byte[] bin)888f5eb0d93SShawn O. Pearce 	private static void assertHash(RevObject id, byte[] bin) {
889f5eb0d93SShawn O. Pearce 		MessageDigest md = Constants.newMessageDigest();
890f5eb0d93SShawn O. Pearce 		md.update(Constants.encodedTypeString(id.getType()));
891f5eb0d93SShawn O. Pearce 		md.update((byte) ' ');
892f5eb0d93SShawn O. Pearce 		md.update(Constants.encodeASCII(bin.length));
893f5eb0d93SShawn O. Pearce 		md.update((byte) 0);
894f5eb0d93SShawn O. Pearce 		md.update(bin);
895d9e07a57SRobin Rosenberg 		assertEquals(id, ObjectId.fromRaw(md.digest()));
896f5eb0d93SShawn O. Pearce 	}
897f5eb0d93SShawn O. Pearce 
898f5eb0d93SShawn O. Pearce 	/**
899f5eb0d93SShawn O. Pearce 	 * Pack all reachable objects in the repository into a single pack file.
900f5eb0d93SShawn O. Pearce 	 * <p>
901f5eb0d93SShawn O. Pearce 	 * All loose objects are automatically pruned. Existing packs however are
902f5eb0d93SShawn O. Pearce 	 * not removed.
903f5eb0d93SShawn O. Pearce 	 *
904f5eb0d93SShawn O. Pearce 	 * @throws Exception
905f5eb0d93SShawn O. Pearce 	 */
packAndPrune()906f5eb0d93SShawn O. Pearce 	public void packAndPrune() throws Exception {
90789d4a737SShawn O. Pearce 		if (db.getObjectDatabase() instanceof ObjectDirectory) {
90889d4a737SShawn O. Pearce 			ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase();
9096b62e53bSShawn O. Pearce 			NullProgressMonitor m = NullProgressMonitor.INSTANCE;
910f5eb0d93SShawn O. Pearce 
911971dafd3SNasser Grainawi 			final PackFile pack, idx;
9126599111dSDave Borowitz 			try (PackWriter pw = new PackWriter(db)) {
9133b444863SDavid Pursehouse 				Set<ObjectId> all = new HashSet<>();
9141f6d43a6SDavid Pursehouse 				for (Ref r : db.getRefDatabase().getRefs())
915f5eb0d93SShawn O. Pearce 					all.add(r.getObjectId());
9164e650c0dSShawn Pearce 				pw.preparePack(m, all, PackWriter.NONE);
917f5eb0d93SShawn O. Pearce 
918*2a6b2eddSNasser Grainawi 				pack = new PackFile(odb.getPackDirectory(), pw.computeName(),
919*2a6b2eddSNasser Grainawi 						PackExt.PACK);
9206599111dSDave Borowitz 				try (OutputStream out =
9218208da2fSShawn Pearce 						new BufferedOutputStream(new FileOutputStream(pack))) {
9226b62e53bSShawn O. Pearce 					pw.writePack(m, m, out);
923f5eb0d93SShawn O. Pearce 				}
924f5eb0d93SShawn O. Pearce 				pack.setReadOnly();
925f5eb0d93SShawn O. Pearce 
926971dafd3SNasser Grainawi 				idx = pack.create(PackExt.INDEX);
9276599111dSDave Borowitz 				try (OutputStream out =
9288208da2fSShawn Pearce 						new BufferedOutputStream(new FileOutputStream(idx))) {
929f5eb0d93SShawn O. Pearce 					pw.writeIndex(out);
930f5eb0d93SShawn O. Pearce 				}
931f5eb0d93SShawn O. Pearce 				idx.setReadOnly();
932a45728d7SShawn O. Pearce 			}
933f5eb0d93SShawn O. Pearce 
93482ecfb3eSColby Ranger 			odb.openPack(pack);
935f5eb0d93SShawn O. Pearce 			updateServerInfo();
936f5eb0d93SShawn O. Pearce 			prunePacked(odb);
937f5eb0d93SShawn O. Pearce 		}
93889d4a737SShawn O. Pearce 	}
939f5eb0d93SShawn O. Pearce 
9408ed59c51SJackson Toeniskoetter 	/**
9418ed59c51SJackson Toeniskoetter 	 * Closes the underlying {@link Repository} object and any other internal
9428ed59c51SJackson Toeniskoetter 	 * resources.
9438ed59c51SJackson Toeniskoetter 	 * <p>
9448ed59c51SJackson Toeniskoetter 	 * {@link AutoCloseable} resources that may escape this object, such as
9458ed59c51SJackson Toeniskoetter 	 * those returned by the {@link #git} and {@link #getRevWalk()} methods are
9468ed59c51SJackson Toeniskoetter 	 * not closed.
9478ed59c51SJackson Toeniskoetter 	 */
9488ed59c51SJackson Toeniskoetter 	@Override
close()9498ed59c51SJackson Toeniskoetter 	public void close() {
9508ed59c51SJackson Toeniskoetter 		try {
9518ed59c51SJackson Toeniskoetter 			inserter.close();
9528ed59c51SJackson Toeniskoetter 		} finally {
9538ed59c51SJackson Toeniskoetter 			db.close();
9548ed59c51SJackson Toeniskoetter 		}
9558ed59c51SJackson Toeniskoetter 	}
9568ed59c51SJackson Toeniskoetter 
prunePacked(ObjectDirectory odb)957a57dd1c1SRobin Rosenberg 	private static void prunePacked(ObjectDirectory odb) throws IOException {
958efb154fcSNasser Grainawi 		for (Pack p : odb.getPacks()) {
959f5eb0d93SShawn O. Pearce 			for (MutableEntry e : p)
96045731756SMatthias Sohn 				FileUtils.delete(odb.fileFor(e.toObjectId()));
961f5eb0d93SShawn O. Pearce 		}
962f5eb0d93SShawn O. Pearce 	}
963f5eb0d93SShawn O. Pearce 
writeFile(File p, byte[] bin)9646d370d83SHan-Wen Nienhuys 	private void writeFile(File p, byte[] bin) throws IOException,
96589d4a737SShawn O. Pearce 			ObjectWritingException {
966e96cb22aSDavid Pursehouse 		final LockFile lck = new LockFile(p);
967f5eb0d93SShawn O. Pearce 		if (!lck.lock())
968f5eb0d93SShawn O. Pearce 			throw new ObjectWritingException("Can't write " + p);
969f5eb0d93SShawn O. Pearce 		try {
970f5eb0d93SShawn O. Pearce 			lck.write(bin);
971f5eb0d93SShawn O. Pearce 		} catch (IOException ioe) {
9724cc13297SDavid Pursehouse 			throw new ObjectWritingException("Can't write " + p, ioe);
973f5eb0d93SShawn O. Pearce 		}
974f5eb0d93SShawn O. Pearce 		if (!lck.commit())
975f5eb0d93SShawn O. Pearce 			throw new ObjectWritingException("Can't write " + p);
976f5eb0d93SShawn O. Pearce 	}
977f5eb0d93SShawn O. Pearce 
978f945c424SShawn O. Pearce 	/** Helper to build a branch with one or more commits */
979f945c424SShawn O. Pearce 	public class BranchBuilder {
980f945c424SShawn O. Pearce 		private final String ref;
981f945c424SShawn O. Pearce 
BranchBuilder(String ref)982f3ec7cf3SHan-Wen Nienhuys 		BranchBuilder(String ref) {
983f945c424SShawn O. Pearce 			this.ref = ref;
984f945c424SShawn O. Pearce 		}
985f945c424SShawn O. Pearce 
986f945c424SShawn O. Pearce 		/**
987f945c424SShawn O. Pearce 		 * @return construct a new commit builder that updates this branch. If
988f945c424SShawn O. Pearce 		 *         the branch already exists, the commit builder will have its
989f945c424SShawn O. Pearce 		 *         first parent as the current commit and its tree will be
990f945c424SShawn O. Pearce 		 *         initialized to the current files.
991f945c424SShawn O. Pearce 		 * @throws Exception
992f945c424SShawn O. Pearce 		 *             the commit builder can't read the current branch state
993f945c424SShawn O. Pearce 		 */
commit()994f945c424SShawn O. Pearce 		public CommitBuilder commit() throws Exception {
995f945c424SShawn O. Pearce 			return new CommitBuilder(this);
996f945c424SShawn O. Pearce 		}
997f945c424SShawn O. Pearce 
998f945c424SShawn O. Pearce 		/**
999f945c424SShawn O. Pearce 		 * Forcefully update this branch to a particular commit.
1000f945c424SShawn O. Pearce 		 *
1001f945c424SShawn O. Pearce 		 * @param to
1002f945c424SShawn O. Pearce 		 *            the commit to update to.
1003f945c424SShawn O. Pearce 		 * @return {@code to}.
1004f945c424SShawn O. Pearce 		 * @throws Exception
1005f945c424SShawn O. Pearce 		 */
update(CommitBuilder to)1006f945c424SShawn O. Pearce 		public RevCommit update(CommitBuilder to) throws Exception {
1007f945c424SShawn O. Pearce 			return update(to.create());
1008f945c424SShawn O. Pearce 		}
1009f945c424SShawn O. Pearce 
1010f945c424SShawn O. Pearce 		/**
1011f945c424SShawn O. Pearce 		 * Forcefully update this branch to a particular commit.
1012f945c424SShawn O. Pearce 		 *
1013f945c424SShawn O. Pearce 		 * @param to
1014f945c424SShawn O. Pearce 		 *            the commit to update to.
1015f945c424SShawn O. Pearce 		 * @return {@code to}.
1016f945c424SShawn O. Pearce 		 * @throws Exception
1017f945c424SShawn O. Pearce 		 */
update(RevCommit to)1018f945c424SShawn O. Pearce 		public RevCommit update(RevCommit to) throws Exception {
1019f945c424SShawn O. Pearce 			return TestRepository.this.update(ref, to);
1020f945c424SShawn O. Pearce 		}
1021bc9df9c6SJonathan Nieder 
1022bc9df9c6SJonathan Nieder 		/**
1023bc9df9c6SJonathan Nieder 		 * Delete this branch.
1024bc9df9c6SJonathan Nieder 		 * @throws Exception
1025bc9df9c6SJonathan Nieder 		 * @since 4.4
1026bc9df9c6SJonathan Nieder 		 */
delete()1027bc9df9c6SJonathan Nieder 		public void delete() throws Exception {
1028bc9df9c6SJonathan Nieder 			TestRepository.this.delete(ref);
1029bc9df9c6SJonathan Nieder 		}
1030f945c424SShawn O. Pearce 	}
1031f945c424SShawn O. Pearce 
1032f945c424SShawn O. Pearce 	/** Helper to generate a commit. */
1033f945c424SShawn O. Pearce 	public class CommitBuilder {
1034f945c424SShawn O. Pearce 		private final BranchBuilder branch;
1035f945c424SShawn O. Pearce 
1036f945c424SShawn O. Pearce 		private final DirCache tree = DirCache.newInCore();
1037f945c424SShawn O. Pearce 
1038a5514932SSasa Zivkov 		private ObjectId topLevelTree;
1039a5514932SSasa Zivkov 
10403b444863SDavid Pursehouse 		private final List<RevCommit> parents = new ArrayList<>(2);
1041f945c424SShawn O. Pearce 
1042f945c424SShawn O. Pearce 		private int tick = 1;
1043f945c424SShawn O. Pearce 
1044f945c424SShawn O. Pearce 		private String message = "";
1045f945c424SShawn O. Pearce 
1046f945c424SShawn O. Pearce 		private RevCommit self;
1047f945c424SShawn O. Pearce 
10483d5e70bcSDave Borowitz 		private PersonIdent author;
10493d5e70bcSDave Borowitz 		private PersonIdent committer;
10503d5e70bcSDave Borowitz 
10518b6f9aceSDave Borowitz 		private String changeId;
1052c1d40caaSDave Borowitz 
1053da85ca73SDave Borowitz 		private boolean updateCommitterTime;
1054da85ca73SDave Borowitz 
CommitBuilder()1055f945c424SShawn O. Pearce 		CommitBuilder() {
1056f945c424SShawn O. Pearce 			branch = null;
1057f945c424SShawn O. Pearce 		}
1058f945c424SShawn O. Pearce 
CommitBuilder(BranchBuilder b)1059f945c424SShawn O. Pearce 		CommitBuilder(BranchBuilder b) throws Exception {
1060f945c424SShawn O. Pearce 			branch = b;
1061f945c424SShawn O. Pearce 
10624c236ff4SMatthias Sohn 			Ref ref = db.exactRef(branch.ref);
10630b625445SDave Borowitz 			if (ref != null && ref.getObjectId() != null)
1064f945c424SShawn O. Pearce 				parent(pool.parseCommit(ref.getObjectId()));
1065f945c424SShawn O. Pearce 		}
1066f945c424SShawn O. Pearce 
CommitBuilder(CommitBuilder prior)1067f945c424SShawn O. Pearce 		CommitBuilder(CommitBuilder prior) throws Exception {
1068f945c424SShawn O. Pearce 			branch = prior.branch;
1069f945c424SShawn O. Pearce 
1070f945c424SShawn O. Pearce 			DirCacheBuilder b = tree.builder();
1071f945c424SShawn O. Pearce 			for (int i = 0; i < prior.tree.getEntryCount(); i++)
1072f945c424SShawn O. Pearce 				b.add(prior.tree.getEntry(i));
1073f945c424SShawn O. Pearce 			b.finish();
1074f945c424SShawn O. Pearce 
1075f945c424SShawn O. Pearce 			parents.add(prior.create());
1076f945c424SShawn O. Pearce 		}
1077f945c424SShawn O. Pearce 
1078376c20f4SMatthias Sohn 		/**
1079376c20f4SMatthias Sohn 		 * set parent commit
1080376c20f4SMatthias Sohn 		 *
1081376c20f4SMatthias Sohn 		 * @param p
1082376c20f4SMatthias Sohn 		 *            parent commit
1083376c20f4SMatthias Sohn 		 * @return this commit builder
1084376c20f4SMatthias Sohn 		 * @throws Exception
1085376c20f4SMatthias Sohn 		 */
parent(RevCommit p)1086f945c424SShawn O. Pearce 		public CommitBuilder parent(RevCommit p) throws Exception {
1087f945c424SShawn O. Pearce 			if (parents.isEmpty()) {
1088f945c424SShawn O. Pearce 				DirCacheBuilder b = tree.builder();
1089f945c424SShawn O. Pearce 				parseBody(p);
109094228bdeSShawn O. Pearce 				b.addTree(new byte[0], DirCacheEntry.STAGE_0, pool
109194228bdeSShawn O. Pearce 						.getObjectReader(), p.getTree());
1092f945c424SShawn O. Pearce 				b.finish();
1093f945c424SShawn O. Pearce 			}
1094f945c424SShawn O. Pearce 			parents.add(p);
1095f945c424SShawn O. Pearce 			return this;
1096f945c424SShawn O. Pearce 		}
1097f945c424SShawn O. Pearce 
1098376c20f4SMatthias Sohn 		/**
1099376c20f4SMatthias Sohn 		 * Get parent commits
1100376c20f4SMatthias Sohn 		 *
1101376c20f4SMatthias Sohn 		 * @return parent commits
1102376c20f4SMatthias Sohn 		 */
parents()110382872182SDave Borowitz 		public List<RevCommit> parents() {
110482872182SDave Borowitz 			return Collections.unmodifiableList(parents);
110582872182SDave Borowitz 		}
110682872182SDave Borowitz 
1107376c20f4SMatthias Sohn 		/**
1108376c20f4SMatthias Sohn 		 * Remove parent commits
1109376c20f4SMatthias Sohn 		 *
1110376c20f4SMatthias Sohn 		 * @return this commit builder
1111376c20f4SMatthias Sohn 		 */
noParents()1112f945c424SShawn O. Pearce 		public CommitBuilder noParents() {
1113f945c424SShawn O. Pearce 			parents.clear();
1114f945c424SShawn O. Pearce 			return this;
1115f945c424SShawn O. Pearce 		}
1116f945c424SShawn O. Pearce 
1117376c20f4SMatthias Sohn 		/**
1118376c20f4SMatthias Sohn 		 * Remove files
1119376c20f4SMatthias Sohn 		 *
1120376c20f4SMatthias Sohn 		 * @return this commit builder
1121376c20f4SMatthias Sohn 		 */
noFiles()1122f945c424SShawn O. Pearce 		public CommitBuilder noFiles() {
1123f945c424SShawn O. Pearce 			tree.clear();
1124f945c424SShawn O. Pearce 			return this;
1125f945c424SShawn O. Pearce 		}
1126f945c424SShawn O. Pearce 
1127376c20f4SMatthias Sohn 		/**
1128376c20f4SMatthias Sohn 		 * Set top level tree
1129376c20f4SMatthias Sohn 		 *
1130376c20f4SMatthias Sohn 		 * @param treeId
1131376c20f4SMatthias Sohn 		 *            the top level tree
1132376c20f4SMatthias Sohn 		 * @return this commit builder
1133376c20f4SMatthias Sohn 		 */
setTopLevelTree(ObjectId treeId)1134a5514932SSasa Zivkov 		public CommitBuilder setTopLevelTree(ObjectId treeId) {
1135a5514932SSasa Zivkov 			topLevelTree = treeId;
1136a5514932SSasa Zivkov 			return this;
1137a5514932SSasa Zivkov 		}
1138a5514932SSasa Zivkov 
1139376c20f4SMatthias Sohn 		/**
1140376c20f4SMatthias Sohn 		 * Add file with given content
1141376c20f4SMatthias Sohn 		 *
1142376c20f4SMatthias Sohn 		 * @param path
1143376c20f4SMatthias Sohn 		 *            path of the file
1144376c20f4SMatthias Sohn 		 * @param content
1145376c20f4SMatthias Sohn 		 *            the file content
1146376c20f4SMatthias Sohn 		 * @return this commit builder
1147376c20f4SMatthias Sohn 		 * @throws Exception
1148376c20f4SMatthias Sohn 		 */
add(String path, String content)1149f945c424SShawn O. Pearce 		public CommitBuilder add(String path, String content) throws Exception {
1150f945c424SShawn O. Pearce 			return add(path, blob(content));
1151f945c424SShawn O. Pearce 		}
1152f945c424SShawn O. Pearce 
1153376c20f4SMatthias Sohn 		/**
1154376c20f4SMatthias Sohn 		 * Add file with given path and blob
1155376c20f4SMatthias Sohn 		 *
1156376c20f4SMatthias Sohn 		 * @param path
1157376c20f4SMatthias Sohn 		 *            path of the file
1158376c20f4SMatthias Sohn 		 * @param id
1159376c20f4SMatthias Sohn 		 *            blob for this file
1160376c20f4SMatthias Sohn 		 * @return this commit builder
1161376c20f4SMatthias Sohn 		 * @throws Exception
1162376c20f4SMatthias Sohn 		 */
add(String path, RevBlob id)11636d370d83SHan-Wen Nienhuys 		public CommitBuilder add(String path, RevBlob id)
1164f945c424SShawn O. Pearce 				throws Exception {
11659346f1ccSDave Borowitz 			return edit(new PathEdit(path) {
1166f945c424SShawn O. Pearce 				@Override
1167f945c424SShawn O. Pearce 				public void apply(DirCacheEntry ent) {
1168f945c424SShawn O. Pearce 					ent.setFileMode(FileMode.REGULAR_FILE);
1169f945c424SShawn O. Pearce 					ent.setObjectId(id);
1170f945c424SShawn O. Pearce 				}
1171f945c424SShawn O. Pearce 			});
11729346f1ccSDave Borowitz 		}
11739346f1ccSDave Borowitz 
1174376c20f4SMatthias Sohn 		/**
1175376c20f4SMatthias Sohn 		 * Edit the index
1176376c20f4SMatthias Sohn 		 *
1177376c20f4SMatthias Sohn 		 * @param edit
1178376c20f4SMatthias Sohn 		 *            the index record update
1179376c20f4SMatthias Sohn 		 * @return this commit builder
1180376c20f4SMatthias Sohn 		 */
edit(PathEdit edit)11819346f1ccSDave Borowitz 		public CommitBuilder edit(PathEdit edit) {
11829346f1ccSDave Borowitz 			DirCacheEditor e = tree.editor();
11839346f1ccSDave Borowitz 			e.add(edit);
1184f945c424SShawn O. Pearce 			e.finish();
1185f945c424SShawn O. Pearce 			return this;
1186f945c424SShawn O. Pearce 		}
1187f945c424SShawn O. Pearce 
1188376c20f4SMatthias Sohn 		/**
1189376c20f4SMatthias Sohn 		 * Remove a file
1190376c20f4SMatthias Sohn 		 *
1191376c20f4SMatthias Sohn 		 * @param path
1192376c20f4SMatthias Sohn 		 *            path of the file
1193376c20f4SMatthias Sohn 		 * @return this commit builder
1194376c20f4SMatthias Sohn 		 */
rm(String path)1195f945c424SShawn O. Pearce 		public CommitBuilder rm(String path) {
1196f945c424SShawn O. Pearce 			DirCacheEditor e = tree.editor();
1197f945c424SShawn O. Pearce 			e.add(new DeletePath(path));
1198f945c424SShawn O. Pearce 			e.add(new DeleteTree(path));
1199f945c424SShawn O. Pearce 			e.finish();
1200f945c424SShawn O. Pearce 			return this;
1201f945c424SShawn O. Pearce 		}
1202f945c424SShawn O. Pearce 
1203376c20f4SMatthias Sohn 		/**
1204376c20f4SMatthias Sohn 		 * Set commit message
1205376c20f4SMatthias Sohn 		 *
1206376c20f4SMatthias Sohn 		 * @param m
1207376c20f4SMatthias Sohn 		 *            the message
1208376c20f4SMatthias Sohn 		 * @return this commit builder
1209376c20f4SMatthias Sohn 		 */
message(String m)1210f945c424SShawn O. Pearce 		public CommitBuilder message(String m) {
1211f945c424SShawn O. Pearce 			message = m;
1212f945c424SShawn O. Pearce 			return this;
1213f945c424SShawn O. Pearce 		}
1214f945c424SShawn O. Pearce 
1215376c20f4SMatthias Sohn 		/**
1216376c20f4SMatthias Sohn 		 * Get the commit message
1217376c20f4SMatthias Sohn 		 *
1218376c20f4SMatthias Sohn 		 * @return the commit message
1219376c20f4SMatthias Sohn 		 */
message()122082872182SDave Borowitz 		public String message() {
122182872182SDave Borowitz 			return message;
122282872182SDave Borowitz 		}
122382872182SDave Borowitz 
1224376c20f4SMatthias Sohn 		/**
1225376c20f4SMatthias Sohn 		 * Tick the clock
1226376c20f4SMatthias Sohn 		 *
1227376c20f4SMatthias Sohn 		 * @param secs
1228376c20f4SMatthias Sohn 		 *            number of seconds
1229376c20f4SMatthias Sohn 		 * @return this commit builder
1230376c20f4SMatthias Sohn 		 */
tick(int secs)1231f945c424SShawn O. Pearce 		public CommitBuilder tick(int secs) {
1232f945c424SShawn O. Pearce 			tick = secs;
1233f945c424SShawn O. Pearce 			return this;
1234f945c424SShawn O. Pearce 		}
1235f945c424SShawn O. Pearce 
1236376c20f4SMatthias Sohn 		/**
1237376c20f4SMatthias Sohn 		 * Set author and committer identity
1238376c20f4SMatthias Sohn 		 *
1239376c20f4SMatthias Sohn 		 * @param ident
1240376c20f4SMatthias Sohn 		 *            identity to set
1241376c20f4SMatthias Sohn 		 * @return this commit builder
1242376c20f4SMatthias Sohn 		 */
ident(PersonIdent ident)12433d5e70bcSDave Borowitz 		public CommitBuilder ident(PersonIdent ident) {
12443d5e70bcSDave Borowitz 			author = ident;
12453d5e70bcSDave Borowitz 			committer = ident;
12463d5e70bcSDave Borowitz 			return this;
12473d5e70bcSDave Borowitz 		}
12483d5e70bcSDave Borowitz 
1249376c20f4SMatthias Sohn 		/**
1250376c20f4SMatthias Sohn 		 * Set the author identity
1251376c20f4SMatthias Sohn 		 *
1252376c20f4SMatthias Sohn 		 * @param a
1253376c20f4SMatthias Sohn 		 *            the author's identity
1254376c20f4SMatthias Sohn 		 * @return this commit builder
1255376c20f4SMatthias Sohn 		 */
author(PersonIdent a)12563d5e70bcSDave Borowitz 		public CommitBuilder author(PersonIdent a) {
12573d5e70bcSDave Borowitz 			author = a;
12583d5e70bcSDave Borowitz 			return this;
12593d5e70bcSDave Borowitz 		}
12603d5e70bcSDave Borowitz 
1261376c20f4SMatthias Sohn 		/**
1262376c20f4SMatthias Sohn 		 * Get the author identity
1263376c20f4SMatthias Sohn 		 *
1264376c20f4SMatthias Sohn 		 * @return the author identity
1265376c20f4SMatthias Sohn 		 */
author()126682872182SDave Borowitz 		public PersonIdent author() {
126782872182SDave Borowitz 			return author;
126882872182SDave Borowitz 		}
126982872182SDave Borowitz 
1270376c20f4SMatthias Sohn 		/**
1271376c20f4SMatthias Sohn 		 * Set the committer identity
1272376c20f4SMatthias Sohn 		 *
1273376c20f4SMatthias Sohn 		 * @param c
1274376c20f4SMatthias Sohn 		 *            the committer identity
1275376c20f4SMatthias Sohn 		 * @return this commit builder
1276376c20f4SMatthias Sohn 		 */
committer(PersonIdent c)12773d5e70bcSDave Borowitz 		public CommitBuilder committer(PersonIdent c) {
12783d5e70bcSDave Borowitz 			committer = c;
12793d5e70bcSDave Borowitz 			return this;
12803d5e70bcSDave Borowitz 		}
12813d5e70bcSDave Borowitz 
1282376c20f4SMatthias Sohn 		/**
1283376c20f4SMatthias Sohn 		 * Get the committer identity
1284376c20f4SMatthias Sohn 		 *
1285376c20f4SMatthias Sohn 		 * @return the committer identity
1286376c20f4SMatthias Sohn 		 */
committer()128782872182SDave Borowitz 		public PersonIdent committer() {
128882872182SDave Borowitz 			return committer;
128982872182SDave Borowitz 		}
129082872182SDave Borowitz 
1291376c20f4SMatthias Sohn 		/**
1292376c20f4SMatthias Sohn 		 * Insert changeId
1293376c20f4SMatthias Sohn 		 *
1294376c20f4SMatthias Sohn 		 * @return this commit builder
1295376c20f4SMatthias Sohn 		 */
insertChangeId()1296c1d40caaSDave Borowitz 		public CommitBuilder insertChangeId() {
12978b6f9aceSDave Borowitz 			changeId = "";
12988b6f9aceSDave Borowitz 			return this;
12998b6f9aceSDave Borowitz 		}
13008b6f9aceSDave Borowitz 
1301376c20f4SMatthias Sohn 		/**
1302376c20f4SMatthias Sohn 		 * Insert given changeId
1303376c20f4SMatthias Sohn 		 *
1304376c20f4SMatthias Sohn 		 * @param c
1305376c20f4SMatthias Sohn 		 *            changeId
1306376c20f4SMatthias Sohn 		 * @return this commit builder
1307376c20f4SMatthias Sohn 		 */
insertChangeId(String c)13088b6f9aceSDave Borowitz 		public CommitBuilder insertChangeId(String c) {
13098b6f9aceSDave Borowitz 			// Validate, but store as a string so we can use "" as a sentinel.
13108b6f9aceSDave Borowitz 			ObjectId.fromString(c);
13118b6f9aceSDave Borowitz 			changeId = c;
1312c1d40caaSDave Borowitz 			return this;
1313c1d40caaSDave Borowitz 		}
1314c1d40caaSDave Borowitz 
1315376c20f4SMatthias Sohn 		/**
1316376c20f4SMatthias Sohn 		 * Create the commit
1317376c20f4SMatthias Sohn 		 *
1318376c20f4SMatthias Sohn 		 * @return the new commit
1319376c20f4SMatthias Sohn 		 * @throws Exception
1320376c20f4SMatthias Sohn 		 *             if creation failed
1321376c20f4SMatthias Sohn 		 */
create()1322f945c424SShawn O. Pearce 		public RevCommit create() throws Exception {
1323f945c424SShawn O. Pearce 			if (self == null) {
1324f945c424SShawn O. Pearce 				TestRepository.this.tick(tick);
1325f945c424SShawn O. Pearce 
132622b28569SShawn O. Pearce 				final org.eclipse.jgit.lib.CommitBuilder c;
132722b28569SShawn O. Pearce 
132822b28569SShawn O. Pearce 				c = new org.eclipse.jgit.lib.CommitBuilder();
1329b46b635cSShawn O. Pearce 				c.setParentIds(parents);
13303e2b9b69SShawn O. Pearce 				setAuthorAndCommitter(c);
13313d5e70bcSDave Borowitz 				if (author != null)
13323d5e70bcSDave Borowitz 					c.setAuthor(author);
1333da85ca73SDave Borowitz 				if (committer != null) {
1334da85ca73SDave Borowitz 					if (updateCommitterTime)
1335069040ddSTerry Parker 						committer = new PersonIdent(committer, getDate());
13363d5e70bcSDave Borowitz 					c.setCommitter(committer);
1337da85ca73SDave Borowitz 				}
1338f945c424SShawn O. Pearce 
133988530a17SShawn O. Pearce 				ObjectId commitId;
13406599111dSDave Borowitz 				try (ObjectInserter ins = inserter) {
1341a5514932SSasa Zivkov 					if (topLevelTree != null)
1342a5514932SSasa Zivkov 						c.setTreeId(topLevelTree);
1343a5514932SSasa Zivkov 					else
13446599111dSDave Borowitz 						c.setTreeId(tree.writeTree(ins));
1345c1d40caaSDave Borowitz 					insertChangeId(c);
1346c1d40caaSDave Borowitz 					c.setMessage(message);
13476599111dSDave Borowitz 					commitId = ins.insert(c);
13486599111dSDave Borowitz 					ins.flush();
134988530a17SShawn O. Pearce 				}
135000e51e35STerry Parker 				self = pool.parseCommit(commitId);
1351f945c424SShawn O. Pearce 
1352f945c424SShawn O. Pearce 				if (branch != null)
1353f945c424SShawn O. Pearce 					branch.update(self);
1354f945c424SShawn O. Pearce 			}
1355f945c424SShawn O. Pearce 			return self;
1356f945c424SShawn O. Pearce 		}
1357f945c424SShawn O. Pearce 
insertChangeId(org.eclipse.jgit.lib.CommitBuilder c)13583af05f60SMatthias Sohn 		private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c) {
13598b6f9aceSDave Borowitz 			if (changeId == null)
13608b6f9aceSDave Borowitz 				return;
13616ed07f5cSDave Borowitz 			int idx = ChangeIdUtil.indexOfChangeId(message, "\n");
13626ed07f5cSDave Borowitz 			if (idx >= 0)
13636ed07f5cSDave Borowitz 				return;
13646ed07f5cSDave Borowitz 
1365c1d40caaSDave Borowitz 			ObjectId firstParentId = null;
1366c1d40caaSDave Borowitz 			if (!parents.isEmpty())
1367c1d40caaSDave Borowitz 				firstParentId = parents.get(0);
13688b6f9aceSDave Borowitz 
13698b6f9aceSDave Borowitz 			ObjectId cid;
13700db509f7SCarsten Hammer 			if (changeId.isEmpty())
13718b6f9aceSDave Borowitz 				cid = ChangeIdUtil.computeChangeId(c.getTreeId(), firstParentId,
13728b6f9aceSDave Borowitz 						c.getAuthor(), c.getCommitter(), message);
13738b6f9aceSDave Borowitz 			else
13748b6f9aceSDave Borowitz 				cid = ObjectId.fromString(changeId);
13758b6f9aceSDave Borowitz 			message = ChangeIdUtil.insertId(message, cid);
13768b6f9aceSDave Borowitz 			if (cid != null)
1377c1d40caaSDave Borowitz 				message = message.replaceAll("\nChange-Id: I" //$NON-NLS-1$
1378c1d40caaSDave Borowitz 						+ ObjectId.zeroId().getName() + "\n", "\nChange-Id: I" //$NON-NLS-1$ //$NON-NLS-2$
13798b6f9aceSDave Borowitz 						+ cid.getName() + "\n"); //$NON-NLS-1$
1380c1d40caaSDave Borowitz 		}
1381c1d40caaSDave Borowitz 
1382376c20f4SMatthias Sohn 		/**
1383376c20f4SMatthias Sohn 		 * Create child commit builder
1384376c20f4SMatthias Sohn 		 *
1385376c20f4SMatthias Sohn 		 * @return child commit builder
1386376c20f4SMatthias Sohn 		 * @throws Exception
1387376c20f4SMatthias Sohn 		 */
child()1388f945c424SShawn O. Pearce 		public CommitBuilder child() throws Exception {
1389f945c424SShawn O. Pearce 			return new CommitBuilder(this);
1390f945c424SShawn O. Pearce 		}
1391f945c424SShawn O. Pearce 	}
1392f945c424SShawn O. Pearce }
1393