aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn O. Pearce <spearce@spearce.org>2010-06-14 15:48:53 -0700
committerShawn O. Pearce <spearce@spearce.org>2010-06-25 17:46:41 -0700
commitcad10e6640258fd6bc6bc3183e4dbc61e83bf544 (patch)
treef61e2f8c1cedf9620a784b7c537697b1044f4619
parent3e3a50db5e9c396769c24778f7f496c79068412d (diff)
downloadjgit-cad10e6640258fd6bc6bc3183e4dbc61e83bf544.tar.gz
jgit-cad10e6640258fd6bc6bc3183e4dbc61e83bf544.zip
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>
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java17
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java177
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java395
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java331
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java11
12 files changed, 695 insertions, 283 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java
index feef66f9ad..e2a3564662 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java
@@ -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 @@ public class ReadTreeTest extends RepositoryTestCase {
}
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());
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java
index 40f110684e..64b1254cc3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java
@@ -86,6 +86,11 @@ public final class AlternateRepositoryDatabase extends ObjectDatabase {
}
@Override
+ public ObjectInserter newInserter() {
+ return odb.newInserter();
+ }
+
+ @Override
public boolean exists() {
return odb.exists();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java
index 3dcea1636f..f593bfca4a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java
@@ -129,4 +129,9 @@ public class CachedObjectDatabase extends ObjectDatabase {
// The situation might become even more tricky if we will consider alternates.
return wrapped.newCachedDatabase();
}
+
+ @Override
+ public ObjectInserter newInserter() {
+ return wrapped.newInserter();
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
index 9080ec1398..ab024c790c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
@@ -74,7 +74,6 @@ public class CoreConfig {
}
/**
- * @see ObjectWriter
* @return The compression level to use when storing loose objects
*/
public int getCompression() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java
index f5b1bf8480..0ad558b949 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java
@@ -249,12 +249,17 @@ public class FileRepository extends Repository {
}
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;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
index 7eac79fb78..df52ae02f8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
@@ -92,6 +92,17 @@ public abstract class ObjectDatabase {
}
/**
+ * 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.
*/
public final void close() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java
index 9a5bcdb68c..ac3c7bf279 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java
@@ -76,6 +76,8 @@ import org.eclipse.jgit.util.FS;
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");
@@ -131,6 +136,11 @@ public class ObjectDirectory extends ObjectDatabase {
}
@Override
+ public ObjectInserter newInserter() {
+ return new ObjectDirectoryInserter(this, config);
+ }
+
+ @Override
public void closeSelf() {
final PackList packs = packList.get();
packList.set(NO_PACKS);
@@ -501,7 +511,7 @@ public class ObjectDirectory extends ObjectDatabase {
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 {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java
new file mode 100644
index 0000000000..146d2d6250
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java
@@ -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.");
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
new file mode 100644
index 0000000000..fd99d39e78
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
@@ -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();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java
index 3ba67476e9..ce91efb8e4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java
@@ -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 class ObjectWriter {
*/
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');
- }
-
- 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');
+ 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('\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 class ObjectWriter {
* @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;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index 201c7a3fdc..334581415a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -148,6 +148,11 @@ public abstract class Repository {
*/
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();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
index 6c146f79f0..2ffcbed9f1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
@@ -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 class ChangeIdUtil {
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