/* * 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.BufferedOutputStream; 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 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.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; 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.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectWriter; import org.eclipse.jgit.lib.PackFile; import org.eclipse.jgit.lib.PackWriter; 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.lib.PackIndex.MutableEntry; 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; /** 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. *
* 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. *
* 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. *
* 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. *
* 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. *
* The tagger is the committer identity, at the current time as specified by * {@link #tick(int)}. The time is not increased. *
* 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
* 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.openObject(o).getCachedBytes();
oc.checkCommit(bin);
assertHash(o, bin);
}
for (;;) {
final RevObject o = ow.nextObject();
if (o == null)
break;
final byte[] bin = db.openObject(o).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);
Assert.assertEquals(id.copy(), ObjectId.fromRaw(md.digest()));
}
/**
* Pack all reachable objects in the repository into a single pack file.
*
* All loose objects are automatically pruned. Existing packs however are
* not removed.
*
* @throws Exception
*/
public void packAndPrune() throws Exception {
final ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase();
final PackWriter pw = new PackWriter(db, NullProgressMonitor.INSTANCE);
Set