pickaxe: use textconv for -S counting
[gitweb.git] / archive-zip.c
index 02d1f3787acd8d6583073458ab7c8946684f9371..f5af81f904df081002dad46a71be2eca8e3bebab 100644 (file)
@@ -3,6 +3,7 @@
  */
 #include "cache.h"
 #include "archive.h"
+#include "streaming.h"
 
 static int zip_date;
 static int zip_time;
@@ -15,6 +16,7 @@ static unsigned int zip_dir_offset;
 static unsigned int zip_dir_entries;
 
 #define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024)
+#define ZIP_STREAM (8)
 
 struct zip_local_header {
        unsigned char magic[4];
@@ -31,6 +33,14 @@ struct zip_local_header {
        unsigned char _end[1];
 };
 
+struct zip_data_desc {
+       unsigned char magic[4];
+       unsigned char crc32[4];
+       unsigned char compressed_size[4];
+       unsigned char size[4];
+       unsigned char _end[1];
+};
+
 struct zip_dir_header {
        unsigned char magic[4];
        unsigned char creator_version[2];
@@ -70,6 +80,7 @@ struct zip_dir_trailer {
  * we're interested in.
  */
 #define ZIP_LOCAL_HEADER_SIZE  offsetof(struct zip_local_header, _end)
+#define ZIP_DATA_DESC_SIZE     offsetof(struct zip_data_desc, _end)
 #define ZIP_DIR_HEADER_SIZE    offsetof(struct zip_dir_header, _end)
 #define ZIP_DIR_TRAILER_SIZE   offsetof(struct zip_dir_trailer, _end)
 
@@ -120,20 +131,59 @@ static void *zlib_deflate(void *data, unsigned long size,
        return buffer;
 }
 
+static void write_zip_data_desc(unsigned long size,
+                               unsigned long compressed_size,
+                               unsigned long crc)
+{
+       struct zip_data_desc trailer;
+
+       copy_le32(trailer.magic, 0x08074b50);
+       copy_le32(trailer.crc32, crc);
+       copy_le32(trailer.compressed_size, compressed_size);
+       copy_le32(trailer.size, size);
+       write_or_die(1, &trailer, ZIP_DATA_DESC_SIZE);
+}
+
+static void set_zip_dir_data_desc(struct zip_dir_header *header,
+                                 unsigned long size,
+                                 unsigned long compressed_size,
+                                 unsigned long crc)
+{
+       copy_le32(header->crc32, crc);
+       copy_le32(header->compressed_size, compressed_size);
+       copy_le32(header->size, size);
+}
+
+static void set_zip_header_data_desc(struct zip_local_header *header,
+                                    unsigned long size,
+                                    unsigned long compressed_size,
+                                    unsigned long crc)
+{
+       copy_le32(header->crc32, crc);
+       copy_le32(header->compressed_size, compressed_size);
+       copy_le32(header->size, size);
+}
+
+#define STREAM_BUFFER_SIZE (1024 * 16)
+
 static int write_zip_entry(struct archiver_args *args,
-               const unsigned char *sha1, const char *path, size_t pathlen,
-               unsigned int mode, void *buffer, unsigned long size)
+                          const unsigned char *sha1,
+                          const char *path, size_t pathlen,
+                          unsigned int mode)
 {
        struct zip_local_header header;
        struct zip_dir_header dirent;
        unsigned long attr2;
        unsigned long compressed_size;
-       unsigned long uncompressed_size;
        unsigned long crc;
        unsigned long direntsize;
        int method;
        unsigned char *out;
        void *deflated = NULL;
+       void *buffer;
+       struct git_istream *stream = NULL;
+       unsigned long flags = 0;
+       unsigned long size;
 
        crc = crc32(0, NULL, 0);
 
@@ -146,24 +196,43 @@ static int write_zip_entry(struct archiver_args *args,
                method = 0;
                attr2 = 16;
                out = NULL;
-               uncompressed_size = 0;
+               size = 0;
                compressed_size = 0;
+               buffer = NULL;
+               size = 0;
        } else if (S_ISREG(mode) || S_ISLNK(mode)) {
+               enum object_type type = sha1_object_info(sha1, &size);
+
                method = 0;
                attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) :
                        (mode & 0111) ? ((mode) << 16) : 0;
-               if (S_ISREG(mode) && args->compression_level != 0)
+               if (S_ISREG(mode) && args->compression_level != 0 && size > 0)
                        method = 8;
-               crc = crc32(crc, buffer, size);
-               out = buffer;
-               uncompressed_size = size;
                compressed_size = size;
+
+               if (S_ISREG(mode) && type == OBJ_BLOB && !args->convert &&
+                   size > big_file_threshold) {
+                       stream = open_istream(sha1, &type, &size, NULL);
+                       if (!stream)
+                               return error("cannot stream blob %s",
+                                            sha1_to_hex(sha1));
+                       flags |= ZIP_STREAM;
+                       out = buffer = NULL;
+               } else {
+                       buffer = sha1_file_to_archive(args, path, sha1, mode,
+                                                     &type, &size);
+                       if (!buffer)
+                               return error("cannot read %s",
+                                            sha1_to_hex(sha1));
+                       crc = crc32(crc, buffer, size);
+                       out = buffer;
+               }
        } else {
                return error("unsupported file mode: 0%o (SHA1: %s)", mode,
                                sha1_to_hex(sha1));
        }
 
-       if (method == 8) {
+       if (buffer && method == 8) {
                deflated = zlib_deflate(buffer, size, args->compression_level,
                                &compressed_size);
                if (deflated && compressed_size - 6 < size) {
@@ -188,13 +257,11 @@ static int write_zip_entry(struct archiver_args *args,
        copy_le16(dirent.creator_version,
                S_ISLNK(mode) || (S_ISREG(mode) && (mode & 0111)) ? 0x0317 : 0);
        copy_le16(dirent.version, 10);
-       copy_le16(dirent.flags, 0);
+       copy_le16(dirent.flags, flags);
        copy_le16(dirent.compression_method, method);
        copy_le16(dirent.mtime, zip_time);
        copy_le16(dirent.mdate, zip_date);
-       copy_le32(dirent.crc32, crc);
-       copy_le32(dirent.compressed_size, compressed_size);
-       copy_le32(dirent.size, uncompressed_size);
+       set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
        copy_le16(dirent.filename_length, pathlen);
        copy_le16(dirent.extra_length, 0);
        copy_le16(dirent.comment_length, 0);
@@ -202,33 +269,120 @@ static int write_zip_entry(struct archiver_args *args,
        copy_le16(dirent.attr1, 0);
        copy_le32(dirent.attr2, attr2);
        copy_le32(dirent.offset, zip_offset);
-       memcpy(zip_dir + zip_dir_offset, &dirent, ZIP_DIR_HEADER_SIZE);
-       zip_dir_offset += ZIP_DIR_HEADER_SIZE;
-       memcpy(zip_dir + zip_dir_offset, path, pathlen);
-       zip_dir_offset += pathlen;
-       zip_dir_entries++;
 
        copy_le32(header.magic, 0x04034b50);
        copy_le16(header.version, 10);
-       copy_le16(header.flags, 0);
+       copy_le16(header.flags, flags);
        copy_le16(header.compression_method, method);
        copy_le16(header.mtime, zip_time);
        copy_le16(header.mdate, zip_date);
-       copy_le32(header.crc32, crc);
-       copy_le32(header.compressed_size, compressed_size);
-       copy_le32(header.size, uncompressed_size);
+       if (flags & ZIP_STREAM)
+               set_zip_header_data_desc(&header, 0, 0, 0);
+       else
+               set_zip_header_data_desc(&header, size, compressed_size, crc);
        copy_le16(header.filename_length, pathlen);
        copy_le16(header.extra_length, 0);
        write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE);
        zip_offset += ZIP_LOCAL_HEADER_SIZE;
        write_or_die(1, path, pathlen);
        zip_offset += pathlen;
-       if (compressed_size > 0) {
+       if (stream && method == 0) {
+               unsigned char buf[STREAM_BUFFER_SIZE];
+               ssize_t readlen;
+
+               for (;;) {
+                       readlen = read_istream(stream, buf, sizeof(buf));
+                       if (readlen <= 0)
+                               break;
+                       crc = crc32(crc, buf, readlen);
+                       write_or_die(1, buf, readlen);
+               }
+               close_istream(stream);
+               if (readlen)
+                       return readlen;
+
+               compressed_size = size;
+               zip_offset += compressed_size;
+
+               write_zip_data_desc(size, compressed_size, crc);
+               zip_offset += ZIP_DATA_DESC_SIZE;
+
+               set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
+       } else if (stream && method == 8) {
+               unsigned char buf[STREAM_BUFFER_SIZE];
+               ssize_t readlen;
+               git_zstream zstream;
+               int result;
+               size_t out_len;
+               unsigned char compressed[STREAM_BUFFER_SIZE * 2];
+
+               memset(&zstream, 0, sizeof(zstream));
+               git_deflate_init(&zstream, args->compression_level);
+
+               compressed_size = 0;
+               zstream.next_out = compressed;
+               zstream.avail_out = sizeof(compressed);
+
+               for (;;) {
+                       readlen = read_istream(stream, buf, sizeof(buf));
+                       if (readlen <= 0)
+                               break;
+                       crc = crc32(crc, buf, readlen);
+
+                       zstream.next_in = buf;
+                       zstream.avail_in = readlen;
+                       result = git_deflate(&zstream, 0);
+                       if (result != Z_OK)
+                               die("deflate error (%d)", result);
+                       out = compressed;
+                       if (!compressed_size)
+                               out += 2;
+                       out_len = zstream.next_out - out;
+
+                       if (out_len > 0) {
+                               write_or_die(1, out, out_len);
+                               compressed_size += out_len;
+                               zstream.next_out = compressed;
+                               zstream.avail_out = sizeof(compressed);
+                       }
+
+               }
+               close_istream(stream);
+               if (readlen)
+                       return readlen;
+
+               zstream.next_in = buf;
+               zstream.avail_in = 0;
+               result = git_deflate(&zstream, Z_FINISH);
+               if (result != Z_STREAM_END)
+                       die("deflate error (%d)", result);
+
+               git_deflate_end(&zstream);
+               out = compressed;
+               if (!compressed_size)
+                       out += 2;
+               out_len = zstream.next_out - out - 4;
+               write_or_die(1, out, out_len);
+               compressed_size += out_len;
+               zip_offset += compressed_size;
+
+               write_zip_data_desc(size, compressed_size, crc);
+               zip_offset += ZIP_DATA_DESC_SIZE;
+
+               set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
+       } else if (compressed_size > 0) {
                write_or_die(1, out, compressed_size);
                zip_offset += compressed_size;
        }
 
        free(deflated);
+       free(buffer);
+
+       memcpy(zip_dir + zip_dir_offset, &dirent, ZIP_DIR_HEADER_SIZE);
+       zip_dir_offset += ZIP_DIR_HEADER_SIZE;
+       memcpy(zip_dir + zip_dir_offset, path, pathlen);
+       zip_dir_offset += pathlen;
+       zip_dir_entries++;
 
        return 0;
 }