2 * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials provided
17 * with the distribution.
19 * - Neither the name of the Git Development Community nor the
20 * names of its contributors may be used to endorse or promote
21 * products derived from this software without specific prior
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
25 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 package org
.spearce
.jgit
.lib
;
41 import java
.io
.ByteArrayInputStream
;
42 import java
.io
.ByteArrayOutputStream
;
44 import java
.io
.FileInputStream
;
45 import java
.io
.FileOutputStream
;
46 import java
.io
.IOException
;
47 import java
.io
.InputStream
;
48 import java
.io
.OutputStreamWriter
;
49 import java
.security
.MessageDigest
;
50 import java
.util
.zip
.Deflater
;
51 import java
.util
.zip
.DeflaterOutputStream
;
53 import org
.spearce
.jgit
.errors
.ObjectWritingException
;
56 * A class for writing loose objects.
58 public class ObjectWriter
{
59 private static final byte[] htree
= Constants
.encodeASCII("tree");
61 private static final byte[] hparent
= Constants
.encodeASCII("parent");
63 private static final byte[] hauthor
= Constants
.encodeASCII("author");
65 private static final byte[] hcommitter
= Constants
.encodeASCII("committer");
67 private static final byte[] hencoding
= Constants
.encodeASCII("encoding");
69 private final Repository r
;
71 private final byte[] buf
;
73 private final MessageDigest md
;
75 private final Deflater def
;
78 * Construct an Object writer for the specified repository
81 public ObjectWriter(final Repository d
) {
84 md
= Constants
.newMessageDigest();
85 def
= new Deflater(r
.getConfig().getCore().getCompression());
89 * Write a blob with the specified data
91 * @param b bytes of the blob
92 * @return SHA-1 of the blob
95 public ObjectId
writeBlob(final byte[] b
) throws IOException
{
96 return writeBlob(b
.length
, new ByteArrayInputStream(b
));
100 * Write a blob with the data in the specified file
103 * a file containing blob data
104 * @return SHA-1 of the blob
105 * @throws IOException
107 public ObjectId
writeBlob(final File f
) throws IOException
{
108 final FileInputStream is
= new FileInputStream(f
);
110 return writeBlob(f
.length(), is
);
117 * Write a blob with data from a stream
120 * number of bytes to consume from the stream
122 * stream with blob data
123 * @return SHA-1 of the blob
124 * @throws IOException
126 public ObjectId
writeBlob(final long len
, final InputStream is
)
128 return writeObject(Constants
.OBJ_BLOB
, len
, is
, true);
132 * Write a Tree to the object database.
136 * @return SHA-1 of the tree
137 * @throws IOException
139 public ObjectId
writeTree(final Tree t
) throws IOException
{
140 final ByteArrayOutputStream o
= new ByteArrayOutputStream();
141 final TreeEntry
[] items
= t
.members();
142 for (int k
= 0; k
< items
.length
; k
++) {
143 final TreeEntry e
= items
[k
];
144 final ObjectId id
= e
.getId();
147 throw new ObjectWritingException("Object at path \""
148 + e
.getFullName() + "\" does not have an id assigned."
149 + " All object ids must be assigned prior"
150 + " to writing a tree.");
152 e
.getMode().copyTo(o
);
154 o
.write(e
.getNameUTF8());
158 return writeTree(o
.toByteArray());
161 private ObjectId
writeTree(final byte[] b
) throws IOException
{
162 return writeTree(b
.length
, new ByteArrayInputStream(b
));
165 private ObjectId
writeTree(final long len
, final InputStream is
)
167 return writeObject(Constants
.OBJ_TREE
, len
, is
, true);
171 * Write a Commit to the object database
175 * @return SHA-1 of the commit
176 * @throws IOException
178 public ObjectId
writeCommit(final Commit c
) throws IOException
{
179 final ByteArrayOutputStream os
= new ByteArrayOutputStream();
180 String encoding
= c
.getEncoding();
181 if (encoding
== null)
182 encoding
= Constants
.CHARACTER_ENCODING
;
183 final OutputStreamWriter w
= new OutputStreamWriter(os
, encoding
);
187 c
.getTreeId().copyTo(os
);
190 ObjectId
[] ps
= c
.getParentIds();
191 for (int i
=0; i
<ps
.length
; ++i
) {
200 w
.write(c
.getAuthor().toExternalString());
204 os
.write(hcommitter
);
206 w
.write(c
.getCommitter().toExternalString());
210 if (!encoding
.equals("UTF-8")) {
213 os
.write(Constants
.encodeASCII(encoding
));
218 w
.write(c
.getMessage());
221 return writeCommit(os
.toByteArray());
224 private ObjectId
writeTag(final byte[] b
) throws IOException
{
225 return writeTag(b
.length
, new ByteArrayInputStream(b
));
229 * Write an annotated Tag to the object database
233 * @return SHA-1 of the tag
234 * @throws IOException
236 public ObjectId
writeTag(final Tag c
) throws IOException
{
237 final ByteArrayOutputStream os
= new ByteArrayOutputStream();
238 final OutputStreamWriter w
= new OutputStreamWriter(os
,
242 c
.getObjId().copyTo(w
);
246 w
.write(c
.getType());
254 w
.write(c
.getAuthor().toExternalString());
258 w
.write(c
.getMessage());
261 return writeTag(os
.toByteArray());
264 private ObjectId
writeCommit(final byte[] b
) throws IOException
{
265 return writeCommit(b
.length
, new ByteArrayInputStream(b
));
268 private ObjectId
writeCommit(final long len
, final InputStream is
)
270 return writeObject(Constants
.OBJ_COMMIT
, len
, is
, true);
273 private ObjectId
writeTag(final long len
, final InputStream is
)
275 return writeObject(Constants
.OBJ_TAG
, len
, is
, true);
279 * Compute the SHA-1 of a blob without creating an object. This is for
280 * figuring out if we already have a blob or not.
282 * @param len number of bytes to consume
283 * @param is stream for read blob data from
284 * @return SHA-1 of a looked for blob
285 * @throws IOException
287 public ObjectId
computeBlobSha1(final long len
, final InputStream is
)
289 return writeObject(Constants
.OBJ_BLOB
, len
, is
, false);
292 @SuppressWarnings("null")
293 ObjectId
writeObject(final int type
, long len
, final InputStream is
,
294 boolean store
) throws IOException
{
296 final DeflaterOutputStream deflateStream
;
297 final FileOutputStream fileStream
;
301 t
= File
.createTempFile("noz", null, r
.getObjectsDirectory());
302 fileStream
= new FileOutputStream(t
);
311 deflateStream
= new DeflaterOutputStream(fileStream
, def
);
313 deflateStream
= null;
319 header
= Constants
.encodedTypeString(type
);
321 if (deflateStream
!= null)
322 deflateStream
.write(header
);
324 md
.update((byte) ' ');
325 if (deflateStream
!= null)
326 deflateStream
.write((byte) ' ');
328 header
= Constants
.encodeASCII(len
);
330 if (deflateStream
!= null)
331 deflateStream
.write(header
);
334 if (deflateStream
!= null)
335 deflateStream
.write((byte) 0);
338 && (n
= is
.read(buf
, 0, (int) Math
.min(len
, buf
.length
))) > 0) {
339 md
.update(buf
, 0, n
);
340 if (deflateStream
!= null)
341 deflateStream
.write(buf
, 0, n
);
346 throw new IOException("Input did not match supplied length. "
347 + len
+ " bytes are missing.");
349 if (deflateStream
!= null ) {
350 deflateStream
.close();
355 id
= ObjectId
.fromRaw(md
.digest());
357 if (id
== null && deflateStream
!= null) {
359 deflateStream
.close();
369 if (r
.hasObject(id
)) {
370 // Object is already in the repository so remove
371 // the temporary file.
375 final File o
= r
.toFile(id
);
376 if (!t
.renameTo(o
)) {
377 // Maybe the directory doesn't exist yet as the object
378 // directories are always lazily created. Note that we
379 // try the rename first as the directory likely does exist.
381 o
.getParentFile().mkdir();
382 if (!t
.renameTo(o
)) {
383 if (!r
.hasObject(id
)) {
384 // The object failed to be renamed into its proper
385 // location and it doesn't exist in the repository
386 // either. We really don't know what went wrong, so
390 throw new ObjectWritingException("Unable to"
391 + " create new object: " + o
);