Refactor object writing responsiblities to ObjectDatabase
The ObjectInserter API permits ObjectDatabase implementations to control their own object insertion behavior, rather than forcing it to always be a new loose file created in the local filesystem. Inserted objects can also be queued and written asynchronously to the main application, such as by appending into a pack file that is later closed and added to the repository. This change also starts to open the door to non-file based object storage, such as an in-memory HashMap for unit testing, or a more complex system built on top of a distributed hash table. To help existing application code port to the newer interface we are keeping ObjectWriter as a delegation wrapper to the new API. Each ObjectWriter instances holds a reference to an ObjectInserter for the Repository's top-level ObjectDatabase, and it flushes and releases that instance on each object processed. Change-Id: I413224fb95563e7330c82748deb0aada4e0d6ace Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
This commit is contained in:
parent
3e3a50db5e
commit
cad10e6640
|
@ -46,10 +46,8 @@
|
|||
|
||||
package org.eclipse.jgit.lib;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
@ -125,11 +123,9 @@ private Tree buildTree(HashMap<String, String> headEntries) throws IOException {
|
|||
}
|
||||
|
||||
ObjectId genSha1(String data) {
|
||||
InputStream is = new ByteArrayInputStream(data.getBytes());
|
||||
ObjectWriter objectWriter = new ObjectWriter(db);
|
||||
try {
|
||||
return objectWriter.writeObject(Constants.OBJ_BLOB, data
|
||||
.getBytes().length, is, true);
|
||||
return objectWriter.writeBlob(data.getBytes());
|
||||
} catch (IOException e) {
|
||||
fail(e.toString());
|
||||
}
|
||||
|
|
|
@ -85,6 +85,11 @@ public void create() throws IOException {
|
|||
repository.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectInserter newInserter() {
|
||||
return odb.newInserter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return odb.exists();
|
||||
|
|
|
@ -129,4 +129,9 @@ public ObjectDatabase newCachedDatabase() {
|
|||
// The situation might become even more tricky if we will consider alternates.
|
||||
return wrapped.newCachedDatabase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectInserter newInserter() {
|
||||
return wrapped.newInserter();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,6 @@ private CoreConfig(final Config rc) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @see ObjectWriter
|
||||
* @return The compression level to use when storing loose objects
|
||||
*/
|
||||
public int getCompression() {
|
||||
|
|
|
@ -249,12 +249,17 @@ public FileRepository(final File d, final File workTree, final File objectDir,
|
|||
}
|
||||
|
||||
refs = new RefDirectory(this);
|
||||
if (objectDir != null)
|
||||
objectDatabase = new ObjectDirectory(fs.resolve(objectDir, ""),
|
||||
alternateObjectDir, fs);
|
||||
else
|
||||
objectDatabase = new ObjectDirectory(fs.resolve(gitDir, "objects"),
|
||||
alternateObjectDir, fs);
|
||||
if (objectDir != null) {
|
||||
objectDatabase = new ObjectDirectory(repoConfig, //
|
||||
fs.resolve(objectDir, ""), //
|
||||
alternateObjectDir, //
|
||||
fs);
|
||||
} else {
|
||||
objectDatabase = new ObjectDirectory(repoConfig, //
|
||||
fs.resolve(gitDir, "objects"), //
|
||||
alternateObjectDir, //
|
||||
fs);
|
||||
}
|
||||
|
||||
if (indexFile != null)
|
||||
this.indexFile = indexFile;
|
||||
|
|
|
@ -91,6 +91,17 @@ public void create() throws IOException {
|
|||
// Assume no action is required.
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code ObjectInserter} to insert new objects.
|
||||
* <p>
|
||||
* The returned inserter is not itself thread-safe, but multiple concurrent
|
||||
* inserter instances created from the same {@code ObjectDatabase} must be
|
||||
* thread-safe.
|
||||
*
|
||||
* @return writer the caller can use to create objects in this database.
|
||||
*/
|
||||
public abstract ObjectInserter newInserter();
|
||||
|
||||
/**
|
||||
* Close any resources held by this database and its active alternates.
|
||||
*/
|
||||
|
|
|
@ -76,6 +76,8 @@
|
|||
public class ObjectDirectory extends ObjectDatabase {
|
||||
private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]);
|
||||
|
||||
private final Config config;
|
||||
|
||||
private final File objects;
|
||||
|
||||
private final File infoDirectory;
|
||||
|
@ -93,6 +95,8 @@ public class ObjectDirectory extends ObjectDatabase {
|
|||
/**
|
||||
* Initialize a reference to an on-disk object directory.
|
||||
*
|
||||
* @param cfg
|
||||
* configuration this directory consults for write settings.
|
||||
* @param dir
|
||||
* the location of the <code>objects</code> directory.
|
||||
* @param alternateObjectDir
|
||||
|
@ -101,7 +105,8 @@ public class ObjectDirectory extends ObjectDatabase {
|
|||
* the file system abstraction which will be necessary to
|
||||
* perform certain file system operations.
|
||||
*/
|
||||
public ObjectDirectory(final File dir, File[] alternateObjectDir, FS fs) {
|
||||
public ObjectDirectory(final Config cfg, final File dir, File[] alternateObjectDir, FS fs) {
|
||||
config = cfg;
|
||||
objects = dir;
|
||||
this.alternateObjectDir = alternateObjectDir;
|
||||
infoDirectory = new File(objects, "info");
|
||||
|
@ -130,6 +135,11 @@ public void create() throws IOException {
|
|||
packDirectory.mkdir();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectInserter newInserter() {
|
||||
return new ObjectDirectoryInserter(this, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeSelf() {
|
||||
final PackList packs = packList.get();
|
||||
|
@ -501,7 +511,7 @@ private ObjectDatabase openAlternate(File objdir) throws IOException {
|
|||
final Repository db = RepositoryCache.open(FileKey.exact(parent, fs));
|
||||
return new AlternateRepositoryDatabase(db);
|
||||
}
|
||||
return new ObjectDirectory(objdir, null, fs);
|
||||
return new ObjectDirectory(config, objdir, null, fs);
|
||||
}
|
||||
|
||||
private static final class PackList {
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
|
||||
* Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
|
||||
* Copyright (C) 2009, Google Inc.
|
||||
* and other copyright owners as documented in the project's IP log.
|
||||
*
|
||||
* This program and the accompanying materials are made available
|
||||
|
@ -44,61 +45,49 @@
|
|||
|
||||
package org.eclipse.jgit.lib;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
||||
import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
|
||||
import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
|
||||
import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.security.MessageDigest;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
|
||||
import org.eclipse.jgit.JGitText;
|
||||
import org.eclipse.jgit.errors.ObjectWritingException;
|
||||
|
||||
/**
|
||||
* A class for writing loose objects.
|
||||
*
|
||||
* @deprecated Use {@link Repository#newObjectInserter()}.
|
||||
*/
|
||||
public class ObjectWriter {
|
||||
private static final byte[] htree = Constants.encodeASCII("tree");
|
||||
|
||||
private static final byte[] hparent = Constants.encodeASCII("parent");
|
||||
|
||||
private static final byte[] hauthor = Constants.encodeASCII("author");
|
||||
|
||||
private static final byte[] hcommitter = Constants.encodeASCII("committer");
|
||||
|
||||
private static final byte[] hencoding = Constants.encodeASCII("encoding");
|
||||
|
||||
private final Repository r;
|
||||
|
||||
private final byte[] buf;
|
||||
|
||||
private final MessageDigest md;
|
||||
private final ObjectInserter inserter;
|
||||
|
||||
/**
|
||||
* Construct an Object writer for the specified repository
|
||||
*
|
||||
* @param d
|
||||
*/
|
||||
public ObjectWriter(final Repository d) {
|
||||
r = d;
|
||||
buf = new byte[8192];
|
||||
md = Constants.newMessageDigest();
|
||||
inserter = d.newObjectInserter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a blob with the specified data
|
||||
*
|
||||
* @param b bytes of the blob
|
||||
* @param b
|
||||
* bytes of the blob
|
||||
* @return SHA-1 of the blob
|
||||
* @throws IOException
|
||||
*/
|
||||
public ObjectId writeBlob(final byte[] b) throws IOException {
|
||||
return writeBlob(b.length, new ByteArrayInputStream(b));
|
||||
try {
|
||||
ObjectId id = inserter.insert(OBJ_BLOB, b);
|
||||
inserter.flush();
|
||||
return id;
|
||||
} finally {
|
||||
inserter.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,174 +119,101 @@ public ObjectId writeBlob(final File f) throws IOException {
|
|||
*/
|
||||
public ObjectId writeBlob(final long len, final InputStream is)
|
||||
throws IOException {
|
||||
return writeObject(Constants.OBJ_BLOB, len, is, true);
|
||||
try {
|
||||
ObjectId id = inserter.insert(OBJ_BLOB, len, is);
|
||||
inserter.flush();
|
||||
return id;
|
||||
} finally {
|
||||
inserter.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a Tree to the object database.
|
||||
*
|
||||
* @param t
|
||||
* @param tree
|
||||
* Tree
|
||||
* @return SHA-1 of the tree
|
||||
* @throws IOException
|
||||
*/
|
||||
public ObjectId writeTree(final Tree t) throws IOException {
|
||||
final ByteArrayOutputStream o = new ByteArrayOutputStream();
|
||||
final TreeEntry[] items = t.members();
|
||||
for (int k = 0; k < items.length; k++) {
|
||||
final TreeEntry e = items[k];
|
||||
final ObjectId id = e.getId();
|
||||
|
||||
if (id == null)
|
||||
throw new ObjectWritingException(MessageFormat.format(
|
||||
JGitText.get().objectAtPathDoesNotHaveId, e.getFullName()));
|
||||
|
||||
e.getMode().copyTo(o);
|
||||
o.write(' ');
|
||||
o.write(e.getNameUTF8());
|
||||
o.write(0);
|
||||
id.copyRawTo(o);
|
||||
public ObjectId writeTree(Tree tree) throws IOException {
|
||||
try {
|
||||
ObjectId id = inserter.insert(OBJ_TREE, inserter.format(tree));
|
||||
inserter.flush();
|
||||
return id;
|
||||
} finally {
|
||||
inserter.release();
|
||||
}
|
||||
return writeCanonicalTree(o.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a canonical tree to the object database.
|
||||
*
|
||||
* @param b
|
||||
* @param treeData
|
||||
* the canonical encoding of the tree object.
|
||||
* @return SHA-1 of the tree
|
||||
* @throws IOException
|
||||
*/
|
||||
public ObjectId writeCanonicalTree(final byte[] b) throws IOException {
|
||||
return writeTree(b.length, new ByteArrayInputStream(b));
|
||||
}
|
||||
|
||||
private ObjectId writeTree(final long len, final InputStream is)
|
||||
throws IOException {
|
||||
return writeObject(Constants.OBJ_TREE, len, is, true);
|
||||
public ObjectId writeCanonicalTree(byte[] treeData) throws IOException {
|
||||
try {
|
||||
ObjectId id = inserter.insert(OBJ_TREE, treeData);
|
||||
inserter.flush();
|
||||
return id;
|
||||
} finally {
|
||||
inserter.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a Commit to the object database
|
||||
*
|
||||
* @param c
|
||||
* @param commit
|
||||
* Commit to store
|
||||
* @return SHA-1 of the commit
|
||||
* @throws IOException
|
||||
*/
|
||||
public ObjectId writeCommit(final Commit c) throws IOException {
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
String encoding = c.getEncoding();
|
||||
if (encoding == null)
|
||||
encoding = Constants.CHARACTER_ENCODING;
|
||||
final OutputStreamWriter w = new OutputStreamWriter(os, encoding);
|
||||
|
||||
os.write(htree);
|
||||
os.write(' ');
|
||||
c.getTreeId().copyTo(os);
|
||||
os.write('\n');
|
||||
|
||||
ObjectId[] ps = c.getParentIds();
|
||||
for (int i=0; i<ps.length; ++i) {
|
||||
os.write(hparent);
|
||||
os.write(' ');
|
||||
ps[i].copyTo(os);
|
||||
os.write('\n');
|
||||
public ObjectId writeCommit(Commit commit) throws IOException {
|
||||
try {
|
||||
ObjectId id = inserter.insert(OBJ_COMMIT, inserter.format(commit));
|
||||
inserter.flush();
|
||||
return id;
|
||||
} finally {
|
||||
inserter.release();
|
||||
}
|
||||
|
||||
os.write(hauthor);
|
||||
os.write(' ');
|
||||
w.write(c.getAuthor().toExternalString());
|
||||
w.flush();
|
||||
os.write('\n');
|
||||
|
||||
os.write(hcommitter);
|
||||
os.write(' ');
|
||||
w.write(c.getCommitter().toExternalString());
|
||||
w.flush();
|
||||
os.write('\n');
|
||||
|
||||
if (!encoding.equals(Constants.CHARACTER_ENCODING)) {
|
||||
os.write(hencoding);
|
||||
os.write(' ');
|
||||
os.write(Constants.encodeASCII(encoding));
|
||||
os.write('\n');
|
||||
}
|
||||
|
||||
os.write('\n');
|
||||
w.write(c.getMessage());
|
||||
w.flush();
|
||||
|
||||
return writeCommit(os.toByteArray());
|
||||
}
|
||||
|
||||
private ObjectId writeTag(final byte[] b) throws IOException {
|
||||
return writeTag(b.length, new ByteArrayInputStream(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an annotated Tag to the object database
|
||||
*
|
||||
* @param c
|
||||
* @param tag
|
||||
* Tag
|
||||
* @return SHA-1 of the tag
|
||||
* @throws IOException
|
||||
*/
|
||||
public ObjectId writeTag(final Tag c) throws IOException {
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
final OutputStreamWriter w = new OutputStreamWriter(os,
|
||||
Constants.CHARSET);
|
||||
|
||||
w.write("object ");
|
||||
c.getObjId().copyTo(w);
|
||||
w.write('\n');
|
||||
|
||||
w.write("type ");
|
||||
w.write(c.getType());
|
||||
w.write("\n");
|
||||
|
||||
w.write("tag ");
|
||||
w.write(c.getTag());
|
||||
w.write("\n");
|
||||
|
||||
w.write("tagger ");
|
||||
w.write(c.getAuthor().toExternalString());
|
||||
w.write('\n');
|
||||
|
||||
w.write('\n');
|
||||
w.write(c.getMessage());
|
||||
w.close();
|
||||
|
||||
return writeTag(os.toByteArray());
|
||||
}
|
||||
|
||||
private ObjectId writeCommit(final byte[] b) throws IOException {
|
||||
return writeCommit(b.length, new ByteArrayInputStream(b));
|
||||
}
|
||||
|
||||
private ObjectId writeCommit(final long len, final InputStream is)
|
||||
throws IOException {
|
||||
return writeObject(Constants.OBJ_COMMIT, len, is, true);
|
||||
}
|
||||
|
||||
private ObjectId writeTag(final long len, final InputStream is)
|
||||
throws IOException {
|
||||
return writeObject(Constants.OBJ_TAG, len, is, true);
|
||||
public ObjectId writeTag(Tag tag) throws IOException {
|
||||
try {
|
||||
ObjectId id = inserter.insert(OBJ_TAG, inserter.format(tag));
|
||||
inserter.flush();
|
||||
return id;
|
||||
} finally {
|
||||
inserter.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the SHA-1 of a blob without creating an object. This is for
|
||||
* figuring out if we already have a blob or not.
|
||||
*
|
||||
* @param len number of bytes to consume
|
||||
* @param is stream for read blob data from
|
||||
* @param len
|
||||
* number of bytes to consume
|
||||
* @param is
|
||||
* stream for read blob data from
|
||||
* @return SHA-1 of a looked for blob
|
||||
* @throws IOException
|
||||
*/
|
||||
public ObjectId computeBlobSha1(final long len, final InputStream is)
|
||||
public ObjectId computeBlobSha1(long len, InputStream is)
|
||||
throws IOException {
|
||||
return writeObject(Constants.OBJ_BLOB, len, is, false);
|
||||
return computeObjectSha1(OBJ_BLOB, len, is);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -313,119 +229,12 @@ public ObjectId computeBlobSha1(final long len, final InputStream is)
|
|||
* @return SHA-1 of data combined with type information
|
||||
* @throws IOException
|
||||
*/
|
||||
public ObjectId computeObjectSha1(final int type, final long len, final InputStream is)
|
||||
public ObjectId computeObjectSha1(int type, long len, InputStream is)
|
||||
throws IOException {
|
||||
return writeObject(type, len, is, false);
|
||||
}
|
||||
|
||||
ObjectId writeObject(final int type, long len, final InputStream is,
|
||||
boolean store) throws IOException {
|
||||
final File t;
|
||||
final DeflaterOutputStream deflateStream;
|
||||
final FileOutputStream fileStream;
|
||||
ObjectId id = null;
|
||||
Deflater def = null;
|
||||
|
||||
if (store) {
|
||||
t = File.createTempFile("noz", null, r.getObjectsDirectory());
|
||||
fileStream = new FileOutputStream(t);
|
||||
} else {
|
||||
t = null;
|
||||
fileStream = null;
|
||||
}
|
||||
|
||||
md.reset();
|
||||
if (store) {
|
||||
def = new Deflater(r.getConfig().get(CoreConfig.KEY).getCompression());
|
||||
deflateStream = new DeflaterOutputStream(fileStream, def);
|
||||
} else
|
||||
deflateStream = null;
|
||||
|
||||
try {
|
||||
byte[] header;
|
||||
int n;
|
||||
|
||||
header = Constants.encodedTypeString(type);
|
||||
md.update(header);
|
||||
if (deflateStream != null)
|
||||
deflateStream.write(header);
|
||||
|
||||
md.update((byte) ' ');
|
||||
if (deflateStream != null)
|
||||
deflateStream.write((byte) ' ');
|
||||
|
||||
header = Constants.encodeASCII(len);
|
||||
md.update(header);
|
||||
if (deflateStream != null)
|
||||
deflateStream.write(header);
|
||||
|
||||
md.update((byte) 0);
|
||||
if (deflateStream != null)
|
||||
deflateStream.write((byte) 0);
|
||||
|
||||
while (len > 0
|
||||
&& (n = is.read(buf, 0, (int) Math.min(len, buf.length))) > 0) {
|
||||
md.update(buf, 0, n);
|
||||
if (deflateStream != null)
|
||||
deflateStream.write(buf, 0, n);
|
||||
len -= n;
|
||||
}
|
||||
|
||||
if (len != 0)
|
||||
throw new IOException("Input did not match supplied length. "
|
||||
+ len + " bytes are missing.");
|
||||
|
||||
if (deflateStream != null ) {
|
||||
deflateStream.close();
|
||||
if (t != null)
|
||||
t.setReadOnly();
|
||||
}
|
||||
|
||||
id = ObjectId.fromRaw(md.digest());
|
||||
return inserter.idFor(type, len, is);
|
||||
} finally {
|
||||
if (id == null && deflateStream != null) {
|
||||
try {
|
||||
deflateStream.close();
|
||||
} finally {
|
||||
t.delete();
|
||||
}
|
||||
}
|
||||
if (def != null) {
|
||||
def.end();
|
||||
}
|
||||
inserter.release();
|
||||
}
|
||||
|
||||
if (t == null)
|
||||
return id;
|
||||
|
||||
if (r.hasObject(id)) {
|
||||
// Object is already in the repository so remove
|
||||
// the temporary file.
|
||||
//
|
||||
t.delete();
|
||||
} else {
|
||||
final File o = r.toFile(id);
|
||||
if (!t.renameTo(o)) {
|
||||
// Maybe the directory doesn't exist yet as the object
|
||||
// directories are always lazily created. Note that we
|
||||
// try the rename first as the directory likely does exist.
|
||||
//
|
||||
o.getParentFile().mkdir();
|
||||
if (!t.renameTo(o)) {
|
||||
if (!r.hasObject(id)) {
|
||||
// The object failed to be renamed into its proper
|
||||
// location and it doesn't exist in the repository
|
||||
// either. We really don't know what went wrong, so
|
||||
// fail.
|
||||
//
|
||||
t.delete();
|
||||
throw new ObjectWritingException("Unable to"
|
||||
+ " create new object: " + o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,6 +148,11 @@ public File getDirectory() {
|
|||
*/
|
||||
public abstract ObjectDatabase getObjectDatabase();
|
||||
|
||||
/** @return a new inserter to create objects in {@link #getObjectDatabase()} */
|
||||
public ObjectInserter newObjectInserter() {
|
||||
return getObjectDatabase().newInserter();
|
||||
}
|
||||
|
||||
/** @return the reference database which stores the reference namespace. */
|
||||
public abstract RefDatabase getRefDatabase();
|
||||
|
||||
|
|
|
@ -42,13 +42,12 @@
|
|||
*/
|
||||
package org.eclipse.jgit.util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectInserter;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectWriter;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
|
||||
/**
|
||||
|
@ -113,12 +112,8 @@ public static ObjectId computeChangeId(final ObjectId treeId,
|
|||
b.append(committer.toExternalString());
|
||||
b.append("\n\n");
|
||||
b.append(cleanMessage);
|
||||
ObjectWriter w = new ObjectWriter(null);
|
||||
byte[] bytes = b.toString().getBytes(Constants.CHARACTER_ENCODING);
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
|
||||
ObjectId sha1 = w.computeObjectSha1(Constants.OBJ_COMMIT, bytes.length,
|
||||
is);
|
||||
return sha1;
|
||||
return new ObjectInserter.Formatter().idFor(Constants.OBJ_COMMIT, //
|
||||
b.toString().getBytes(Constants.CHARACTER_ENCODING));
|
||||
}
|
||||
|
||||
private static final Pattern issuePattern = Pattern
|
||||
|
|
Loading…
Reference in New Issue