1
+#define USE_THE_REPOSITORY_VARIABLE
2
+
3
+#include "test-tool.h"
4
+#include "git-compat-util.h"
5
+#include "git-zlib.h"
6
+#include "hash.h"
7
+#include "hex.h"
8
+#include "object-file.h"
9
+#include "object.h"
10
+#include "pack.h"
11
+#include "parse-options.h"
12
+#include "parse.h"
13
+#include "repository.h"
14
+#include "setup.h"
15
+#include "strbuf.h"
16
+#include "write-or-die.h"
17
+
18
+#define BLOCK_SIZE 0xffff
19
+static const unsigned char zeros[BLOCK_SIZE];
20
+
21
+/*
22
+ * Write data as an uncompressed zlib stream.
23
+ * For data larger than 64KB, writes multiple uncompressed blocks.
24
+ * If data is NULL, writes zeros.
25
+ * Updates the pack checksum context.
26
+ */
27
+static void write_uncompressed_zlib(FILE *f, struct git_hash_ctx *pack_ctx,
28
+ const void *data, size_t len,
29
+ const struct git_hash_algo *algo)
30
+{
31
+ unsigned char zlib_header[2] = { 0x78, 0x01 }; /* CMF, FLG */
32
+ unsigned char block_header[5];
33
+ const unsigned char *p = data;
34
+ size_t remaining = len;
35
+ uint32_t adler = 1L; /* adler32 initial value */
36
+ unsigned char adler_buf[4];
37
+
38
+ /* Write zlib header */
39
+ fwrite_or_die(f, zlib_header, sizeof(zlib_header));
40
+ algo->update_fn(pack_ctx, zlib_header, 2);
41
+
42
+ /* Write uncompressed blocks (max 64KB each) */
43
+ do {
44
+ size_t block_len = remaining > BLOCK_SIZE ? BLOCK_SIZE : remaining;
45
+ int is_final = (block_len == remaining);
46
+ const unsigned char *block_data = data ? p : zeros;
47
+
48
+ block_header[0] = is_final ? 0x01 : 0x00;
49
+ block_header[1] = block_len & 0xff;
50
+ block_header[2] = (block_len >> 8) & 0xff;
51
+ block_header[3] = block_header[1] ^ 0xff;
52
+ block_header[4] = block_header[2] ^ 0xff;
53
+
54
+ fwrite_or_die(f, block_header, sizeof(block_header));
55
+ algo->update_fn(pack_ctx, block_header, 5);
56
+
57
+ if (block_len) {
58
+ fwrite_or_die(f, block_data, block_len);
59
+ algo->update_fn(pack_ctx, block_data, block_len);
60
+ adler = adler32(adler, block_data, block_len);
61
+ }
62
+
63
+ if (data)
64
+ p += block_len;
65
+ remaining -= block_len;
66
+ } while (remaining > 0);
67
+
68
+ /* Write adler32 checksum */
69
+ put_be32(adler_buf, adler);
70
+ fwrite_or_die(f, adler_buf, sizeof(adler_buf));
71
+ algo->update_fn(pack_ctx, adler_buf, 4);
72
+}
73
+
74
+/*
75
+ * Write an uncompressed object to the pack file.
76
+ * If `data == NULL`, it is treated like a buffer to NUL bytes.
77
+ * Updates the pack checksum context.
78
+ */
79
+static void write_pack_object(FILE *f, struct git_hash_ctx *pack_ctx,
80
+ enum object_type type,
81
+ const void *data, size_t len,
82
+ struct object_id *oid,
83
+ const struct git_hash_algo *algo)
84
+{
85
+ unsigned char pack_header[MAX_PACK_OBJECT_HEADER];
86
+ char object_header[32];
87
+ int pack_header_len, object_header_len;
88
+ struct git_hash_ctx ctx;
89
+
90
+ /* Write pack object header */
91
+ pack_header_len = encode_in_pack_object_header(pack_header,
92
+ sizeof(pack_header),
93
+ type, len);
94
+ fwrite_or_die(f, pack_header, pack_header_len);
95
+ algo->update_fn(pack_ctx, pack_header, pack_header_len);
96
+
97
+ /* Write the data as uncompressed zlib */
98
+ write_uncompressed_zlib(f, pack_ctx, data, len, algo);
99
+
100
+ algo->init_fn(&ctx);
101
+ object_header_len = format_object_header(object_header,
102
+ sizeof(object_header),
103
+ type, len);
104
+ algo->update_fn(&ctx, object_header, object_header_len);
105
+ if (data)
106
+ algo->update_fn(&ctx, data, len);
107
+ else {
108
+ for (size_t i = len / BLOCK_SIZE; i; i--)
109
+ algo->update_fn(&ctx, zeros, BLOCK_SIZE);
110
+ algo->update_fn(&ctx, zeros, len % BLOCK_SIZE);
111
+ }
112
+ algo->final_oid_fn(oid, &ctx);
113
+}
114
+
115
+/*
116
+ * Generate a pack file with a single large (>4GB) reachable object.
117
+ *
118
+ * Creates:
119
+ * 1. A large blob (all NUL bytes)
120
+ * 2. A tree containing that blob as "file"
121
+ * 3. A commit using that tree
122
+ * 4. The empty tree
123
+ * 5. A child commit using the empty tree
124
+ *
125
+ * This is useful for testing that Git can handle objects larger than 4GB.
126
+ */
127
+static int generate_pack_with_large_object(const char *path, size_t blob_size,
128
+ const struct git_hash_algo *algo)
129
+{
130
+ FILE *f = xfopen(path, "wb");
131
+ struct git_hash_ctx pack_ctx;
132
+ unsigned char pack_hash[GIT_MAX_RAWSZ];
133
+ struct object_id blob_oid, tree_oid, commit_oid, empty_tree_oid, final_commit_oid;
134
+ struct strbuf buf = STRBUF_INIT;
135
+ const uint32_t object_count = 5;
136
+ struct pack_header pack_header = {
137
+ .hdr_signature = htonl(PACK_SIGNATURE),
138
+ .hdr_version = htonl(PACK_VERSION),
139
+ .hdr_entries = htonl(object_count),
140
+ };
141
+
142
+ algo->init_fn(&pack_ctx);
143
+
144
+ /* Write pack header */
145
+ fwrite_or_die(f, &pack_header, sizeof(pack_header));
146
+ algo->update_fn(&pack_ctx, &pack_header, sizeof(pack_header));
147
+
148
+ /* 1. Write the large blob */
149
+ write_pack_object(f, &pack_ctx, OBJ_BLOB, NULL, blob_size, &blob_oid, algo);
150
+
151
+ /* 2. Write tree containing the blob as "file" */
152
+ strbuf_addf(&buf, "100644 file%c", '\0');
153
+ strbuf_add(&buf, blob_oid.hash, algo->rawsz);
154
+ write_pack_object(f, &pack_ctx, OBJ_TREE, buf.buf, buf.len, &tree_oid, algo);
155
+
156
+ /* 3. Write commit using that tree */
157
+ strbuf_reset(&buf);
158
+ strbuf_addf(&buf,
159
+ "tree %s\n"
160
+ "author A U Thor <author@example.com> 1234567890 +0000\n"
161
+ "committer C O Mitter <committer@example.com> 1234567890 +0000\n"
162
+ "\n"
163
+ "Large blob commit\n",
164
+ oid_to_hex(&tree_oid));
165
+ write_pack_object(f, &pack_ctx, OBJ_COMMIT, buf.buf, buf.len, &commit_oid, algo);
166
+
167
+ /* 4. Write the empty tree */
168
+ write_pack_object(f, &pack_ctx, OBJ_TREE, "", 0, &empty_tree_oid, algo);
169
+
170
+ /* 5. Write final commit using empty tree, with previous commit as parent */
171
+ strbuf_reset(&buf);
172
+ strbuf_addf(&buf,
173
+ "tree %s\n"
174
+ "parent %s\n"
175
+ "author A U Thor <author@example.com> 1234567890 +0000\n"
176
+ "committer C O Mitter <committer@example.com> 1234567890 +0000\n"
177
+ "\n"
178
+ "Empty tree commit\n",
179
+ oid_to_hex(&empty_tree_oid),
180
+ oid_to_hex(&commit_oid));
181
+ write_pack_object(f, &pack_ctx, OBJ_COMMIT, buf.buf, buf.len, &final_commit_oid, algo);
182
+
183
+ /* Write pack trailer (checksum) */
184
+ algo->final_fn(pack_hash, &pack_ctx);
185
+ fwrite_or_die(f, pack_hash, algo->rawsz);
186
+ if (fclose(f))
187
+ die_errno(_("could not close '%s'"), path);
188
+
189
+ strbuf_release(&buf);
190
+
191
+ /* Print the final commit OID so caller can set up refs */
192
+ printf("%s\n", oid_to_hex(&final_commit_oid));
193
+
194
+ return 0;
195
+}
196
+
197
+static int cmd__synthesize__pack(int argc, const char **argv,
198
+ const char *prefix UNUSED,
199
+ struct repository *repo)
200
+{
201
+ int non_git;
202
+ int reachable_large = 0;
203
+ const struct git_hash_algo *algo;
204
+ size_t blob_size;
205
+ uintmax_t blob_size_u;
206
+ const char *path;
207
+ const char * const usage[] = {
208
+ "test-tool synthesize pack "
209
+ "--reachable-large <blob-size> <filename>",
210
+ NULL
211
+ };
212
+ struct option options[] = {
213
+ OPT_BOOL(0, "reachable-large", &reachable_large,
214
+ N_("write a pack with a single reachable large blob")),
215
+ OPT_END()
216
+ };
217
+
218
+ setup_git_directory_gently(&non_git);
219
+ repo = the_repository;
220
+ algo = repo->hash_algo;
221
+
222
+ argc = parse_options(argc, argv, NULL, options, usage,
223
+ PARSE_OPT_KEEP_ARGV0);
224
+ if (argc != 3 || !reachable_large)
225
+ usage_with_options(usage, options);
226
+
227
+ if (!git_parse_unsigned(argv[1], &blob_size_u,
228
+ maximum_unsigned_value_of_type(size_t)))
229
+ die(_("'%s' is not a valid blob size"), argv[1]);
230
+ blob_size = blob_size_u;
231
+ path = argv[2];
232
+
233
+ return !!generate_pack_with_large_object(path, blob_size, algo);
234
+}
235
+
236
+int cmd__synthesize(int argc, const char **argv)
237
+{
238
+ const char *prefix = NULL;
239
+ char const * const synthesize_usage[] = {
240
+ "test-tool synthesize pack <options>",
241
+ NULL,
242
+ };
243
+ parse_opt_subcommand_fn *fn = NULL;
244
+ struct option options[] = {
245
+ OPT_SUBCOMMAND("pack", &fn, cmd__synthesize__pack),
246
+ OPT_END()
247
+ };
248
+ argc = parse_options(argc, argv, prefix, options, synthesize_usage, 0);
249
+ return !!fn(argc, argv, prefix, NULL);
250
+}