Refactor object writing responsiblities to ObjectDatabase

The ObjectInserter API permits ObjectDatabase implementations to
control their own object insertion behavior, rather than forcing
it to always be a new loose file created in the local filesystem.
Inserted objects can also be queued and written asynchronously to
the main application, such as by appending into a pack file that
is later closed and added to the repository.

This change also starts to open the door to non-file based object
storage, such as an in-memory HashMap for unit testing, or a more
complex system built on top of a distributed hash table.

To help existing application code port to the newer interface we
are keeping ObjectWriter as a delegation wrapper to the new API.
Each ObjectWriter instances holds a reference to an ObjectInserter
for the Repository's top-level ObjectDatabase, and it flushes and
releases that instance on each object processed.

Change-Id: I413224fb95563e7330c82748deb0aada4e0d6ace
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
This commit is contained in:
Shawn O. Pearce 2010-06-14 15:48:53 -07:00
parent 3e3a50db5e
commit cad10e6640
12 changed files with 695 additions and 283 deletions

View File

@ -46,10 +46,8 @@
package org.eclipse.jgit.lib;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
@ -125,11 +123,9 @@ private Tree buildTree(HashMap<String, String> headEntries) throws IOException {
}
ObjectId genSha1(String data) {
InputStream is = new ByteArrayInputStream(data.getBytes());
ObjectWriter objectWriter = new ObjectWriter(db);
try {
return objectWriter.writeObject(Constants.OBJ_BLOB, data
.getBytes().length, is, true);
return objectWriter.writeBlob(data.getBytes());
} catch (IOException e) {
fail(e.toString());
}

View File

@ -85,6 +85,11 @@ public void create() throws IOException {
repository.create();
}
@Override
public ObjectInserter newInserter() {
return odb.newInserter();
}
@Override
public boolean exists() {
return odb.exists();

View File

@ -129,4 +129,9 @@ public ObjectDatabase newCachedDatabase() {
// The situation might become even more tricky if we will consider alternates.
return wrapped.newCachedDatabase();
}
@Override
public ObjectInserter newInserter() {
return wrapped.newInserter();
}
}

View File

@ -74,7 +74,6 @@ private CoreConfig(final Config rc) {
}
/**
* @see ObjectWriter
* @return The compression level to use when storing loose objects
*/
public int getCompression() {

View File

@ -249,12 +249,17 @@ public FileRepository(final File d, final File workTree, final File objectDir,
}
refs = new RefDirectory(this);
if (objectDir != null)
objectDatabase = new ObjectDirectory(fs.resolve(objectDir, ""),
alternateObjectDir, fs);
else
objectDatabase = new ObjectDirectory(fs.resolve(gitDir, "objects"),
alternateObjectDir, fs);
if (objectDir != null) {
objectDatabase = new ObjectDirectory(repoConfig, //
fs.resolve(objectDir, ""), //
alternateObjectDir, //
fs);
} else {
objectDatabase = new ObjectDirectory(repoConfig, //
fs.resolve(gitDir, "objects"), //
alternateObjectDir, //
fs);
}
if (indexFile != null)
this.indexFile = indexFile;

View File

@ -91,6 +91,17 @@ public void create() throws IOException {
// Assume no action is required.
}
/**
* Create a new {@code ObjectInserter} to insert new objects.
* <p>
* The returned inserter is not itself thread-safe, but multiple concurrent
* inserter instances created from the same {@code ObjectDatabase} must be
* thread-safe.
*
* @return writer the caller can use to create objects in this database.
*/
public abstract ObjectInserter newInserter();
/**
* Close any resources held by this database and its active alternates.
*/

View File

@ -76,6 +76,8 @@
public class ObjectDirectory extends ObjectDatabase {
private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]);
private final Config config;
private final File objects;
private final File infoDirectory;
@ -93,6 +95,8 @@ public class ObjectDirectory extends ObjectDatabase {
/**
* Initialize a reference to an on-disk object directory.
*
* @param cfg
* configuration this directory consults for write settings.
* @param dir
* the location of the <code>objects</code> directory.
* @param alternateObjectDir
@ -101,7 +105,8 @@ public class ObjectDirectory extends ObjectDatabase {
* the file system abstraction which will be necessary to
* perform certain file system operations.
*/
public ObjectDirectory(final File dir, File[] alternateObjectDir, FS fs) {
public ObjectDirectory(final Config cfg, final File dir, File[] alternateObjectDir, FS fs) {
config = cfg;
objects = dir;
this.alternateObjectDir = alternateObjectDir;
infoDirectory = new File(objects, "info");
@ -130,6 +135,11 @@ public void create() throws IOException {
packDirectory.mkdir();
}
@Override
public ObjectInserter newInserter() {
return new ObjectDirectoryInserter(this, config);
}
@Override
public void closeSelf() {
final PackList packs = packList.get();
@ -501,7 +511,7 @@ private ObjectDatabase openAlternate(File objdir) throws IOException {
final Repository db = RepositoryCache.open(FileKey.exact(parent, fs));
return new AlternateRepositoryDatabase(db);
}
return new ObjectDirectory(objdir, null, fs);
return new ObjectDirectory(config, objdir, null, fs);
}
private static final class PackList {

View File

@ -0,0 +1,177 @@
/*
* Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2009, 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.lib;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import org.eclipse.jgit.errors.ObjectWritingException;
/** Creates loose objects in a {@link ObjectDirectory}. */
class ObjectDirectoryInserter extends ObjectInserter {
private final ObjectDirectory db;
private final Config config;
private Deflater deflate;
ObjectDirectoryInserter(final ObjectDirectory dest, final Config cfg) {
db = dest;
config = cfg;
}
@Override
public ObjectId insert(final int type, long len, final InputStream is)
throws IOException {
final MessageDigest md = digest();
final File tmp = toTemp(md, type, len, is);
final ObjectId id = ObjectId.fromRaw(md.digest());
if (db.hasObject(id)) {
// Object is already in the repository, remove temporary file.
//
tmp.delete();
return id;
}
final File dst = db.fileFor(id);
if (tmp.renameTo(dst))
return id;
// Maybe the directory doesn't exist yet as the object
// directories are always lazily created. Note that we
// try the rename first as the directory likely does exist.
//
dst.getParentFile().mkdir();
if (tmp.renameTo(dst))
return id;
if (db.hasObject(id)) {
tmp.delete();
return id;
}
// The object failed to be renamed into its proper
// location and it doesn't exist in the repository
// either. We really don't know what went wrong, so
// fail.
//
tmp.delete();
throw new ObjectWritingException("Unable to create new object: " + dst);
}
@Override
public void flush() throws IOException {
// Do nothing. Objects are immediately visible.
}
@Override
public void release() {
if (deflate != null) {
try {
deflate.end();
} finally {
deflate = null;
}
}
}
private File toTemp(final MessageDigest md, final int type, long len,
final InputStream is) throws IOException, FileNotFoundException,
Error {
boolean delete = true;
File tmp = File.createTempFile("noz", null, db.getDirectory());
try {
DigestOutputStream dOut = new DigestOutputStream(
compress(new FileOutputStream(tmp)), md);
try {
dOut.write(Constants.encodedTypeString(type));
dOut.write((byte) ' ');
dOut.write(Constants.encodeASCII(len));
dOut.write((byte) 0);
final byte[] buf = buffer();
while (len > 0) {
int n = is.read(buf, 0, (int) Math.min(len, buf.length));
if (n <= 0)
throw shortInput(len);
dOut.write(buf, 0, n);
len -= n;
}
} finally {
dOut.close();
}
tmp.setReadOnly();
delete = false;
return tmp;
} finally {
if (delete)
tmp.delete();
}
}
private DeflaterOutputStream compress(final OutputStream out) {
if (deflate == null)
deflate = new Deflater(config.get(CoreConfig.KEY).getCompression());
else
deflate.reset();
return new DeflaterOutputStream(out, deflate);
}
private static EOFException shortInput(long missing) {
return new EOFException("Input did not match supplied length. "
+ missing + " bytes are missing.");
}
}

View File

@ -0,0 +1,395 @@
/*
* Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2009, 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.lib;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.text.MessageFormat;
import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.errors.ObjectWritingException;
/**
* Inserts objects into an existing {@code ObjectDatabase}.
* <p>
* An inserter is not thread-safe. Individual threads should each obtain their
* own unique inserter instance, or must arrange for locking at a higher level
* to ensure the inserter is in use by no more than one thread at a time.
* <p>
* Objects written by an inserter may not be immediately visible for reading
* after the insert method completes. Callers must invoke either
* {@link #release()} or {@link #flush()} prior to updating references or
* otherwise making the returned ObjectIds visible to other code.
*/
public abstract class ObjectInserter {
private static final byte[] htree = Constants.encodeASCII("tree");
private static final byte[] hparent = Constants.encodeASCII("parent");
private static final byte[] hauthor = Constants.encodeASCII("author");
private static final byte[] hcommitter = Constants.encodeASCII("committer");
private static final byte[] hencoding = Constants.encodeASCII("encoding");
/** An inserter that can be used for formatting and id generation only. */
public static class Formatter extends ObjectInserter {
@Override
public ObjectId insert(int objectType, long length, InputStream in)
throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void flush() throws IOException {
// Do nothing.
}
@Override
public void release() {
// Do nothing.
}
}
/** Digest to compute the name of an object. */
private final MessageDigest digest;
/** Temporary working buffer for streaming data through. */
private byte[] tempBuffer;
/** Create a new inserter for a database. */
protected ObjectInserter() {
digest = Constants.newMessageDigest();
}
/** @return a temporary byte array for use by the caller. */
protected byte[] buffer() {
if (tempBuffer == null)
tempBuffer = new byte[8192];
return tempBuffer;
}
/** @return digest to help compute an ObjectId */
protected MessageDigest digest() {
digest.reset();
return digest;
}
/**
* Compute the name of an object, without inserting it.
*
* @param type
* type code of the object to store.
* @param data
* complete content of the object.
* @return the name of the object.
*/
public ObjectId idFor(int type, byte[] data) {
return idFor(type, data, 0, data.length);
}
/**
* Compute the name of an object, without inserting it.
*
* @param type
* type code of the object to store.
* @param data
* complete content of the object.
* @param off
* first position within {@code data}.
* @param len
* number of bytes to copy from {@code data}.
* @return the name of the object.
*/
public ObjectId idFor(int type, byte[] data, int off, int len) {
MessageDigest md = digest();
md.update(Constants.encodedTypeString(type));
md.update((byte) ' ');
md.update(Constants.encodeASCII(len));
md.update((byte) 0);
md.update(data, off, len);
return ObjectId.fromRaw(md.digest());
}
/**
* Compute the name of an object, without inserting it.
*
* @param objectType
* type code of the object to store.
* @param length
* number of bytes to scan from {@code in}.
* @param in
* stream providing the object content. The caller is responsible
* for closing the stream.
* @return the name of the object.
* @throws IOException
* the source stream could not be read.
*/
public ObjectId idFor(int objectType, long length, InputStream in)
throws IOException {
MessageDigest md = digest();
md.update(Constants.encodedTypeString(objectType));
md.update((byte) ' ');
md.update(Constants.encodeASCII(length));
md.update((byte) 0);
byte[] buf = buffer();
while (length > 0) {
int n = in.read(buf, 0, (int) Math.min(length, buf.length));
if (n < 0)
throw new EOFException("Unexpected end of input");
md.update(buf, 0, n);
length -= n;
}
return ObjectId.fromRaw(md.digest());
}
/**
* Insert a single object into the store, returning its unique name.
*
* @param type
* type code of the object to store.
* @param data
* complete content of the object.
* @return the name of the object.
* @throws IOException
* the object could not be stored.
*/
public ObjectId insert(final int type, final byte[] data)
throws IOException {
return insert(type, data, 0, data.length);
}
/**
* Insert a single object into the store, returning its unique name.
*
* @param type
* type code of the object to store.
* @param data
* complete content of the object.
* @param off
* first position within {@code data}.
* @param len
* number of bytes to copy from {@code data}.
* @return the name of the object.
* @throws IOException
* the object could not be stored.
*/
public ObjectId insert(int type, byte[] data, int off, int len)
throws IOException {
return insert(type, len, new ByteArrayInputStream(data, off, len));
}
/**
* Insert a single object into the store, returning its unique name.
*
* @param objectType
* type code of the object to store.
* @param length
* number of bytes to copy from {@code in}.
* @param in
* stream providing the object content. The caller is responsible
* for closing the stream.
* @return the name of the object.
* @throws IOException
* the object could not be stored, or the source stream could
* not be read.
*/
public abstract ObjectId insert(int objectType, long length, InputStream in)
throws IOException;
/**
* Make all inserted objects visible.
* <p>
* The flush may take some period of time to make the objects available to
* other threads.
*
* @throws IOException
* the flush could not be completed; objects inserted thus far
* are in an indeterminate state.
*/
public abstract void flush() throws IOException;
/**
* Release any resources used by this inserter.
* <p>
* An inserter that has been released can be used again, but may need to be
* released after the subsequent usage.
*/
public abstract void release();
/**
* Format a Tree in canonical format.
*
* @param tree
* the tree object to format
* @return canonical encoding of the tree object.
* @throws IOException
* the tree cannot be loaded, or its not in a writable state.
*/
public final byte[] format(Tree tree) throws IOException {
ByteArrayOutputStream o = new ByteArrayOutputStream();
for (TreeEntry e : tree.members()) {
ObjectId id = e.getId();
if (id == null)
throw new ObjectWritingException(MessageFormat.format(JGitText
.get().objectAtPathDoesNotHaveId, e.getFullName()));
e.getMode().copyTo(o);
o.write(' ');
o.write(e.getNameUTF8());
o.write(0);
id.copyRawTo(o);
}
return o.toByteArray();
}
/**
* Format a Commit in canonical format.
*
* @param commit
* the commit object to format
* @return canonical encoding of the commit object.
* @throws UnsupportedEncodingException
* the commit's chosen encoding isn't supported on this JVM.
*/
public final byte[] format(Commit commit)
throws UnsupportedEncodingException {
String encoding = commit.getEncoding();
if (encoding == null)
encoding = Constants.CHARACTER_ENCODING;
ByteArrayOutputStream os = new ByteArrayOutputStream();
OutputStreamWriter w = new OutputStreamWriter(os, encoding);
try {
os.write(htree);
os.write(' ');
commit.getTreeId().copyTo(os);
os.write('\n');
ObjectId[] ps = commit.getParentIds();
for (int i = 0; i < ps.length; ++i) {
os.write(hparent);
os.write(' ');
ps[i].copyTo(os);
os.write('\n');
}
os.write(hauthor);
os.write(' ');
w.write(commit.getAuthor().toExternalString());
w.flush();
os.write('\n');
os.write(hcommitter);
os.write(' ');
w.write(commit.getCommitter().toExternalString());
w.flush();
os.write('\n');
if (!encoding.equals(Constants.CHARACTER_ENCODING)) {
os.write(hencoding);
os.write(' ');
os.write(Constants.encodeASCII(encoding));
os.write('\n');
}
os.write('\n');
w.write(commit.getMessage());
w.flush();
} catch (IOException err) {
// This should never occur, the only way to get it above is
// for the ByteArrayOutputStream to throw, but it doesn't.
//
throw new RuntimeException(err);
}
return os.toByteArray();
}
/**
* Format a Tag in canonical format.
*
* @param tag
* the tag object to format
* @return canonical encoding of the tag object.
*/
public final byte[] format(Tag tag) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
OutputStreamWriter w = new OutputStreamWriter(os, Constants.CHARSET);
try {
w.write("object ");
tag.getObjId().copyTo(w);
w.write('\n');
w.write("type ");
w.write(tag.getType());
w.write("\n");
w.write("tag ");
w.write(tag.getTag());
w.write("\n");
w.write("tagger ");
w.write(tag.getAuthor().toExternalString());
w.write('\n');
w.write('\n');
w.write(tag.getMessage());
w.close();
} catch (IOException err) {
// This should never occur, the only way to get it above is
// for the ByteArrayOutputStream to throw, but it doesn't.
//
throw new RuntimeException(err);
}
return os.toByteArray();
}
}

View File

@ -1,6 +1,7 @@
/*
* Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2009, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@ -44,61 +45,49 @@
package org.eclipse.jgit.lib;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.security.MessageDigest;
import java.text.MessageFormat;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.errors.ObjectWritingException;
/**
* A class for writing loose objects.
*
* @deprecated Use {@link Repository#newObjectInserter()}.
*/
public class ObjectWriter {
private static final byte[] htree = Constants.encodeASCII("tree");
private static final byte[] hparent = Constants.encodeASCII("parent");
private static final byte[] hauthor = Constants.encodeASCII("author");
private static final byte[] hcommitter = Constants.encodeASCII("committer");
private static final byte[] hencoding = Constants.encodeASCII("encoding");
private final Repository r;
private final byte[] buf;
private final MessageDigest md;
private final ObjectInserter inserter;
/**
* Construct an Object writer for the specified repository
*
* @param d
*/
public ObjectWriter(final Repository d) {
r = d;
buf = new byte[8192];
md = Constants.newMessageDigest();
inserter = d.newObjectInserter();
}
/**
* Write a blob with the specified data
*
* @param b bytes of the blob
* @param b
* bytes of the blob
* @return SHA-1 of the blob
* @throws IOException
*/
public ObjectId writeBlob(final byte[] b) throws IOException {
return writeBlob(b.length, new ByteArrayInputStream(b));
try {
ObjectId id = inserter.insert(OBJ_BLOB, b);
inserter.flush();
return id;
} finally {
inserter.release();
}
}
/**
@ -130,174 +119,101 @@ public ObjectId writeBlob(final File f) throws IOException {
*/
public ObjectId writeBlob(final long len, final InputStream is)
throws IOException {
return writeObject(Constants.OBJ_BLOB, len, is, true);
try {
ObjectId id = inserter.insert(OBJ_BLOB, len, is);
inserter.flush();
return id;
} finally {
inserter.release();
}
}
/**
* Write a Tree to the object database.
*
* @param t
* @param tree
* Tree
* @return SHA-1 of the tree
* @throws IOException
*/
public ObjectId writeTree(final Tree t) throws IOException {
final ByteArrayOutputStream o = new ByteArrayOutputStream();
final TreeEntry[] items = t.members();
for (int k = 0; k < items.length; k++) {
final TreeEntry e = items[k];
final ObjectId id = e.getId();
if (id == null)
throw new ObjectWritingException(MessageFormat.format(
JGitText.get().objectAtPathDoesNotHaveId, e.getFullName()));
e.getMode().copyTo(o);
o.write(' ');
o.write(e.getNameUTF8());
o.write(0);
id.copyRawTo(o);
public ObjectId writeTree(Tree tree) throws IOException {
try {
ObjectId id = inserter.insert(OBJ_TREE, inserter.format(tree));
inserter.flush();
return id;
} finally {
inserter.release();
}
return writeCanonicalTree(o.toByteArray());
}
/**
* Write a canonical tree to the object database.
*
* @param b
* @param treeData
* the canonical encoding of the tree object.
* @return SHA-1 of the tree
* @throws IOException
*/
public ObjectId writeCanonicalTree(final byte[] b) throws IOException {
return writeTree(b.length, new ByteArrayInputStream(b));
}
private ObjectId writeTree(final long len, final InputStream is)
throws IOException {
return writeObject(Constants.OBJ_TREE, len, is, true);
public ObjectId writeCanonicalTree(byte[] treeData) throws IOException {
try {
ObjectId id = inserter.insert(OBJ_TREE, treeData);
inserter.flush();
return id;
} finally {
inserter.release();
}
}
/**
* Write a Commit to the object database
*
* @param c
* @param commit
* Commit to store
* @return SHA-1 of the commit
* @throws IOException
*/
public ObjectId writeCommit(final Commit c) throws IOException {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
String encoding = c.getEncoding();
if (encoding == null)
encoding = Constants.CHARACTER_ENCODING;
final OutputStreamWriter w = new OutputStreamWriter(os, encoding);
os.write(htree);
os.write(' ');
c.getTreeId().copyTo(os);
os.write('\n');
ObjectId[] ps = c.getParentIds();
for (int i=0; i<ps.length; ++i) {
os.write(hparent);
os.write(' ');
ps[i].copyTo(os);
os.write('\n');
public ObjectId writeCommit(Commit commit) throws IOException {
try {
ObjectId id = inserter.insert(OBJ_COMMIT, inserter.format(commit));
inserter.flush();
return id;
} finally {
inserter.release();
}
os.write(hauthor);
os.write(' ');
w.write(c.getAuthor().toExternalString());
w.flush();
os.write('\n');
os.write(hcommitter);
os.write(' ');
w.write(c.getCommitter().toExternalString());
w.flush();
os.write('\n');
if (!encoding.equals(Constants.CHARACTER_ENCODING)) {
os.write(hencoding);
os.write(' ');
os.write(Constants.encodeASCII(encoding));
os.write('\n');
}
os.write('\n');
w.write(c.getMessage());
w.flush();
return writeCommit(os.toByteArray());
}
private ObjectId writeTag(final byte[] b) throws IOException {
return writeTag(b.length, new ByteArrayInputStream(b));
}
/**
* Write an annotated Tag to the object database
*
* @param c
* @param tag
* Tag
* @return SHA-1 of the tag
* @throws IOException
*/
public ObjectId writeTag(final Tag c) throws IOException {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
final OutputStreamWriter w = new OutputStreamWriter(os,
Constants.CHARSET);
w.write("object ");
c.getObjId().copyTo(w);
w.write('\n');
w.write("type ");
w.write(c.getType());
w.write("\n");
w.write("tag ");
w.write(c.getTag());
w.write("\n");
w.write("tagger ");
w.write(c.getAuthor().toExternalString());
w.write('\n');
w.write('\n');
w.write(c.getMessage());
w.close();
return writeTag(os.toByteArray());
}
private ObjectId writeCommit(final byte[] b) throws IOException {
return writeCommit(b.length, new ByteArrayInputStream(b));
}
private ObjectId writeCommit(final long len, final InputStream is)
throws IOException {
return writeObject(Constants.OBJ_COMMIT, len, is, true);
}
private ObjectId writeTag(final long len, final InputStream is)
throws IOException {
return writeObject(Constants.OBJ_TAG, len, is, true);
public ObjectId writeTag(Tag tag) throws IOException {
try {
ObjectId id = inserter.insert(OBJ_TAG, inserter.format(tag));
inserter.flush();
return id;
} finally {
inserter.release();
}
}
/**
* Compute the SHA-1 of a blob without creating an object. This is for
* figuring out if we already have a blob or not.
*
* @param len number of bytes to consume
* @param is stream for read blob data from
* @param len
* number of bytes to consume
* @param is
* stream for read blob data from
* @return SHA-1 of a looked for blob
* @throws IOException
*/
public ObjectId computeBlobSha1(final long len, final InputStream is)
public ObjectId computeBlobSha1(long len, InputStream is)
throws IOException {
return writeObject(Constants.OBJ_BLOB, len, is, false);
return computeObjectSha1(OBJ_BLOB, len, is);
}
/**
@ -313,119 +229,12 @@ public ObjectId computeBlobSha1(final long len, final InputStream is)
* @return SHA-1 of data combined with type information
* @throws IOException
*/
public ObjectId computeObjectSha1(final int type, final long len, final InputStream is)
public ObjectId computeObjectSha1(int type, long len, InputStream is)
throws IOException {
return writeObject(type, len, is, false);
}
ObjectId writeObject(final int type, long len, final InputStream is,
boolean store) throws IOException {
final File t;
final DeflaterOutputStream deflateStream;
final FileOutputStream fileStream;
ObjectId id = null;
Deflater def = null;
if (store) {
t = File.createTempFile("noz", null, r.getObjectsDirectory());
fileStream = new FileOutputStream(t);
} else {
t = null;
fileStream = null;
}
md.reset();
if (store) {
def = new Deflater(r.getConfig().get(CoreConfig.KEY).getCompression());
deflateStream = new DeflaterOutputStream(fileStream, def);
} else
deflateStream = null;
try {
byte[] header;
int n;
header = Constants.encodedTypeString(type);
md.update(header);
if (deflateStream != null)
deflateStream.write(header);
md.update((byte) ' ');
if (deflateStream != null)
deflateStream.write((byte) ' ');
header = Constants.encodeASCII(len);
md.update(header);
if (deflateStream != null)
deflateStream.write(header);
md.update((byte) 0);
if (deflateStream != null)
deflateStream.write((byte) 0);
while (len > 0
&& (n = is.read(buf, 0, (int) Math.min(len, buf.length))) > 0) {
md.update(buf, 0, n);
if (deflateStream != null)
deflateStream.write(buf, 0, n);
len -= n;
}
if (len != 0)
throw new IOException("Input did not match supplied length. "
+ len + " bytes are missing.");
if (deflateStream != null ) {
deflateStream.close();
if (t != null)
t.setReadOnly();
}
id = ObjectId.fromRaw(md.digest());
return inserter.idFor(type, len, is);
} finally {
if (id == null && deflateStream != null) {
try {
deflateStream.close();
} finally {
t.delete();
}
}
if (def != null) {
def.end();
}
inserter.release();
}
if (t == null)
return id;
if (r.hasObject(id)) {
// Object is already in the repository so remove
// the temporary file.
//
t.delete();
} else {
final File o = r.toFile(id);
if (!t.renameTo(o)) {
// Maybe the directory doesn't exist yet as the object
// directories are always lazily created. Note that we
// try the rename first as the directory likely does exist.
//
o.getParentFile().mkdir();
if (!t.renameTo(o)) {
if (!r.hasObject(id)) {
// The object failed to be renamed into its proper
// location and it doesn't exist in the repository
// either. We really don't know what went wrong, so
// fail.
//
t.delete();
throw new ObjectWritingException("Unable to"
+ " create new object: " + o);
}
}
}
}
return id;
}
}

View File

@ -148,6 +148,11 @@ public File getDirectory() {
*/
public abstract ObjectDatabase getObjectDatabase();
/** @return a new inserter to create objects in {@link #getObjectDatabase()} */
public ObjectInserter newObjectInserter() {
return getObjectDatabase().newInserter();
}
/** @return the reference database which stores the reference namespace. */
public abstract RefDatabase getRefDatabase();

View File

@ -42,13 +42,12 @@
*/
package org.eclipse.jgit.util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.regex.Pattern;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectWriter;
import org.eclipse.jgit.lib.PersonIdent;
/**
@ -113,12 +112,8 @@ public static ObjectId computeChangeId(final ObjectId treeId,
b.append(committer.toExternalString());
b.append("\n\n");
b.append(cleanMessage);
ObjectWriter w = new ObjectWriter(null);
byte[] bytes = b.toString().getBytes(Constants.CHARACTER_ENCODING);
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
ObjectId sha1 = w.computeObjectSha1(Constants.OBJ_COMMIT, bytes.length,
is);
return sha1;
return new ObjectInserter.Formatter().idFor(Constants.OBJ_COMMIT, //
b.toString().getBytes(Constants.CHARACTER_ENCODING));
}
private static final Pattern issuePattern = Pattern