| /* |
| * Copyright (C) 2009-2010, Google Inc. |
| * and other copyright owners as documented in the project's IP log. |
| * |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Distribution License v1.0 which |
| * accompanies this distribution, is reproduced below, and is |
| * available at http://www.eclipse.org/org/documents/edl-v10.php |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * |
| * - Neither the name of the Eclipse Foundation, Inc. nor the |
| * names of its contributors may be used to endorse or promote |
| * products derived from this software without specific prior |
| * written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.eclipse.jgit.junit; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.fail; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.security.MessageDigest; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.jgit.dircache.DirCache; |
| import org.eclipse.jgit.dircache.DirCacheBuilder; |
| import org.eclipse.jgit.dircache.DirCacheEditor; |
| import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath; |
| import org.eclipse.jgit.dircache.DirCacheEditor.DeleteTree; |
| import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; |
| import org.eclipse.jgit.dircache.DirCacheEntry; |
| import org.eclipse.jgit.errors.IncorrectObjectTypeException; |
| import org.eclipse.jgit.errors.MissingObjectException; |
| import org.eclipse.jgit.errors.ObjectWritingException; |
| import org.eclipse.jgit.internal.storage.file.FileRepository; |
| import org.eclipse.jgit.internal.storage.file.LockFile; |
| import org.eclipse.jgit.internal.storage.file.ObjectDirectory; |
| import org.eclipse.jgit.internal.storage.file.PackFile; |
| import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; |
| import org.eclipse.jgit.internal.storage.pack.PackWriter; |
| import org.eclipse.jgit.lib.AnyObjectId; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.FileMode; |
| import org.eclipse.jgit.lib.NullProgressMonitor; |
| import org.eclipse.jgit.lib.ObjectChecker; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.ObjectInserter; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.RefUpdate; |
| import org.eclipse.jgit.lib.RefWriter; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.lib.TagBuilder; |
| import org.eclipse.jgit.revwalk.ObjectWalk; |
| import org.eclipse.jgit.revwalk.RevBlob; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.revwalk.RevObject; |
| import org.eclipse.jgit.revwalk.RevTag; |
| import org.eclipse.jgit.revwalk.RevTree; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.eclipse.jgit.treewalk.TreeWalk; |
| import org.eclipse.jgit.treewalk.filter.PathFilterGroup; |
| import org.eclipse.jgit.util.FileUtils; |
| import org.eclipse.jgit.util.io.SafeBufferedOutputStream; |
| |
| /** |
| * Wrapper to make creating test data easier. |
| * |
| * @param <R> |
| * type of Repository the test data is stored on. |
| */ |
| public class TestRepository<R extends Repository> { |
| private static final PersonIdent author; |
| |
| private static final PersonIdent committer; |
| |
| static { |
| final MockSystemReader m = new MockSystemReader(); |
| final long now = m.getCurrentTime(); |
| final int tz = m.getTimezone(now); |
| |
| final String an = "J. Author"; |
| final String ae = "jauthor@example.com"; |
| author = new PersonIdent(an, ae, now, tz); |
| |
| final String cn = "J. Committer"; |
| final String ce = "jcommitter@example.com"; |
| committer = new PersonIdent(cn, ce, now, tz); |
| } |
| |
| private final R db; |
| |
| private final RevWalk pool; |
| |
| private final ObjectInserter inserter; |
| |
| private long now; |
| |
| /** |
| * Wrap a repository with test building tools. |
| * |
| * @param db |
| * the test repository to write into. |
| * @throws IOException |
| */ |
| public TestRepository(R db) throws IOException { |
| this(db, new RevWalk(db)); |
| } |
| |
| /** |
| * Wrap a repository with test building tools. |
| * |
| * @param db |
| * the test repository to write into. |
| * @param rw |
| * the RevObject pool to use for object lookup. |
| * @throws IOException |
| */ |
| public TestRepository(R db, RevWalk rw) throws IOException { |
| this.db = db; |
| this.pool = rw; |
| this.inserter = db.newObjectInserter(); |
| this.now = 1236977987000L; |
| } |
| |
| /** @return the repository this helper class operates against. */ |
| public R getRepository() { |
| return db; |
| } |
| |
| /** @return get the RevWalk pool all objects are allocated through. */ |
| public RevWalk getRevWalk() { |
| return pool; |
| } |
| |
| /** @return current time adjusted by {@link #tick(int)}. */ |
| public Date getClock() { |
| return new Date(now); |
| } |
| |
| /** |
| * Adjust the current time that will used by the next commit. |
| * |
| * @param secDelta |
| * number of seconds to add to the current time. |
| */ |
| public void tick(final int secDelta) { |
| now += secDelta * 1000L; |
| } |
| |
| /** |
| * Set the author and committer using {@link #getClock()}. |
| * |
| * @param c |
| * the commit builder to store. |
| */ |
| public void setAuthorAndCommitter(org.eclipse.jgit.lib.CommitBuilder c) { |
| c.setAuthor(new PersonIdent(author, new Date(now))); |
| c.setCommitter(new PersonIdent(committer, new Date(now))); |
| } |
| |
| /** |
| * Create a new blob object in the repository. |
| * |
| * @param content |
| * file content, will be UTF-8 encoded. |
| * @return reference to the blob. |
| * @throws Exception |
| */ |
| public RevBlob blob(final String content) throws Exception { |
| return blob(content.getBytes("UTF-8")); |
| } |
| |
| /** |
| * Create a new blob object in the repository. |
| * |
| * @param content |
| * binary file content. |
| * @return reference to the blob. |
| * @throws Exception |
| */ |
| public RevBlob blob(final byte[] content) throws Exception { |
| ObjectId id; |
| try { |
| id = inserter.insert(Constants.OBJ_BLOB, content); |
| inserter.flush(); |
| } finally { |
| inserter.release(); |
| } |
| return pool.lookupBlob(id); |
| } |
| |
| /** |
| * Construct a regular file mode tree entry. |
| * |
| * @param path |
| * path of the file. |
| * @param blob |
| * a blob, previously constructed in the repository. |
| * @return the entry. |
| * @throws Exception |
| */ |
| public DirCacheEntry file(final String path, final RevBlob blob) |
| throws Exception { |
| final DirCacheEntry e = new DirCacheEntry(path); |
| e.setFileMode(FileMode.REGULAR_FILE); |
| e.setObjectId(blob); |
| return e; |
| } |
| |
| /** |
| * Construct a tree from a specific listing of file entries. |
| * |
| * @param entries |
| * the files to include in the tree. The collection does not need |
| * to be sorted properly and may be empty. |
| * @return reference to the tree specified by the entry list. |
| * @throws Exception |
| */ |
| public RevTree tree(final DirCacheEntry... entries) throws Exception { |
| final DirCache dc = DirCache.newInCore(); |
| final DirCacheBuilder b = dc.builder(); |
| for (final DirCacheEntry e : entries) |
| b.add(e); |
| b.finish(); |
| ObjectId root; |
| try { |
| root = dc.writeTree(inserter); |
| inserter.flush(); |
| } finally { |
| inserter.release(); |
| } |
| return pool.lookupTree(root); |
| } |
| |
| /** |
| * Lookup an entry stored in a tree, failing if not present. |
| * |
| * @param tree |
| * the tree to search. |
| * @param path |
| * the path to find the entry of. |
| * @return the parsed object entry at this path, never null. |
| * @throws Exception |
| */ |
| public RevObject get(final RevTree tree, final String path) |
| throws Exception { |
| final TreeWalk tw = new TreeWalk(pool.getObjectReader()); |
| tw.setFilter(PathFilterGroup.createFromStrings(Collections |
| .singleton(path))); |
| tw.reset(tree); |
| while (tw.next()) { |
| if (tw.isSubtree() && !path.equals(tw.getPathString())) { |
| tw.enterSubtree(); |
| continue; |
| } |
| final ObjectId entid = tw.getObjectId(0); |
| final FileMode entmode = tw.getFileMode(0); |
| return pool.lookupAny(entid, entmode.getObjectType()); |
| } |
| fail("Can't find " + path + " in tree " + tree.name()); |
| return null; // never reached. |
| } |
| |
| /** |
| * Create a new commit. |
| * <p> |
| * See {@link #commit(int, RevTree, RevCommit...)}. The tree is the empty |
| * tree (no files or subdirectories). |
| * |
| * @param parents |
| * zero or more parents of the commit. |
| * @return the new commit. |
| * @throws Exception |
| */ |
| public RevCommit commit(final RevCommit... parents) throws Exception { |
| return commit(1, tree(), parents); |
| } |
| |
| /** |
| * Create a new commit. |
| * <p> |
| * See {@link #commit(int, RevTree, RevCommit...)}. |
| * |
| * @param tree |
| * the root tree for the commit. |
| * @param parents |
| * zero or more parents of the commit. |
| * @return the new commit. |
| * @throws Exception |
| */ |
| public RevCommit commit(final RevTree tree, final RevCommit... parents) |
| throws Exception { |
| return commit(1, tree, parents); |
| } |
| |
| /** |
| * Create a new commit. |
| * <p> |
| * See {@link #commit(int, RevTree, RevCommit...)}. The tree is the empty |
| * tree (no files or subdirectories). |
| * |
| * @param secDelta |
| * number of seconds to advance {@link #tick(int)} by. |
| * @param parents |
| * zero or more parents of the commit. |
| * @return the new commit. |
| * @throws Exception |
| */ |
| public RevCommit commit(final int secDelta, final RevCommit... parents) |
| throws Exception { |
| return commit(secDelta, tree(), parents); |
| } |
| |
| /** |
| * Create a new commit. |
| * <p> |
| * The author and committer identities are stored using the current |
| * timestamp, after being incremented by {@code secDelta}. The message body |
| * is empty. |
| * |
| * @param secDelta |
| * number of seconds to advance {@link #tick(int)} by. |
| * @param tree |
| * the root tree for the commit. |
| * @param parents |
| * zero or more parents of the commit. |
| * @return the new commit. |
| * @throws Exception |
| */ |
| public RevCommit commit(final int secDelta, final RevTree tree, |
| final RevCommit... parents) throws Exception { |
| tick(secDelta); |
| |
| final org.eclipse.jgit.lib.CommitBuilder c; |
| |
| c = new org.eclipse.jgit.lib.CommitBuilder(); |
| c.setTreeId(tree); |
| c.setParentIds(parents); |
| c.setAuthor(new PersonIdent(author, new Date(now))); |
| c.setCommitter(new PersonIdent(committer, new Date(now))); |
| c.setMessage(""); |
| ObjectId id; |
| try { |
| id = inserter.insert(c); |
| inserter.flush(); |
| } finally { |
| inserter.release(); |
| } |
| return pool.lookupCommit(id); |
| } |
| |
| /** @return a new commit builder. */ |
| public CommitBuilder commit() { |
| return new CommitBuilder(); |
| } |
| |
| /** |
| * Construct an annotated tag object pointing at another object. |
| * <p> |
| * The tagger is the committer identity, at the current time as specified by |
| * {@link #tick(int)}. The time is not increased. |
| * <p> |
| * The tag message is empty. |
| * |
| * @param name |
| * name of the tag. Traditionally a tag name should not start |
| * with {@code refs/tags/}. |
| * @param dst |
| * object the tag should be pointed at. |
| * @return the annotated tag object. |
| * @throws Exception |
| */ |
| public RevTag tag(final String name, final RevObject dst) throws Exception { |
| final TagBuilder t = new TagBuilder(); |
| t.setObjectId(dst); |
| t.setTag(name); |
| t.setTagger(new PersonIdent(committer, new Date(now))); |
| t.setMessage(""); |
| ObjectId id; |
| try { |
| id = inserter.insert(t); |
| inserter.flush(); |
| } finally { |
| inserter.release(); |
| } |
| return (RevTag) pool.lookupAny(id, Constants.OBJ_TAG); |
| } |
| |
| /** |
| * Update a reference to point to an object. |
| * |
| * @param ref |
| * the name of the reference to update to. If {@code ref} does |
| * not start with {@code refs/} and is not the magic names |
| * {@code HEAD} {@code FETCH_HEAD} or {@code MERGE_HEAD}, then |
| * {@code refs/heads/} will be prefixed in front of the given |
| * name, thereby assuming it is a branch. |
| * @param to |
| * the target object. |
| * @return the target object. |
| * @throws Exception |
| */ |
| public RevCommit update(String ref, CommitBuilder to) throws Exception { |
| return update(ref, to.create()); |
| } |
| |
| /** |
| * Update a reference to point to an object. |
| * |
| * @param <T> |
| * type of the target object. |
| * @param ref |
| * the name of the reference to update to. If {@code ref} does |
| * not start with {@code refs/} and is not the magic names |
| * {@code HEAD} {@code FETCH_HEAD} or {@code MERGE_HEAD}, then |
| * {@code refs/heads/} will be prefixed in front of the given |
| * name, thereby assuming it is a branch. |
| * @param obj |
| * the target object. |
| * @return the target object. |
| * @throws Exception |
| */ |
| public <T extends AnyObjectId> T update(String ref, T obj) throws Exception { |
| if (Constants.HEAD.equals(ref)) { |
| // nothing |
| } else if ("FETCH_HEAD".equals(ref)) { |
| // nothing |
| } else if ("MERGE_HEAD".equals(ref)) { |
| // nothing |
| } else if (ref.startsWith(Constants.R_REFS)) { |
| // nothing |
| } else |
| ref = Constants.R_HEADS + ref; |
| |
| RefUpdate u = db.updateRef(ref); |
| u.setNewObjectId(obj); |
| switch (u.forceUpdate()) { |
| case FAST_FORWARD: |
| case FORCED: |
| case NEW: |
| case NO_CHANGE: |
| updateServerInfo(); |
| return obj; |
| |
| default: |
| throw new IOException("Cannot write " + ref + " " + u.getResult()); |
| } |
| } |
| |
| /** |
| * Update the dumb client server info files. |
| * |
| * @throws Exception |
| */ |
| public void updateServerInfo() throws Exception { |
| if (db instanceof FileRepository) { |
| final FileRepository fr = (FileRepository) db; |
| RefWriter rw = new RefWriter(fr.getAllRefs().values()) { |
| @Override |
| protected void writeFile(final String name, final byte[] bin) |
| throws IOException { |
| File path = new File(fr.getDirectory(), name); |
| TestRepository.this.writeFile(path, bin); |
| } |
| }; |
| rw.writePackedRefs(); |
| rw.writeInfoRefs(); |
| |
| final StringBuilder w = new StringBuilder(); |
| for (PackFile p : fr.getObjectDatabase().getPacks()) { |
| w.append("P "); |
| w.append(p.getPackFile().getName()); |
| w.append('\n'); |
| } |
| writeFile(new File(new File(fr.getObjectDatabase().getDirectory(), |
| "info"), "packs"), Constants.encodeASCII(w.toString())); |
| } |
| } |
| |
| /** |
| * Ensure the body of the given object has been parsed. |
| * |
| * @param <T> |
| * type of object, e.g. {@link RevTag} or {@link RevCommit}. |
| * @param object |
| * reference to the (possibly unparsed) object to force body |
| * parsing of. |
| * @return {@code object} |
| * @throws Exception |
| */ |
| public <T extends RevObject> T parseBody(final T object) throws Exception { |
| pool.parseBody(object); |
| return object; |
| } |
| |
| /** |
| * Create a new branch builder for this repository. |
| * |
| * @param ref |
| * name of the branch to be constructed. If {@code ref} does not |
| * start with {@code refs/} the prefix {@code refs/heads/} will |
| * be added. |
| * @return builder for the named branch. |
| */ |
| public BranchBuilder branch(String ref) { |
| if (Constants.HEAD.equals(ref)) { |
| // nothing |
| } else if (ref.startsWith(Constants.R_REFS)) { |
| // nothing |
| } else |
| ref = Constants.R_HEADS + ref; |
| return new BranchBuilder(ref); |
| } |
| |
| /** |
| * Tag an object using a lightweight tag. |
| * |
| * @param name |
| * the tag name. The /refs/tags/ prefix will be added if the name |
| * doesn't start with it |
| * @param obj |
| * the object to tag |
| * @return the tagged object |
| * @throws Exception |
| */ |
| public ObjectId lightweightTag(String name, ObjectId obj) throws Exception { |
| if (!name.startsWith(Constants.R_TAGS)) |
| name = Constants.R_TAGS + name; |
| return update(name, obj); |
| } |
| |
| /** |
| * Run consistency checks against the object database. |
| * <p> |
| * This method completes silently if the checks pass. A temporary revision |
| * pool is constructed during the checking. |
| * |
| * @param tips |
| * the tips to start checking from; if not supplied the refs of |
| * the repository are used instead. |
| * @throws MissingObjectException |
| * @throws IncorrectObjectTypeException |
| * @throws IOException |
| */ |
| public void fsck(RevObject... tips) throws MissingObjectException, |
| IncorrectObjectTypeException, IOException { |
| ObjectWalk ow = new ObjectWalk(db); |
| if (tips.length != 0) { |
| for (RevObject o : tips) |
| ow.markStart(ow.parseAny(o)); |
| } else { |
| for (Ref r : db.getAllRefs().values()) |
| ow.markStart(ow.parseAny(r.getObjectId())); |
| } |
| |
| ObjectChecker oc = new ObjectChecker(); |
| for (;;) { |
| final RevCommit o = ow.next(); |
| if (o == null) |
| break; |
| |
| final byte[] bin = db.open(o, o.getType()).getCachedBytes(); |
| oc.checkCommit(bin); |
| assertHash(o, bin); |
| } |
| |
| for (;;) { |
| final RevObject o = ow.nextObject(); |
| if (o == null) |
| break; |
| |
| final byte[] bin = db.open(o, o.getType()).getCachedBytes(); |
| oc.check(o.getType(), bin); |
| assertHash(o, bin); |
| } |
| } |
| |
| private static void assertHash(RevObject id, byte[] bin) { |
| MessageDigest md = Constants.newMessageDigest(); |
| md.update(Constants.encodedTypeString(id.getType())); |
| md.update((byte) ' '); |
| md.update(Constants.encodeASCII(bin.length)); |
| md.update((byte) 0); |
| md.update(bin); |
| assertEquals(id, ObjectId.fromRaw(md.digest())); |
| } |
| |
| /** |
| * Pack all reachable objects in the repository into a single pack file. |
| * <p> |
| * All loose objects are automatically pruned. Existing packs however are |
| * not removed. |
| * |
| * @throws Exception |
| */ |
| public void packAndPrune() throws Exception { |
| if (db.getObjectDatabase() instanceof ObjectDirectory) { |
| ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase(); |
| NullProgressMonitor m = NullProgressMonitor.INSTANCE; |
| |
| final File pack, idx; |
| PackWriter pw = new PackWriter(db); |
| try { |
| Set<ObjectId> all = new HashSet<ObjectId>(); |
| for (Ref r : db.getAllRefs().values()) |
| all.add(r.getObjectId()); |
| pw.preparePack(m, all, Collections.<ObjectId> emptySet()); |
| |
| final ObjectId name = pw.computeName(); |
| OutputStream out; |
| |
| pack = nameFor(odb, name, ".pack"); |
| out = new SafeBufferedOutputStream(new FileOutputStream(pack)); |
| try { |
| pw.writePack(m, m, out); |
| } finally { |
| out.close(); |
| } |
| pack.setReadOnly(); |
| |
| idx = nameFor(odb, name, ".idx"); |
| out = new SafeBufferedOutputStream(new FileOutputStream(idx)); |
| try { |
| pw.writeIndex(out); |
| } finally { |
| out.close(); |
| } |
| idx.setReadOnly(); |
| } finally { |
| pw.release(); |
| } |
| |
| odb.openPack(pack); |
| updateServerInfo(); |
| prunePacked(odb); |
| } |
| } |
| |
| private static void prunePacked(ObjectDirectory odb) throws IOException { |
| for (PackFile p : odb.getPacks()) { |
| for (MutableEntry e : p) |
| FileUtils.delete(odb.fileFor(e.toObjectId())); |
| } |
| } |
| |
| private static File nameFor(ObjectDirectory odb, ObjectId name, String t) { |
| File packdir = new File(odb.getDirectory(), "pack"); |
| return new File(packdir, "pack-" + name.name() + t); |
| } |
| |
| private void writeFile(final File p, final byte[] bin) throws IOException, |
| ObjectWritingException { |
| final LockFile lck = new LockFile(p, db.getFS()); |
| if (!lck.lock()) |
| throw new ObjectWritingException("Can't write " + p); |
| try { |
| lck.write(bin); |
| } catch (IOException ioe) { |
| throw new ObjectWritingException("Can't write " + p); |
| } |
| if (!lck.commit()) |
| throw new ObjectWritingException("Can't write " + p); |
| } |
| |
| /** Helper to build a branch with one or more commits */ |
| public class BranchBuilder { |
| private final String ref; |
| |
| BranchBuilder(final String ref) { |
| this.ref = ref; |
| } |
| |
| /** |
| * @return construct a new commit builder that updates this branch. If |
| * the branch already exists, the commit builder will have its |
| * first parent as the current commit and its tree will be |
| * initialized to the current files. |
| * @throws Exception |
| * the commit builder can't read the current branch state |
| */ |
| public CommitBuilder commit() throws Exception { |
| return new CommitBuilder(this); |
| } |
| |
| /** |
| * Forcefully update this branch to a particular commit. |
| * |
| * @param to |
| * the commit to update to. |
| * @return {@code to}. |
| * @throws Exception |
| */ |
| public RevCommit update(CommitBuilder to) throws Exception { |
| return update(to.create()); |
| } |
| |
| /** |
| * Forcefully update this branch to a particular commit. |
| * |
| * @param to |
| * the commit to update to. |
| * @return {@code to}. |
| * @throws Exception |
| */ |
| public RevCommit update(RevCommit to) throws Exception { |
| return TestRepository.this.update(ref, to); |
| } |
| } |
| |
| /** Helper to generate a commit. */ |
| public class CommitBuilder { |
| private final BranchBuilder branch; |
| |
| private final DirCache tree = DirCache.newInCore(); |
| |
| private ObjectId topLevelTree; |
| |
| private final List<RevCommit> parents = new ArrayList<RevCommit>(2); |
| |
| private int tick = 1; |
| |
| private String message = ""; |
| |
| private RevCommit self; |
| |
| CommitBuilder() { |
| branch = null; |
| } |
| |
| CommitBuilder(BranchBuilder b) throws Exception { |
| branch = b; |
| |
| Ref ref = db.getRef(branch.ref); |
| if (ref != null) { |
| parent(pool.parseCommit(ref.getObjectId())); |
| } |
| } |
| |
| CommitBuilder(CommitBuilder prior) throws Exception { |
| branch = prior.branch; |
| |
| DirCacheBuilder b = tree.builder(); |
| for (int i = 0; i < prior.tree.getEntryCount(); i++) |
| b.add(prior.tree.getEntry(i)); |
| b.finish(); |
| |
| parents.add(prior.create()); |
| } |
| |
| public CommitBuilder parent(RevCommit p) throws Exception { |
| if (parents.isEmpty()) { |
| DirCacheBuilder b = tree.builder(); |
| parseBody(p); |
| b.addTree(new byte[0], DirCacheEntry.STAGE_0, pool |
| .getObjectReader(), p.getTree()); |
| b.finish(); |
| } |
| parents.add(p); |
| return this; |
| } |
| |
| public CommitBuilder noParents() { |
| parents.clear(); |
| return this; |
| } |
| |
| public CommitBuilder noFiles() { |
| tree.clear(); |
| return this; |
| } |
| |
| public CommitBuilder setTopLevelTree(ObjectId treeId) { |
| topLevelTree = treeId; |
| return this; |
| } |
| |
| public CommitBuilder add(String path, String content) throws Exception { |
| return add(path, blob(content)); |
| } |
| |
| public CommitBuilder add(String path, final RevBlob id) |
| throws Exception { |
| return edit(new PathEdit(path) { |
| @Override |
| public void apply(DirCacheEntry ent) { |
| ent.setFileMode(FileMode.REGULAR_FILE); |
| ent.setObjectId(id); |
| } |
| }); |
| } |
| |
| public CommitBuilder edit(PathEdit edit) { |
| DirCacheEditor e = tree.editor(); |
| e.add(edit); |
| e.finish(); |
| return this; |
| } |
| |
| public CommitBuilder rm(String path) { |
| DirCacheEditor e = tree.editor(); |
| e.add(new DeletePath(path)); |
| e.add(new DeleteTree(path)); |
| e.finish(); |
| return this; |
| } |
| |
| public CommitBuilder message(String m) { |
| message = m; |
| return this; |
| } |
| |
| public CommitBuilder tick(int secs) { |
| tick = secs; |
| return this; |
| } |
| |
| public RevCommit create() throws Exception { |
| if (self == null) { |
| TestRepository.this.tick(tick); |
| |
| final org.eclipse.jgit.lib.CommitBuilder c; |
| |
| c = new org.eclipse.jgit.lib.CommitBuilder(); |
| c.setParentIds(parents); |
| setAuthorAndCommitter(c); |
| c.setMessage(message); |
| |
| ObjectId commitId; |
| try { |
| if (topLevelTree != null) |
| c.setTreeId(topLevelTree); |
| else |
| c.setTreeId(tree.writeTree(inserter)); |
| commitId = inserter.insert(c); |
| inserter.flush(); |
| } finally { |
| inserter.release(); |
| } |
| self = pool.lookupCommit(commitId); |
| |
| if (branch != null) |
| branch.update(self); |
| } |
| return self; |
| } |
| |
| public CommitBuilder child() throws Exception { |
| return new CommitBuilder(this); |
| } |
| } |
| } |