]> source.dussan.org Git - jgit.git/commitdiff
Refactor object writing responsiblities to ObjectDatabase 59/859/7
authorShawn O. Pearce <spearce@spearce.org>
Mon, 14 Jun 2010 22:48:53 +0000 (15:48 -0700)
committerShawn O. Pearce <spearce@spearce.org>
Sat, 26 Jun 2010 00:46:41 +0000 (17:46 -0700)
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>
12 files changed:
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/CachedObjectDatabase.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/FileRepository.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectoryInserter.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java

index feef66f9ad3d40bde7b342051271e0da26e23563..e2a3564662c3a6191616bb4e6ec761d3ebeb9e8a 100644 (file)
 
 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());
                }
index 40f110684eea5f0c81ab1d33c1d0a9f0064f39a7..64b1254cc3ee227cd48290a1eddc76761d91dae1 100644 (file)
@@ -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();
index 3dcea1636f39086b836de19ab1b176d66f2229d4..f593bfca4af0e03812d2a1f11ea6a57f0a7447ca 100644 (file)
@@ -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();
+       }
 }
index 9080ec13980b37ec6ca3835c22d9ad12eb93c188..ab024c790c13f9562c59aab25de29436afbe396b 100644 (file)
@@ -74,7 +74,6 @@ public class CoreConfig {
        }
 
        /**
-        * @see ObjectWriter
         * @return The compression level to use when storing loose objects
         */
        public int getCompression() {
index f5b1bf84801bcf12f36c5685cd52bffc42173a19..0ad558b9493e040bb3a486f789a9edeb102f9cd7 100644 (file)
@@ -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;
index 7eac79fb78a10a850c97372ecf8af7d0166fdd4a..df52ae02f8853ba1c913c8197fdf5574dea3fecf 100644 (file)
@@ -91,6 +91,17 @@ public abstract class ObjectDatabase {
                // 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.
         */
index 9a5bcdb68c2181d70825aa15db77df1066eb90d4..ac3c7bf27925899e01498aff085ce04a2e092ced 100644 (file)
@@ -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");
@@ -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 (file)
index 0000000..146d2d6
--- /dev/null
@@ -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 (file)
index 0000000..fd99d39
--- /dev/null
@@ -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();
+       }
+}
index 3ba67476e9656e35e842a48d63729dcee03b745b..ce91efb8e44abf03cbd0e1eb4e023497dff6b215 100644 (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
 
 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;
        }
 }
index 201c7a3fdc87352d55e365db311504ccca6f58c1..334581415ae698aa05adb93bdec998b1be4a0d3b 100644 (file)
@@ -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();
 
index 6c146f79f08f14e5d57c16a8c74dea25e16c0a2a..2ffcbed9f1e474df49f6065257645a2096e73d5a 100644 (file)
  */
 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