archive-zip: streaming for stored files

Write a data descriptor containing the CRC of the entry and its sizes after streaming it out. For simplicity, do that only if we're storing files (option -0) for now. t5000 verifies output. t1050 makes sure the command always respects core.bigfilethreshold Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed May 3, 2012 at 08:51 UTC 2158f883d99a92f801534c91294305ccbe171f79
3 files changed +88 -12
archive-zip.c
+78 -12
@@ -3,6 +3,7 @@
3 */
4 #include "cache.h"
5 #include "archive.h"
6 +#include "streaming.h"
7
8 static int zip_date;
9 static int zip_time;
@@ -15,6 +16,7 @@ static unsigned int zip_dir_offset;
16 static unsigned int zip_dir_entries;
17
18 #define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024)
19 +#define ZIP_STREAM (8)
20
21 struct zip_local_header {
22 unsigned char magic[4];
@@ -31,6 +33,14 @@ struct zip_local_header {
33 unsigned char _end[1];
34 };
35
36 +struct zip_data_desc {
37 + unsigned char magic[4];
38 + unsigned char crc32[4];
39 + unsigned char compressed_size[4];
40 + unsigned char size[4];
41 + unsigned char _end[1];
42 +};
43 +
44 struct zip_dir_header {
45 unsigned char magic[4];
46 unsigned char creator_version[2];
@@ -70,6 +80,7 @@ struct zip_dir_trailer {
80 * we're interested in.
81 */
82 #define ZIP_LOCAL_HEADER_SIZE offsetof(struct zip_local_header, _end)
83 +#define ZIP_DATA_DESC_SIZE offsetof(struct zip_data_desc, _end)
84 #define ZIP_DIR_HEADER_SIZE offsetof(struct zip_dir_header, _end)
85 #define ZIP_DIR_TRAILER_SIZE offsetof(struct zip_dir_trailer, _end)
86
@@ -120,6 +131,19 @@ static void *zlib_deflate(void *data, unsigned long size,
131 return buffer;
132 }
133
134 +static void write_zip_data_desc(unsigned long size,
135 + unsigned long compressed_size,
136 + unsigned long crc)
137 +{
138 + struct zip_data_desc trailer;
139 +
140 + copy_le32(trailer.magic, 0x08074b50);
141 + copy_le32(trailer.crc32, crc);
142 + copy_le32(trailer.compressed_size, compressed_size);
143 + copy_le32(trailer.size, size);
144 + write_or_die(1, &trailer, ZIP_DATA_DESC_SIZE);
145 +}
146 +
147 static void set_zip_dir_data_desc(struct zip_dir_header *header,
148 unsigned long size,
149 unsigned long compressed_size,
@@ -140,6 +164,8 @@ static void set_zip_header_data_desc(struct zip_local_header *header,
164 copy_le32(header->size, size);
165 }
166
167 +#define STREAM_BUFFER_SIZE (1024 * 16)
168 +
169 static int write_zip_entry(struct archiver_args *args,
170 const unsigned char *sha1,
171 const char *path, size_t pathlen,
@@ -155,6 +181,8 @@ static int write_zip_entry(struct archiver_args *args,
181 unsigned char *out;
182 void *deflated = NULL;
183 void *buffer;
184 + struct git_istream *stream = NULL;
185 + unsigned long flags = 0;
186 unsigned long size;
187
188 crc = crc32(0, NULL, 0);
@@ -173,25 +201,38 @@ static int write_zip_entry(struct archiver_args *args,
201 buffer = NULL;
202 size = 0;
203 } else if (S_ISREG(mode) || S_ISLNK(mode)) {
176 - enum object_type type;
177 - buffer = sha1_file_to_archive(args, path, sha1, mode, &type, &size);
178 - if (!buffer)
179 - return error("cannot read %s", sha1_to_hex(sha1));
204 + enum object_type type = sha1_object_info(sha1, &size);
205
206 method = 0;
207 attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) :
208 (mode & 0111) ? ((mode) << 16) : 0;
184 - if (S_ISREG(mode) && args->compression_level != 0)
209 + if (S_ISREG(mode) && args->compression_level != 0 && size > 0)
210 method = 8;
186 - crc = crc32(crc, buffer, size);
187 - out = buffer;
211 compressed_size = size;
212 +
213 + if (S_ISREG(mode) && type == OBJ_BLOB && !args->convert &&
214 + size > big_file_threshold && method == 0) {
215 + stream = open_istream(sha1, &type, &size, NULL);
216 + if (!stream)
217 + return error("cannot stream blob %s",
218 + sha1_to_hex(sha1));
219 + flags |= ZIP_STREAM;
220 + out = buffer = NULL;
221 + } else {
222 + buffer = sha1_file_to_archive(args, path, sha1, mode,
223 + &type, &size);
224 + if (!buffer)
225 + return error("cannot read %s",
226 + sha1_to_hex(sha1));
227 + crc = crc32(crc, buffer, size);
228 + out = buffer;
229 + }
230 } else {
231 return error("unsupported file mode: 0%o (SHA1: %s)", mode,
232 sha1_to_hex(sha1));
233 }
234
194 - if (method == 8) {
235 + if (buffer && method == 8) {
236 deflated = zlib_deflate(buffer, size, args->compression_level,
237 &compressed_size);
238 if (deflated && compressed_size - 6 < size) {
@@ -216,7 +257,7 @@ static int write_zip_entry(struct archiver_args *args,
257 copy_le16(dirent.creator_version,
258 S_ISLNK(mode) || (S_ISREG(mode) && (mode & 0111)) ? 0x0317 : 0);
259 copy_le16(dirent.version, 10);
219 - copy_le16(dirent.flags, 0);
260 + copy_le16(dirent.flags, flags);
261 copy_le16(dirent.compression_method, method);
262 copy_le16(dirent.mtime, zip_time);
263 copy_le16(dirent.mdate, zip_date);
@@ -231,18 +272,43 @@ static int write_zip_entry(struct archiver_args *args,
272
273 copy_le32(header.magic, 0x04034b50);
274 copy_le16(header.version, 10);
234 - copy_le16(header.flags, 0);
275 + copy_le16(header.flags, flags);
276 copy_le16(header.compression_method, method);
277 copy_le16(header.mtime, zip_time);
278 copy_le16(header.mdate, zip_date);
238 - set_zip_header_data_desc(&header, size, compressed_size, crc);
279 + if (flags & ZIP_STREAM)
280 + set_zip_header_data_desc(&header, 0, 0, 0);
281 + else
282 + set_zip_header_data_desc(&header, size, compressed_size, crc);
283 copy_le16(header.filename_length, pathlen);
284 copy_le16(header.extra_length, 0);
285 write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE);
286 zip_offset += ZIP_LOCAL_HEADER_SIZE;
287 write_or_die(1, path, pathlen);
288 zip_offset += pathlen;
245 - if (compressed_size > 0) {
289 + if (stream && method == 0) {
290 + unsigned char buf[STREAM_BUFFER_SIZE];
291 + ssize_t readlen;
292 +
293 + for (;;) {
294 + readlen = read_istream(stream, buf, sizeof(buf));
295 + if (readlen <= 0)
296 + break;
297 + crc = crc32(crc, buf, readlen);
298 + write_or_die(1, buf, readlen);
299 + }
300 + close_istream(stream);
301 + if (readlen)
302 + return readlen;
303 +
304 + compressed_size = size;
305 + zip_offset += compressed_size;
306 +
307 + write_zip_data_desc(size, compressed_size, crc);
308 + zip_offset += ZIP_DATA_DESC_SIZE;
309 +
310 + set_zip_dir_data_desc(&dirent, size, compressed_size, crc);
311 + } else if (compressed_size > 0) {
312 write_or_die(1, out, compressed_size);
313 zip_offset += compressed_size;
314 }
t/t1050-large.sh
+4
@@ -138,4 +138,8 @@ test_expect_success 'tar achiving' '
138 git archive --format=tar HEAD >/dev/null
139 '
140
141 +test_expect_success 'zip achiving, store only' '
142 + git archive --format=zip -0 HEAD >/dev/null
143 +'
144 +
145 test_done
t/t5000-tar-tree.sh
+6
@@ -244,6 +244,12 @@ test_expect_success UNZIP \
244 'validate file contents with prefix' \
245 'diff -r a e/prefix/a'
246
247 +test_expect_success UNZIP 'git archive -0 --format=zip on large files' '
248 + test_config core.bigfilethreshold 1 &&
249 + git archive -0 --format=zip HEAD >large.zip &&
250 + (mkdir large && cd large && $UNZIP ../large.zip)
251 +'
252 +
253 test_expect_success \
254 'git archive --list outside of a git repo' \
255 'GIT_DIR=some/non-existing/directory git archive --list'