ObjectDirectoryInserter.java

  1. /*
  2.  * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
  3.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  4.  * Copyright (C) 2009, Google Inc. and others
  5.  *
  6.  * This program and the accompanying materials are made available under the
  7.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  8.  * https://www.eclipse.org/org/documents/edl-v10.php.
  9.  *
  10.  * SPDX-License-Identifier: BSD-3-Clause
  11.  */

  12. package org.eclipse.jgit.internal.storage.file;

  13. import java.io.EOFException;
  14. import java.io.File;
  15. import java.io.FileOutputStream;
  16. import java.io.FilterOutputStream;
  17. import java.io.IOException;
  18. import java.io.InputStream;
  19. import java.io.OutputStream;
  20. import java.nio.channels.Channels;
  21. import java.text.MessageFormat;
  22. import java.util.zip.Deflater;
  23. import java.util.zip.DeflaterOutputStream;

  24. import org.eclipse.jgit.errors.ObjectWritingException;
  25. import org.eclipse.jgit.internal.JGitText;
  26. import org.eclipse.jgit.lib.Config;
  27. import org.eclipse.jgit.lib.Constants;
  28. import org.eclipse.jgit.lib.ObjectId;
  29. import org.eclipse.jgit.lib.ObjectInserter;
  30. import org.eclipse.jgit.lib.ObjectReader;
  31. import org.eclipse.jgit.transport.PackParser;
  32. import org.eclipse.jgit.util.FileUtils;
  33. import org.eclipse.jgit.util.IO;
  34. import org.eclipse.jgit.util.sha1.SHA1;

  35. /** Creates loose objects in a {@link ObjectDirectory}. */
  36. class ObjectDirectoryInserter extends ObjectInserter {
  37.     private final FileObjectDatabase db;

  38.     private final WriteConfig config;

  39.     private Deflater deflate;

  40.     ObjectDirectoryInserter(FileObjectDatabase dest, Config cfg) {
  41.         db = dest;
  42.         config = cfg.get(WriteConfig.KEY);
  43.     }

  44.     /** {@inheritDoc} */
  45.     @Override
  46.     public ObjectId insert(int type, byte[] data, int off, int len)
  47.             throws IOException {
  48.         return insert(type, data, off, len, false);
  49.     }

  50.     /**
  51.      * Insert a loose object into the database. If createDuplicate is true,
  52.      * write the loose object even if we already have it in the loose or packed
  53.      * ODB.
  54.      *
  55.      * @param type
  56.      * @param data
  57.      * @param off
  58.      * @param len
  59.      * @param createDuplicate
  60.      * @return ObjectId
  61.      * @throws IOException
  62.      */
  63.     private ObjectId insert(
  64.             int type, byte[] data, int off, int len, boolean createDuplicate)
  65.             throws IOException {
  66.         ObjectId id = idFor(type, data, off, len);
  67.         if (!createDuplicate && db.has(id)) {
  68.             return id;
  69.         }
  70.         File tmp = toTemp(type, data, off, len);
  71.         return insertOneObject(tmp, id, createDuplicate);
  72.     }

  73.     /** {@inheritDoc} */
  74.     @Override
  75.     public ObjectId insert(int type, long len, InputStream is)
  76.             throws IOException {
  77.         return insert(type, len, is, false);
  78.     }

  79.     /**
  80.      * Insert a loose object into the database. If createDuplicate is true,
  81.      * write the loose object even if we already have it in the loose or packed
  82.      * ODB.
  83.      *
  84.      * @param type
  85.      * @param len
  86.      * @param is
  87.      * @param createDuplicate
  88.      * @return ObjectId
  89.      * @throws IOException
  90.      */
  91.     ObjectId insert(int type, long len, InputStream is, boolean createDuplicate)
  92.             throws IOException {
  93.         if (len <= buffer().length) {
  94.             byte[] buf = buffer();
  95.             int actLen = IO.readFully(is, buf, 0);
  96.             return insert(type, buf, 0, actLen, createDuplicate);

  97.         }
  98.         SHA1 md = digest();
  99.         File tmp = toTemp(md, type, len, is);
  100.         ObjectId id = md.toObjectId();
  101.         return insertOneObject(tmp, id, createDuplicate);
  102.     }

  103.     private ObjectId insertOneObject(
  104.             File tmp, ObjectId id, boolean createDuplicate)
  105.             throws IOException {
  106.         switch (db.insertUnpackedObject(tmp, id, createDuplicate)) {
  107.         case INSERTED:
  108.         case EXISTS_PACKED:
  109.         case EXISTS_LOOSE:
  110.             return id;

  111.         case FAILURE:
  112.         default:
  113.             break;
  114.         }

  115.         final File dst = db.fileFor(id);
  116.         throw new ObjectWritingException(MessageFormat
  117.                 .format(JGitText.get().unableToCreateNewObject, dst));
  118.     }

  119.     /** {@inheritDoc} */
  120.     @Override
  121.     public PackParser newPackParser(InputStream in) throws IOException {
  122.         return new ObjectDirectoryPackParser(db, in);
  123.     }

  124.     /** {@inheritDoc} */
  125.     @Override
  126.     public ObjectReader newReader() {
  127.         return new WindowCursor(db, this);
  128.     }

  129.     /** {@inheritDoc} */
  130.     @Override
  131.     public void flush() throws IOException {
  132.         // Do nothing. Loose objects are immediately visible.
  133.     }

  134.     /** {@inheritDoc} */
  135.     @Override
  136.     public void close() {
  137.         if (deflate != null) {
  138.             try {
  139.                 deflate.end();
  140.             } finally {
  141.                 deflate = null;
  142.             }
  143.         }
  144.     }

  145.     private File toTemp(final SHA1 md, final int type, long len,
  146.             final InputStream is) throws IOException {
  147.         boolean delete = true;
  148.         File tmp = newTempFile();
  149.         try (FileOutputStream fOut = new FileOutputStream(tmp)) {
  150.             try {
  151.                 OutputStream out = fOut;
  152.                 if (config.getFSyncObjectFiles()) {
  153.                     out = Channels.newOutputStream(fOut.getChannel());
  154.                 }
  155.                 DeflaterOutputStream cOut = compress(out);
  156.                 SHA1OutputStream dOut = new SHA1OutputStream(cOut, md);
  157.                 writeHeader(dOut, type, len);

  158.                 final byte[] buf = buffer();
  159.                 while (len > 0) {
  160.                     int n = is.read(buf, 0, (int) Math.min(len, buf.length));
  161.                     if (n <= 0) {
  162.                         throw shortInput(len);
  163.                     }
  164.                     dOut.write(buf, 0, n);
  165.                     len -= n;
  166.                 }
  167.                 dOut.flush();
  168.                 cOut.finish();
  169.             } finally {
  170.                 if (config.getFSyncObjectFiles()) {
  171.                     fOut.getChannel().force(true);
  172.                 }
  173.             }

  174.             delete = false;
  175.             return tmp;
  176.         } finally {
  177.             if (delete) {
  178.                 FileUtils.delete(tmp, FileUtils.RETRY);
  179.             }
  180.         }
  181.     }

  182.     private File toTemp(final int type, final byte[] buf, final int pos,
  183.             final int len) throws IOException {
  184.         boolean delete = true;
  185.         File tmp = newTempFile();
  186.         try (FileOutputStream fOut = new FileOutputStream(tmp)) {
  187.             try {
  188.                 OutputStream out = fOut;
  189.                 if (config.getFSyncObjectFiles()) {
  190.                     out = Channels.newOutputStream(fOut.getChannel());
  191.                 }
  192.                 DeflaterOutputStream cOut = compress(out);
  193.                 writeHeader(cOut, type, len);
  194.                 cOut.write(buf, pos, len);
  195.                 cOut.finish();
  196.             } finally {
  197.                 if (config.getFSyncObjectFiles()) {
  198.                     fOut.getChannel().force(true);
  199.                 }
  200.             }
  201.             delete = false;
  202.             return tmp;
  203.         } finally {
  204.             if (delete) {
  205.                 FileUtils.delete(tmp, FileUtils.RETRY);
  206.             }
  207.         }
  208.     }

  209.     void writeHeader(OutputStream out, int type, long len)
  210.             throws IOException {
  211.         out.write(Constants.encodedTypeString(type));
  212.         out.write((byte) ' ');
  213.         out.write(Constants.encodeASCII(len));
  214.         out.write((byte) 0);
  215.     }

  216.     File newTempFile() throws IOException {
  217.         return File.createTempFile("noz", null, db.getDirectory()); //$NON-NLS-1$
  218.     }

  219.     DeflaterOutputStream compress(OutputStream out) {
  220.         if (deflate == null)
  221.             deflate = new Deflater(config.getCompression());
  222.         else
  223.             deflate.reset();
  224.         return new DeflaterOutputStream(out, deflate, 8192);
  225.     }

  226.     private static EOFException shortInput(long missing) {
  227.         return new EOFException(MessageFormat.format(
  228.                 JGitText.get().inputDidntMatchLength, Long.valueOf(missing)));
  229.     }

  230.     private static class SHA1OutputStream extends FilterOutputStream {
  231.         private final SHA1 md;

  232.         SHA1OutputStream(OutputStream out, SHA1 md) {
  233.             super(out);
  234.             this.md = md;
  235.         }

  236.         @Override
  237.         public void write(int b) throws IOException {
  238.             md.update((byte) b);
  239.             out.write(b);
  240.         }

  241.         @Override
  242.         public void write(byte[] in, int p, int n) throws IOException {
  243.             md.update(in, p, n);
  244.             out.write(in, p, n);
  245.         }
  246.     }
  247. }