diff options
author | Shawn O. Pearce <spearce@spearce.org> | 2010-01-05 11:44:52 -0800 |
---|---|---|
committer | Shawn O. Pearce <spearce@spearce.org> | 2010-01-12 11:56:55 -0800 |
commit | f945c424d0cc7688cd160fd5ed9636cd2479e378 (patch) | |
tree | e55b3dcb963eabdc3be1247d3975508f76e98546 /org.eclipse.jgit.junit/src | |
parent | 23cb7f9d5c1fa1c57cc59350ff16a9ecff527313 (diff) | |
download | jgit-f945c424d0cc7688cd160fd5ed9636cd2479e378.tar.gz jgit-f945c424d0cc7688cd160fd5ed9636cd2479e378.zip |
Abstract out utility functions for creating test commits
These routines create a fairly clean DSL for writing out the
structure of a repository in a test case. Abstract them into
a helper class that we can reuse in other test environments.
Change-Id: I55cce3d557e1a28afe2fdf37b3a5b67e2651c9f1
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
Diffstat (limited to 'org.eclipse.jgit.junit/src')
-rw-r--r-- | org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java new file mode 100644 index 0000000000..ce8b3e6ee6 --- /dev/null +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -0,0 +1,650 @@ +/* + * 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 java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import junit.framework.Assert; +import junit.framework.AssertionFailedError; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEntry; +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.errors.ObjectWritingException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Commit; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.LockFile; +import org.eclipse.jgit.lib.ObjectDirectory; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectWriter; +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.Tag; +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; + +/** Wrapper to make creating test data easier. */ +public class TestRepository { + 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 Repository db; + + private final RevWalk pool; + + private final ObjectWriter writer; + + private long now; + + /** + * Wrap a repository with test building tools. + * + * @param db + * the test repository to write into. + * @throws Exception + */ + public TestRepository(Repository db) throws Exception { + 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 Exception + */ + public TestRepository(Repository db, RevWalk rw) throws Exception { + this.db = db; + this.pool = rw; + this.writer = new ObjectWriter(db); + this.now = 1236977987000L; + } + + /** @return the repository this helper class operates against. */ + public Repository 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; + } + + /** + * 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 { + return pool.lookupBlob(writer.writeBlob(content)); + } + + /** + * 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(); + return pool.lookupTree(dc.writeTree(writer)); + } + + /** + * 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 AssertionFailedError + * if the path does not exist in the given tree. + * @throws Exception + */ + public RevObject get(final RevTree tree, final String path) + throws AssertionFailedError, Exception { + final TreeWalk tw = new TreeWalk(db); + 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()); + } + Assert.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 Commit c = new Commit(db); + c.setTreeId(tree); + c.setParentIds(parents); + c.setAuthor(new PersonIdent(author, new Date(now))); + c.setCommitter(new PersonIdent(committer, new Date(now))); + c.setMessage(""); + return pool.lookupCommit(writer.writeCommit(c)); + } + + /** @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 Tag t = new Tag(db); + t.setType(Constants.typeString(dst.getType())); + t.setObjId(dst.toObjectId()); + t.setTag(name); + t.setTagger(new PersonIdent(committer, new Date(now))); + t.setMessage(""); + return (RevTag) pool.lookupAny(writer.writeTag(t), 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)) { + } else if ("FETCH_HEAD".equals(ref)) { + } else if ("MERGE_HEAD".equals(ref)) { + } else if (ref.startsWith(Constants.R_REFS)) { + } 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.getObjectDatabase() instanceof ObjectDirectory) { + RefWriter rw = new RefWriter(db.getAllRefs().values()) { + @Override + protected void writeFile(final String name, final byte[] bin) + throws IOException { + final File p = new File(db.getDirectory(), name); + final LockFile lck = new LockFile(p); + 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); + } + }; + rw.writePackedRefs(); + rw.writeInfoRefs(); + } + } + + /** + * 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)) { + } else if (ref.startsWith(Constants.R_REFS)) { + } else + ref = Constants.R_HEADS + ref; + return new BranchBuilder(ref); + } + + /** 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 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, db, 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 add(String path, String content) throws Exception { + return add(path, blob(content)); + } + + public CommitBuilder add(String path, final RevBlob id) + throws Exception { + DirCacheEditor e = tree.editor(); + e.add(new PathEdit(path) { + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.REGULAR_FILE); + ent.setObjectId(id); + } + }); + 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 Commit c = new Commit(db); + c.setTreeId(pool.lookupTree(tree.writeTree(writer))); + c.setParentIds(parents.toArray(new RevCommit[parents.size()])); + c.setAuthor(new PersonIdent(author, new Date(now))); + c.setCommitter(new PersonIdent(committer, new Date(now))); + c.setMessage(message); + + self = pool.lookupCommit(writer.writeCommit(c)); + + if (branch != null) + branch.update(self); + } + return self; + } + + public CommitBuilder child() throws Exception { + return new CommitBuilder(this); + } + } +} |