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;
|
package org.eclipse.jgit.lib;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
@ -125,11 +123,9 @@ private Tree buildTree(HashMap<String, String> headEntries) throws IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectId genSha1(String data) {
|
ObjectId genSha1(String data) {
|
||||||
InputStream is = new ByteArrayInputStream(data.getBytes());
|
|
||||||
ObjectWriter objectWriter = new ObjectWriter(db);
|
ObjectWriter objectWriter = new ObjectWriter(db);
|
||||||
try {
|
try {
|
||||||
return objectWriter.writeObject(Constants.OBJ_BLOB, data
|
return objectWriter.writeBlob(data.getBytes());
|
||||||
.getBytes().length, is, true);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
fail(e.toString());
|
fail(e.toString());
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,11 @@ public void create() throws IOException {
|
||||||
repository.create();
|
repository.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObjectInserter newInserter() {
|
||||||
|
return odb.newInserter();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean exists() {
|
public boolean exists() {
|
||||||
return odb.exists();
|
return odb.exists();
|
||||||
|
|
|
@ -129,4 +129,9 @@ public ObjectDatabase newCachedDatabase() {
|
||||||
// The situation might become even more tricky if we will consider alternates.
|
// The situation might become even more tricky if we will consider alternates.
|
||||||
return wrapped.newCachedDatabase();
|
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
|
* @return The compression level to use when storing loose objects
|
||||||
*/
|
*/
|
||||||
public int getCompression() {
|
public int getCompression() {
|
||||||
|
|
|
@ -249,12 +249,17 @@ public FileRepository(final File d, final File workTree, final File objectDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
refs = new RefDirectory(this);
|
refs = new RefDirectory(this);
|
||||||
if (objectDir != null)
|
if (objectDir != null) {
|
||||||
objectDatabase = new ObjectDirectory(fs.resolve(objectDir, ""),
|
objectDatabase = new ObjectDirectory(repoConfig, //
|
||||||
alternateObjectDir, fs);
|
fs.resolve(objectDir, ""), //
|
||||||
else
|
alternateObjectDir, //
|
||||||
objectDatabase = new ObjectDirectory(fs.resolve(gitDir, "objects"),
|
fs);
|
||||||
alternateObjectDir, fs);
|
} else {
|
||||||
|
objectDatabase = new ObjectDirectory(repoConfig, //
|
||||||
|
fs.resolve(gitDir, "objects"), //
|
||||||
|
alternateObjectDir, //
|
||||||
|
fs);
|
||||||
|
}
|
||||||
|
|
||||||
if (indexFile != null)
|
if (indexFile != null)
|
||||||
this.indexFile = indexFile;
|
this.indexFile = indexFile;
|
||||||
|
|
|
@ -91,6 +91,17 @@ public void create() throws IOException {
|
||||||
// Assume no action is required.
|
// 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.
|
* Close any resources held by this database and its active alternates.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -76,6 +76,8 @@
|
||||||
public class ObjectDirectory extends ObjectDatabase {
|
public class ObjectDirectory extends ObjectDatabase {
|
||||||
private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]);
|
private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]);
|
||||||
|
|
||||||
|
private final Config config;
|
||||||
|
|
||||||
private final File objects;
|
private final File objects;
|
||||||
|
|
||||||
private final File infoDirectory;
|
private final File infoDirectory;
|
||||||
|
@ -93,6 +95,8 @@ public class ObjectDirectory extends ObjectDatabase {
|
||||||
/**
|
/**
|
||||||
* Initialize a reference to an on-disk object directory.
|
* Initialize a reference to an on-disk object directory.
|
||||||
*
|
*
|
||||||
|
* @param cfg
|
||||||
|
* configuration this directory consults for write settings.
|
||||||
* @param dir
|
* @param dir
|
||||||
* the location of the <code>objects</code> directory.
|
* the location of the <code>objects</code> directory.
|
||||||
* @param alternateObjectDir
|
* @param alternateObjectDir
|
||||||
|
@ -101,7 +105,8 @@ public class ObjectDirectory extends ObjectDatabase {
|
||||||
* the file system abstraction which will be necessary to
|
* the file system abstraction which will be necessary to
|
||||||
* perform certain file system operations.
|
* 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;
|
objects = dir;
|
||||||
this.alternateObjectDir = alternateObjectDir;
|
this.alternateObjectDir = alternateObjectDir;
|
||||||
infoDirectory = new File(objects, "info");
|
infoDirectory = new File(objects, "info");
|
||||||
|
@ -130,6 +135,11 @@ public void create() throws IOException {
|
||||||
packDirectory.mkdir();
|
packDirectory.mkdir();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObjectInserter newInserter() {
|
||||||
|
return new ObjectDirectoryInserter(this, config);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void closeSelf() {
|
public void closeSelf() {
|
||||||
final PackList packs = packList.get();
|
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));
|
final Repository db = RepositoryCache.open(FileKey.exact(parent, fs));
|
||||||
return new AlternateRepositoryDatabase(db);
|
return new AlternateRepositoryDatabase(db);
|
||||||
}
|
}
|
||||||
return new ObjectDirectory(objdir, null, fs);
|
return new ObjectDirectory(config, objdir, null, fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class PackList {
|
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) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
|
||||||
* Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
|
* 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.
|
* and other copyright owners as documented in the project's IP log.
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available
|
* This program and the accompanying materials are made available
|
||||||
|
@ -44,61 +45,49 @@
|
||||||
|
|
||||||
package org.eclipse.jgit.lib;
|
package org.eclipse.jgit.lib;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
||||||
import java.io.ByteArrayOutputStream;
|
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.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
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.
|
* A class for writing loose objects.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link Repository#newObjectInserter()}.
|
||||||
*/
|
*/
|
||||||
public class ObjectWriter {
|
public class ObjectWriter {
|
||||||
private static final byte[] htree = Constants.encodeASCII("tree");
|
private final ObjectInserter inserter;
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an Object writer for the specified repository
|
* Construct an Object writer for the specified repository
|
||||||
|
*
|
||||||
* @param d
|
* @param d
|
||||||
*/
|
*/
|
||||||
public ObjectWriter(final Repository d) {
|
public ObjectWriter(final Repository d) {
|
||||||
r = d;
|
inserter = d.newObjectInserter();
|
||||||
buf = new byte[8192];
|
|
||||||
md = Constants.newMessageDigest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write a blob with the specified data
|
* 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
|
* @return SHA-1 of the blob
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public ObjectId writeBlob(final byte[] b) 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)
|
public ObjectId writeBlob(final long len, final InputStream is)
|
||||||
throws IOException {
|
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.
|
* Write a Tree to the object database.
|
||||||
*
|
*
|
||||||
* @param t
|
* @param tree
|
||||||
* Tree
|
* Tree
|
||||||
* @return SHA-1 of the tree
|
* @return SHA-1 of the tree
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public ObjectId writeTree(final Tree t) throws IOException {
|
public ObjectId writeTree(Tree tree) throws IOException {
|
||||||
final ByteArrayOutputStream o = new ByteArrayOutputStream();
|
try {
|
||||||
final TreeEntry[] items = t.members();
|
ObjectId id = inserter.insert(OBJ_TREE, inserter.format(tree));
|
||||||
for (int k = 0; k < items.length; k++) {
|
inserter.flush();
|
||||||
final TreeEntry e = items[k];
|
return id;
|
||||||
final ObjectId id = e.getId();
|
} finally {
|
||||||
|
inserter.release();
|
||||||
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 writeCanonicalTree(o.toByteArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write a canonical tree to the object database.
|
* Write a canonical tree to the object database.
|
||||||
*
|
*
|
||||||
* @param b
|
* @param treeData
|
||||||
* the canonical encoding of the tree object.
|
* the canonical encoding of the tree object.
|
||||||
* @return SHA-1 of the tree
|
* @return SHA-1 of the tree
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public ObjectId writeCanonicalTree(final byte[] b) throws IOException {
|
public ObjectId writeCanonicalTree(byte[] treeData) throws IOException {
|
||||||
return writeTree(b.length, new ByteArrayInputStream(b));
|
try {
|
||||||
|
ObjectId id = inserter.insert(OBJ_TREE, treeData);
|
||||||
|
inserter.flush();
|
||||||
|
return id;
|
||||||
|
} finally {
|
||||||
|
inserter.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectId writeTree(final long len, final InputStream is)
|
|
||||||
throws IOException {
|
|
||||||
return writeObject(Constants.OBJ_TREE, len, is, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write a Commit to the object database
|
* Write a Commit to the object database
|
||||||
*
|
*
|
||||||
* @param c
|
* @param commit
|
||||||
* Commit to store
|
* Commit to store
|
||||||
* @return SHA-1 of the commit
|
* @return SHA-1 of the commit
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public ObjectId writeCommit(final Commit c) throws IOException {
|
public ObjectId writeCommit(Commit commit) throws IOException {
|
||||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
try {
|
||||||
String encoding = c.getEncoding();
|
ObjectId id = inserter.insert(OBJ_COMMIT, inserter.format(commit));
|
||||||
if (encoding == null)
|
inserter.flush();
|
||||||
encoding = Constants.CHARACTER_ENCODING;
|
return id;
|
||||||
final OutputStreamWriter w = new OutputStreamWriter(os, encoding);
|
} finally {
|
||||||
|
inserter.release();
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
* Write an annotated Tag to the object database
|
||||||
*
|
*
|
||||||
* @param c
|
* @param tag
|
||||||
* Tag
|
* Tag
|
||||||
* @return SHA-1 of the tag
|
* @return SHA-1 of the tag
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public ObjectId writeTag(final Tag c) throws IOException {
|
public ObjectId writeTag(Tag tag) throws IOException {
|
||||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
try {
|
||||||
final OutputStreamWriter w = new OutputStreamWriter(os,
|
ObjectId id = inserter.insert(OBJ_TAG, inserter.format(tag));
|
||||||
Constants.CHARSET);
|
inserter.flush();
|
||||||
|
return id;
|
||||||
w.write("object ");
|
} finally {
|
||||||
c.getObjId().copyTo(w);
|
inserter.release();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the SHA-1 of a blob without creating an object. This is for
|
* Compute the SHA-1 of a blob without creating an object. This is for
|
||||||
* figuring out if we already have a blob or not.
|
* figuring out if we already have a blob or not.
|
||||||
*
|
*
|
||||||
* @param len number of bytes to consume
|
* @param len
|
||||||
* @param is stream for read blob data from
|
* number of bytes to consume
|
||||||
|
* @param is
|
||||||
|
* stream for read blob data from
|
||||||
* @return SHA-1 of a looked for blob
|
* @return SHA-1 of a looked for blob
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public ObjectId computeBlobSha1(final long len, final InputStream is)
|
public ObjectId computeBlobSha1(long len, InputStream is)
|
||||||
throws IOException {
|
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
|
* @return SHA-1 of data combined with type information
|
||||||
* @throws IOException
|
* @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 {
|
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 {
|
try {
|
||||||
byte[] header;
|
return inserter.idFor(type, len, is);
|
||||||
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());
|
|
||||||
} finally {
|
} finally {
|
||||||
if (id == null && deflateStream != null) {
|
inserter.release();
|
||||||
try {
|
|
||||||
deflateStream.close();
|
|
||||||
} finally {
|
|
||||||
t.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (def != null) {
|
|
||||||
def.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
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. */
|
/** @return the reference database which stores the reference namespace. */
|
||||||
public abstract RefDatabase getRefDatabase();
|
public abstract RefDatabase getRefDatabase();
|
||||||
|
|
||||||
|
|
|
@ -42,13 +42,12 @@
|
||||||
*/
|
*/
|
||||||
package org.eclipse.jgit.util;
|
package org.eclipse.jgit.util;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectWriter;
|
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -113,12 +112,8 @@ public static ObjectId computeChangeId(final ObjectId treeId,
|
||||||
b.append(committer.toExternalString());
|
b.append(committer.toExternalString());
|
||||||
b.append("\n\n");
|
b.append("\n\n");
|
||||||
b.append(cleanMessage);
|
b.append(cleanMessage);
|
||||||
ObjectWriter w = new ObjectWriter(null);
|
return new ObjectInserter.Formatter().idFor(Constants.OBJ_COMMIT, //
|
||||||
byte[] bytes = b.toString().getBytes(Constants.CHARACTER_ENCODING);
|
b.toString().getBytes(Constants.CHARACTER_ENCODING));
|
||||||
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
|
|
||||||
ObjectId sha1 = w.computeObjectSha1(Constants.OBJ_COMMIT, bytes.length,
|
|
||||||
is);
|
|
||||||
return sha1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Pattern issuePattern = Pattern
|
private static final Pattern issuePattern = Pattern
|
||||||
|
|
Loading…
Reference in New Issue