@@ -320,11 +320,85 @@ static void added_object(unsigned nr, enum object_type type,
}
}
+struct input_data_from_zstream {
+ git_zstream *zstream;
+ unsigned char buf[4096];
+ int status;
+};
+
+static const char *read_inflate_in_stream(void *data, unsigned long *readlen)
+{
+ struct input_data_from_zstream *input = data;
+ git_zstream *zstream = input->zstream;
+ void *in = fill(1);
+
+ if (!len || input->status == Z_STREAM_END) {
+ *readlen = 0;
+ return NULL;
+ }
+
+ zstream->next_out = input->buf;
+ zstream->avail_out = sizeof(input->buf);
+ zstream->next_in = in;
+ zstream->avail_in = len;
+
+ input->status = git_inflate(zstream, 0);
+ use(len - zstream->avail_in);
+ *readlen = sizeof(input->buf) - zstream->avail_out;
+
+ return (const char *)input->buf;
+}
+
+static void write_stream_blob(unsigned nr, unsigned long size)
+{
+ char hdr[32];
+ int hdrlen;
+ git_zstream zstream;
+ struct input_data_from_zstream data;
+ struct input_stream in_stream = {
+ .read = read_inflate_in_stream,
+ .data = &data,
+ };
+ struct object_id *oid = &obj_list[nr].oid;
+ int ret;
+
+ memset(&zstream, 0, sizeof(zstream));
+ memset(&data, 0, sizeof(data));
+ data.zstream = &zstream;
+ git_inflate_init(&zstream);
+
+ /* Generate the header */
+ hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %"PRIuMAX, type_name(OBJ_BLOB), (uintmax_t)size) + 1;
+
+ if ((ret = write_loose_object(oid, hdr, hdrlen, &in_stream, dry_run, 0, 0)))
+ die(_("failed to write object in stream %d"), ret);
+
+ if (zstream.total_out != size || data.status != Z_STREAM_END)
+ die(_("inflate returned %d"), data.status);
+ git_inflate_end(&zstream);
+
+ if (strict && !dry_run) {
+ struct blob *blob = lookup_blob(the_repository, oid);
+ if (blob)
+ blob->object.flags |= FLAG_WRITTEN;
+ else
+ die("invalid blob object from stream");
+ }
+ obj_list[nr].obj = NULL;
+}
+
static void unpack_non_delta_entry(enum object_type type, unsigned long size,
unsigned nr)
{
- void *buf = get_data(size);
+ void *buf;
+
+ /* Write large blob in stream without allocating full buffer. */
+ if (type == OBJ_BLOB && size > big_file_threshold) {
+ write_stream_blob(nr, size);
+ return;
+ }
+ buf = get_data(size);
if (!dry_run && buf)
write_object(nr, type, buf, size);
else
new file mode 100755
@@ -0,0 +1,92 @@
+#!/bin/sh
+#
+# Copyright (c) 2021 Han Xin
+#
+
+test_description='Test unpack-objects when receive pack'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+
+test_expect_success "create commit with big blobs (1.5 MB)" '
+ test-tool genrandom foo 1500000 >big-blob &&
+ test_commit --append foo big-blob &&
+ test-tool genrandom bar 1500000 >big-blob &&
+ test_commit --append bar big-blob &&
+ (
+ cd .git &&
+ find objects/?? -type f | sort
+ ) >expect &&
+ git repack -ad
+'
+
+test_expect_success 'setup GIT_ALLOC_LIMIT to 1MB' '
+ GIT_ALLOC_LIMIT=1m &&
+ export GIT_ALLOC_LIMIT
+'
+
+test_expect_success 'prepare dest repository' '
+ git init --bare dest.git &&
+ git -C dest.git config core.bigFileThreshold 2m &&
+ git -C dest.git config receive.unpacklimit 100
+'
+
+test_expect_success 'fail to push: cannot allocate' '
+ test_must_fail git push dest.git HEAD 2>err &&
+ test_i18ngrep "remote: fatal: attempting to allocate" err &&
+ (
+ cd dest.git &&
+ find objects/?? -type f | sort
+ ) >actual &&
+ ! test_cmp expect actual
+'
+
+test_expect_success 'set a lower bigfile threshold' '
+ git -C dest.git config core.bigFileThreshold 1m
+'
+
+test_expect_success 'unpack big object in stream' '
+ git push dest.git HEAD &&
+ git -C dest.git fsck &&
+ (
+ cd dest.git &&
+ find objects/?? -type f | sort
+ ) >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for unpack-objects dry-run test' '
+ PACK=$(echo main | git pack-objects --progress --revs test) &&
+ unset GIT_ALLOC_LIMIT &&
+ git init --bare unpack-test.git
+'
+
+test_expect_success 'unpack-objects dry-run with large threshold' '
+ (
+ cd unpack-test.git &&
+ git config core.bigFileThreshold 2m &&
+ git unpack-objects -n <../test-$PACK.pack
+ ) &&
+ (
+ cd unpack-test.git &&
+ find objects/ -type f
+ ) >actual &&
+ test_must_be_empty actual
+'
+
+test_expect_success 'unpack-objects dry-run with small threshold' '
+ (
+ cd unpack-test.git &&
+ git config core.bigFileThreshold 1m &&
+ git unpack-objects -n <../test-$PACK.pack
+ ) &&
+ (
+ cd unpack-test.git &&
+ find objects/ -type f
+ ) >actual &&
+ test_must_be_empty actual
+'
+
+test_done