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 feef66f9a..e2a356466 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 40f110684..64b1254cc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java @@ -85,6 +85,11 @@ public final class AlternateRepositoryDatabase extends ObjectDatabase { repository.create(); } + @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 3dcea1636..f593bfca4 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 9080ec139..ab024c790 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 f5b1bf848..0ad558b94 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 7eac79fb7..df52ae02f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java @@ -91,6 +91,17 @@ public abstract class ObjectDatabase { // Assume no action is required. } + /** + * Create a new {@code ObjectInserter} to insert new objects. + *
+ * 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.
*/
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 9a5bcdb68..ac3c7bf27 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
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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 3ba67476e..ce91efb8e 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 objects
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 class ObjectDirectory extends ObjectDatabase {
packDirectory.mkdir();
}
+ @Override
+ public ObjectInserter newInserter() {
+ return new ObjectDirectoryInserter(this, config);
+ }
+
@Override
public void closeSelf() {
final PackList packs = packList.get();
@@ -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 000000000..146d2d625
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2007, Robin Rosenberg