Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2005, 2006 Rene Scharfe |
3 | | */ |
4 | | |
5 | | #define USE_THE_REPOSITORY_VARIABLE |
6 | | |
7 | | #include "git-compat-util.h" |
8 | | #include "config.h" |
9 | | #include "gettext.h" |
10 | | #include "git-zlib.h" |
11 | | #include "hex.h" |
12 | | #include "tar.h" |
13 | | #include "archive.h" |
14 | | #include "object-store-ll.h" |
15 | | #include "strbuf.h" |
16 | | #include "streaming.h" |
17 | | #include "run-command.h" |
18 | | #include "write-or-die.h" |
19 | | |
20 | 0 | #define RECORDSIZE (512) |
21 | 0 | #define BLOCKSIZE (RECORDSIZE * 20) |
22 | | |
23 | | static char block[BLOCKSIZE]; |
24 | | static unsigned long offset; |
25 | | |
26 | | static int tar_umask = 002; |
27 | | |
28 | | static int write_tar_filter_archive(const struct archiver *ar, |
29 | | struct archiver_args *args); |
30 | | |
31 | | /* |
32 | | * This is the max value that a ustar size header can specify, as it is fixed |
33 | | * at 11 octal digits. POSIX specifies that we switch to extended headers at |
34 | | * this size. |
35 | | * |
36 | | * Likewise for the mtime (which happens to use a buffer of the same size). |
37 | | */ |
38 | | #if ULONG_MAX == 0xFFFFFFFF |
39 | | #define USTAR_MAX_SIZE ULONG_MAX |
40 | | #else |
41 | 0 | #define USTAR_MAX_SIZE 077777777777UL |
42 | | #endif |
43 | | #if TIME_MAX == 0xFFFFFFFF |
44 | | #define USTAR_MAX_MTIME TIME_MAX |
45 | | #else |
46 | 0 | #define USTAR_MAX_MTIME 077777777777ULL |
47 | | #endif |
48 | | |
49 | | static void tar_write_block(const void *buf) |
50 | 0 | { |
51 | 0 | write_or_die(1, buf, BLOCKSIZE); |
52 | 0 | } |
53 | | |
54 | | static void (*write_block)(const void *) = tar_write_block; |
55 | | |
56 | | /* writes out the whole block, but only if it is full */ |
57 | | static void write_if_needed(void) |
58 | 0 | { |
59 | 0 | if (offset == BLOCKSIZE) { |
60 | 0 | write_block(block); |
61 | 0 | offset = 0; |
62 | 0 | } |
63 | 0 | } |
64 | | |
65 | | /* |
66 | | * queues up writes, so that all our write(2) calls write exactly one |
67 | | * full block; pads writes to RECORDSIZE |
68 | | */ |
69 | | static void do_write_blocked(const void *data, unsigned long size) |
70 | 0 | { |
71 | 0 | const char *buf = data; |
72 | |
|
73 | 0 | if (offset) { |
74 | 0 | unsigned long chunk = BLOCKSIZE - offset; |
75 | 0 | if (size < chunk) |
76 | 0 | chunk = size; |
77 | 0 | memcpy(block + offset, buf, chunk); |
78 | 0 | size -= chunk; |
79 | 0 | offset += chunk; |
80 | 0 | buf += chunk; |
81 | 0 | write_if_needed(); |
82 | 0 | } |
83 | 0 | while (size >= BLOCKSIZE) { |
84 | 0 | write_block(buf); |
85 | 0 | size -= BLOCKSIZE; |
86 | 0 | buf += BLOCKSIZE; |
87 | 0 | } |
88 | 0 | if (size) { |
89 | 0 | memcpy(block + offset, buf, size); |
90 | 0 | offset += size; |
91 | 0 | } |
92 | 0 | } |
93 | | |
94 | | static void finish_record(void) |
95 | 0 | { |
96 | 0 | unsigned long tail; |
97 | 0 | tail = offset % RECORDSIZE; |
98 | 0 | if (tail) { |
99 | 0 | memset(block + offset, 0, RECORDSIZE - tail); |
100 | 0 | offset += RECORDSIZE - tail; |
101 | 0 | } |
102 | 0 | write_if_needed(); |
103 | 0 | } |
104 | | |
105 | | static void write_blocked(const void *data, unsigned long size) |
106 | 0 | { |
107 | 0 | do_write_blocked(data, size); |
108 | 0 | finish_record(); |
109 | 0 | } |
110 | | |
111 | | /* |
112 | | * The end of tar archives is marked by 2*512 nul bytes and after that |
113 | | * follows the rest of the block (if any). |
114 | | */ |
115 | | static void write_trailer(void) |
116 | 0 | { |
117 | 0 | int tail = BLOCKSIZE - offset; |
118 | 0 | memset(block + offset, 0, tail); |
119 | 0 | write_block(block); |
120 | 0 | if (tail < 2 * RECORDSIZE) { |
121 | 0 | memset(block, 0, offset); |
122 | 0 | write_block(block); |
123 | 0 | } |
124 | 0 | } |
125 | | |
126 | | /* |
127 | | * queues up writes, so that all our write(2) calls write exactly one |
128 | | * full block; pads writes to RECORDSIZE |
129 | | */ |
130 | | static int stream_blocked(struct repository *r, const struct object_id *oid) |
131 | 0 | { |
132 | 0 | struct git_istream *st; |
133 | 0 | enum object_type type; |
134 | 0 | unsigned long sz; |
135 | 0 | char buf[BLOCKSIZE]; |
136 | 0 | ssize_t readlen; |
137 | |
|
138 | 0 | st = open_istream(r, oid, &type, &sz, NULL); |
139 | 0 | if (!st) |
140 | 0 | return error(_("cannot stream blob %s"), oid_to_hex(oid)); |
141 | 0 | for (;;) { |
142 | 0 | readlen = read_istream(st, buf, sizeof(buf)); |
143 | 0 | if (readlen <= 0) |
144 | 0 | break; |
145 | 0 | do_write_blocked(buf, readlen); |
146 | 0 | } |
147 | 0 | close_istream(st); |
148 | 0 | if (!readlen) |
149 | 0 | finish_record(); |
150 | 0 | return readlen; |
151 | 0 | } |
152 | | |
153 | | /* |
154 | | * pax extended header records have the format "%u %s=%s\n". %u contains |
155 | | * the size of the whole string (including the %u), the first %s is the |
156 | | * keyword, the second one is the value. This function constructs such a |
157 | | * string and appends it to a struct strbuf. |
158 | | */ |
159 | | static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, |
160 | | const char *value, size_t valuelen) |
161 | 0 | { |
162 | 0 | size_t orig_len = sb->len; |
163 | 0 | size_t len, tmp; |
164 | | |
165 | | /* "%u %s=%s\n" */ |
166 | 0 | len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; |
167 | 0 | for (tmp = 1; len / 10 >= tmp; tmp *= 10) |
168 | 0 | len++; |
169 | |
|
170 | 0 | strbuf_grow(sb, len); |
171 | 0 | strbuf_addf(sb, "%"PRIuMAX" %s=", (uintmax_t)len, keyword); |
172 | 0 | strbuf_add(sb, value, valuelen); |
173 | 0 | strbuf_addch(sb, '\n'); |
174 | |
|
175 | 0 | if (len != sb->len - orig_len) |
176 | 0 | BUG("pax extended header length miscalculated as %"PRIuMAX |
177 | 0 | ", should be %"PRIuMAX, |
178 | 0 | (uintmax_t)len, (uintmax_t)(sb->len - orig_len)); |
179 | 0 | } |
180 | | |
181 | | /* |
182 | | * Like strbuf_append_ext_header, but for numeric values. |
183 | | */ |
184 | | static void strbuf_append_ext_header_uint(struct strbuf *sb, |
185 | | const char *keyword, |
186 | | uintmax_t value) |
187 | 0 | { |
188 | 0 | char buf[40]; /* big enough for 2^128 in decimal, plus NUL */ |
189 | 0 | int len; |
190 | |
|
191 | 0 | len = xsnprintf(buf, sizeof(buf), "%"PRIuMAX, value); |
192 | 0 | strbuf_append_ext_header(sb, keyword, buf, len); |
193 | 0 | } |
194 | | |
195 | | static unsigned int ustar_header_chksum(const struct ustar_header *header) |
196 | 0 | { |
197 | 0 | const unsigned char *p = (const unsigned char *)header; |
198 | 0 | unsigned int chksum = 0; |
199 | 0 | while (p < (const unsigned char *)header->chksum) |
200 | 0 | chksum += *p++; |
201 | 0 | chksum += sizeof(header->chksum) * ' '; |
202 | 0 | p += sizeof(header->chksum); |
203 | 0 | while (p < (const unsigned char *)header + sizeof(struct ustar_header)) |
204 | 0 | chksum += *p++; |
205 | 0 | return chksum; |
206 | 0 | } |
207 | | |
208 | | static size_t get_path_prefix(const char *path, size_t pathlen, size_t maxlen) |
209 | 0 | { |
210 | 0 | size_t i = pathlen; |
211 | 0 | if (i > 1 && path[i - 1] == '/') |
212 | 0 | i--; |
213 | 0 | if (i > maxlen) |
214 | 0 | i = maxlen; |
215 | 0 | do { |
216 | 0 | i--; |
217 | 0 | } while (i > 0 && path[i] != '/'); |
218 | 0 | return i; |
219 | 0 | } |
220 | | |
221 | | static void prepare_header(struct archiver_args *args, |
222 | | struct ustar_header *header, |
223 | | unsigned int mode, unsigned long size) |
224 | 0 | { |
225 | 0 | xsnprintf(header->mode, sizeof(header->mode), "%07o", mode & 07777); |
226 | 0 | xsnprintf(header->size, sizeof(header->size), "%011"PRIoMAX , S_ISREG(mode) ? (uintmax_t)size : (uintmax_t)0); |
227 | 0 | xsnprintf(header->mtime, sizeof(header->mtime), "%011lo", (unsigned long) args->time); |
228 | |
|
229 | 0 | xsnprintf(header->uid, sizeof(header->uid), "%07o", 0); |
230 | 0 | xsnprintf(header->gid, sizeof(header->gid), "%07o", 0); |
231 | 0 | strlcpy(header->uname, "root", sizeof(header->uname)); |
232 | 0 | strlcpy(header->gname, "root", sizeof(header->gname)); |
233 | 0 | xsnprintf(header->devmajor, sizeof(header->devmajor), "%07o", 0); |
234 | 0 | xsnprintf(header->devminor, sizeof(header->devminor), "%07o", 0); |
235 | |
|
236 | 0 | memcpy(header->magic, "ustar", 6); |
237 | 0 | memcpy(header->version, "00", 2); |
238 | |
|
239 | 0 | xsnprintf(header->chksum, sizeof(header->chksum), "%07o", ustar_header_chksum(header)); |
240 | 0 | } |
241 | | |
242 | | static void write_extended_header(struct archiver_args *args, |
243 | | const struct object_id *oid, |
244 | | const void *buffer, unsigned long size) |
245 | 0 | { |
246 | 0 | struct ustar_header header; |
247 | 0 | unsigned int mode; |
248 | 0 | memset(&header, 0, sizeof(header)); |
249 | 0 | *header.typeflag = TYPEFLAG_EXT_HEADER; |
250 | 0 | mode = 0100666; |
251 | 0 | xsnprintf(header.name, sizeof(header.name), "%s.paxheader", oid_to_hex(oid)); |
252 | 0 | prepare_header(args, &header, mode, size); |
253 | 0 | write_blocked(&header, sizeof(header)); |
254 | 0 | write_blocked(buffer, size); |
255 | 0 | } |
256 | | |
257 | | static int write_tar_entry(struct archiver_args *args, |
258 | | const struct object_id *oid, |
259 | | const char *path, size_t pathlen, |
260 | | unsigned int mode, |
261 | | void *buffer, unsigned long size) |
262 | 0 | { |
263 | 0 | struct ustar_header header; |
264 | 0 | struct strbuf ext_header = STRBUF_INIT; |
265 | 0 | unsigned long size_in_header; |
266 | 0 | int err = 0; |
267 | |
|
268 | 0 | memset(&header, 0, sizeof(header)); |
269 | |
|
270 | 0 | if (S_ISDIR(mode) || S_ISGITLINK(mode)) { |
271 | 0 | *header.typeflag = TYPEFLAG_DIR; |
272 | 0 | mode = (mode | 0777) & ~tar_umask; |
273 | 0 | } else if (S_ISLNK(mode)) { |
274 | 0 | *header.typeflag = TYPEFLAG_LNK; |
275 | 0 | mode |= 0777; |
276 | 0 | } else if (S_ISREG(mode)) { |
277 | 0 | *header.typeflag = TYPEFLAG_REG; |
278 | 0 | mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask; |
279 | 0 | } else { |
280 | 0 | return error(_("unsupported file mode: 0%o (SHA1: %s)"), |
281 | 0 | mode, oid_to_hex(oid)); |
282 | 0 | } |
283 | 0 | if (pathlen > sizeof(header.name)) { |
284 | 0 | size_t plen = get_path_prefix(path, pathlen, |
285 | 0 | sizeof(header.prefix)); |
286 | 0 | size_t rest = pathlen - plen - 1; |
287 | 0 | if (plen > 0 && rest <= sizeof(header.name)) { |
288 | 0 | memcpy(header.prefix, path, plen); |
289 | 0 | memcpy(header.name, path + plen + 1, rest); |
290 | 0 | } else { |
291 | 0 | xsnprintf(header.name, sizeof(header.name), "%s.data", |
292 | 0 | oid_to_hex(oid)); |
293 | 0 | strbuf_append_ext_header(&ext_header, "path", |
294 | 0 | path, pathlen); |
295 | 0 | } |
296 | 0 | } else |
297 | 0 | memcpy(header.name, path, pathlen); |
298 | |
|
299 | 0 | if (S_ISLNK(mode)) { |
300 | 0 | if (size > sizeof(header.linkname)) { |
301 | 0 | xsnprintf(header.linkname, sizeof(header.linkname), |
302 | 0 | "see %s.paxheader", oid_to_hex(oid)); |
303 | 0 | strbuf_append_ext_header(&ext_header, "linkpath", |
304 | 0 | buffer, size); |
305 | 0 | } else |
306 | 0 | memcpy(header.linkname, buffer, size); |
307 | 0 | } |
308 | |
|
309 | 0 | size_in_header = size; |
310 | 0 | if (S_ISREG(mode) && size > USTAR_MAX_SIZE) { |
311 | 0 | size_in_header = 0; |
312 | 0 | strbuf_append_ext_header_uint(&ext_header, "size", size); |
313 | 0 | } |
314 | |
|
315 | 0 | prepare_header(args, &header, mode, size_in_header); |
316 | |
|
317 | 0 | if (ext_header.len > 0) { |
318 | 0 | write_extended_header(args, oid, ext_header.buf, |
319 | 0 | ext_header.len); |
320 | 0 | } |
321 | 0 | strbuf_release(&ext_header); |
322 | 0 | write_blocked(&header, sizeof(header)); |
323 | 0 | if (S_ISREG(mode) && size > 0) { |
324 | 0 | if (buffer) |
325 | 0 | write_blocked(buffer, size); |
326 | 0 | else |
327 | 0 | err = stream_blocked(args->repo, oid); |
328 | 0 | } |
329 | 0 | return err; |
330 | 0 | } |
331 | | |
332 | | static void write_global_extended_header(struct archiver_args *args) |
333 | 0 | { |
334 | 0 | const struct object_id *oid = args->commit_oid; |
335 | 0 | struct strbuf ext_header = STRBUF_INIT; |
336 | 0 | struct ustar_header header; |
337 | 0 | unsigned int mode; |
338 | |
|
339 | 0 | if (oid) |
340 | 0 | strbuf_append_ext_header(&ext_header, "comment", |
341 | 0 | oid_to_hex(oid), |
342 | 0 | the_hash_algo->hexsz); |
343 | 0 | if (args->time > USTAR_MAX_MTIME) { |
344 | 0 | strbuf_append_ext_header_uint(&ext_header, "mtime", |
345 | 0 | args->time); |
346 | 0 | args->time = USTAR_MAX_MTIME; |
347 | 0 | } |
348 | |
|
349 | 0 | if (!ext_header.len) |
350 | 0 | return; |
351 | | |
352 | 0 | memset(&header, 0, sizeof(header)); |
353 | 0 | *header.typeflag = TYPEFLAG_GLOBAL_HEADER; |
354 | 0 | mode = 0100666; |
355 | 0 | xsnprintf(header.name, sizeof(header.name), "pax_global_header"); |
356 | 0 | prepare_header(args, &header, mode, ext_header.len); |
357 | 0 | write_blocked(&header, sizeof(header)); |
358 | 0 | write_blocked(ext_header.buf, ext_header.len); |
359 | 0 | strbuf_release(&ext_header); |
360 | 0 | } |
361 | | |
362 | | static struct archiver **tar_filters; |
363 | | static int nr_tar_filters; |
364 | | static int alloc_tar_filters; |
365 | | |
366 | | static struct archiver *find_tar_filter(const char *name, size_t len) |
367 | 0 | { |
368 | 0 | int i; |
369 | 0 | for (i = 0; i < nr_tar_filters; i++) { |
370 | 0 | struct archiver *ar = tar_filters[i]; |
371 | 0 | if (!xstrncmpz(ar->name, name, len)) |
372 | 0 | return ar; |
373 | 0 | } |
374 | 0 | return NULL; |
375 | 0 | } |
376 | | |
377 | | static int tar_filter_config(const char *var, const char *value, |
378 | | void *data UNUSED) |
379 | 0 | { |
380 | 0 | struct archiver *ar; |
381 | 0 | const char *name; |
382 | 0 | const char *type; |
383 | 0 | size_t namelen; |
384 | |
|
385 | 0 | if (parse_config_key(var, "tar", &name, &namelen, &type) < 0 || !name) |
386 | 0 | return 0; |
387 | | |
388 | 0 | ar = find_tar_filter(name, namelen); |
389 | 0 | if (!ar) { |
390 | 0 | CALLOC_ARRAY(ar, 1); |
391 | 0 | ar->name = xmemdupz(name, namelen); |
392 | 0 | ar->write_archive = write_tar_filter_archive; |
393 | 0 | ar->flags = ARCHIVER_WANT_COMPRESSION_LEVELS | |
394 | 0 | ARCHIVER_HIGH_COMPRESSION_LEVELS; |
395 | 0 | ALLOC_GROW(tar_filters, nr_tar_filters + 1, alloc_tar_filters); |
396 | 0 | tar_filters[nr_tar_filters++] = ar; |
397 | 0 | } |
398 | |
|
399 | 0 | if (!strcmp(type, "command")) { |
400 | 0 | if (!value) |
401 | 0 | return config_error_nonbool(var); |
402 | 0 | free(ar->filter_command); |
403 | 0 | ar->filter_command = xstrdup(value); |
404 | 0 | return 0; |
405 | 0 | } |
406 | 0 | if (!strcmp(type, "remote")) { |
407 | 0 | if (git_config_bool(var, value)) |
408 | 0 | ar->flags |= ARCHIVER_REMOTE; |
409 | 0 | else |
410 | 0 | ar->flags &= ~ARCHIVER_REMOTE; |
411 | 0 | return 0; |
412 | 0 | } |
413 | | |
414 | 0 | return 0; |
415 | 0 | } |
416 | | |
417 | | static int git_tar_config(const char *var, const char *value, |
418 | | const struct config_context *ctx, void *cb) |
419 | 0 | { |
420 | 0 | if (!strcmp(var, "tar.umask")) { |
421 | 0 | if (value && !strcmp(value, "user")) { |
422 | 0 | tar_umask = umask(0); |
423 | 0 | umask(tar_umask); |
424 | 0 | } else { |
425 | 0 | tar_umask = git_config_int(var, value, ctx->kvi); |
426 | 0 | } |
427 | 0 | return 0; |
428 | 0 | } |
429 | | |
430 | 0 | return tar_filter_config(var, value, cb); |
431 | 0 | } |
432 | | |
433 | | static int write_tar_archive(const struct archiver *ar UNUSED, |
434 | | struct archiver_args *args) |
435 | 0 | { |
436 | 0 | int err = 0; |
437 | |
|
438 | 0 | write_global_extended_header(args); |
439 | 0 | err = write_archive_entries(args, write_tar_entry); |
440 | 0 | if (!err) |
441 | 0 | write_trailer(); |
442 | 0 | return err; |
443 | 0 | } |
444 | | |
445 | | static git_zstream gzstream; |
446 | | static unsigned char outbuf[16384]; |
447 | | |
448 | | static void tgz_deflate(int flush) |
449 | 0 | { |
450 | 0 | while (gzstream.avail_in || flush == Z_FINISH) { |
451 | 0 | int status = git_deflate(&gzstream, flush); |
452 | 0 | if (!gzstream.avail_out || status == Z_STREAM_END) { |
453 | 0 | write_or_die(1, outbuf, gzstream.next_out - outbuf); |
454 | 0 | gzstream.next_out = outbuf; |
455 | 0 | gzstream.avail_out = sizeof(outbuf); |
456 | 0 | if (status == Z_STREAM_END) |
457 | 0 | break; |
458 | 0 | } |
459 | 0 | if (status != Z_OK && status != Z_BUF_ERROR) |
460 | 0 | die(_("deflate error (%d)"), status); |
461 | 0 | } |
462 | 0 | } |
463 | | |
464 | | static void tgz_write_block(const void *data) |
465 | 0 | { |
466 | 0 | gzstream.next_in = (void *)data; |
467 | 0 | gzstream.avail_in = BLOCKSIZE; |
468 | 0 | tgz_deflate(Z_NO_FLUSH); |
469 | 0 | } |
470 | | |
471 | | static const char internal_gzip_command[] = "git archive gzip"; |
472 | | |
473 | | static int write_tar_filter_archive(const struct archiver *ar, |
474 | | struct archiver_args *args) |
475 | 0 | { |
476 | 0 | #if ZLIB_VERNUM >= 0x1221 |
477 | 0 | struct gz_header_s gzhead = { .os = 3 }; /* Unix, for reproducibility */ |
478 | 0 | #endif |
479 | 0 | struct strbuf cmd = STRBUF_INIT; |
480 | 0 | struct child_process filter = CHILD_PROCESS_INIT; |
481 | 0 | int r; |
482 | |
|
483 | 0 | if (!ar->filter_command) |
484 | 0 | BUG("tar-filter archiver called with no filter defined"); |
485 | | |
486 | 0 | if (!strcmp(ar->filter_command, internal_gzip_command)) { |
487 | 0 | write_block = tgz_write_block; |
488 | 0 | git_deflate_init_gzip(&gzstream, args->compression_level); |
489 | 0 | #if ZLIB_VERNUM >= 0x1221 |
490 | 0 | if (deflateSetHeader(&gzstream.z, &gzhead) != Z_OK) |
491 | 0 | BUG("deflateSetHeader() called too late"); |
492 | 0 | #endif |
493 | 0 | gzstream.next_out = outbuf; |
494 | 0 | gzstream.avail_out = sizeof(outbuf); |
495 | |
|
496 | 0 | r = write_tar_archive(ar, args); |
497 | |
|
498 | 0 | tgz_deflate(Z_FINISH); |
499 | 0 | git_deflate_end(&gzstream); |
500 | 0 | return r; |
501 | 0 | } |
502 | | |
503 | 0 | strbuf_addstr(&cmd, ar->filter_command); |
504 | 0 | if (args->compression_level >= 0) |
505 | 0 | strbuf_addf(&cmd, " -%d", args->compression_level); |
506 | |
|
507 | 0 | strvec_push(&filter.args, cmd.buf); |
508 | 0 | filter.use_shell = 1; |
509 | 0 | filter.in = -1; |
510 | 0 | filter.silent_exec_failure = 1; |
511 | |
|
512 | 0 | if (start_command(&filter) < 0) |
513 | 0 | die_errno(_("unable to start '%s' filter"), cmd.buf); |
514 | 0 | close(1); |
515 | 0 | if (dup2(filter.in, 1) < 0) |
516 | 0 | die_errno(_("unable to redirect descriptor")); |
517 | 0 | close(filter.in); |
518 | |
|
519 | 0 | r = write_tar_archive(ar, args); |
520 | |
|
521 | 0 | close(1); |
522 | 0 | if (finish_command(&filter) != 0) |
523 | 0 | die(_("'%s' filter reported error"), cmd.buf); |
524 | | |
525 | 0 | strbuf_release(&cmd); |
526 | 0 | return r; |
527 | 0 | } |
528 | | |
529 | | static struct archiver tar_archiver = { |
530 | | .name = "tar", |
531 | | .write_archive = write_tar_archive, |
532 | | .flags = ARCHIVER_REMOTE, |
533 | | }; |
534 | | |
535 | | void init_tar_archiver(void) |
536 | 0 | { |
537 | 0 | int i; |
538 | 0 | register_archiver(&tar_archiver); |
539 | |
|
540 | 0 | tar_filter_config("tar.tgz.command", internal_gzip_command, NULL); |
541 | 0 | tar_filter_config("tar.tgz.remote", "true", NULL); |
542 | 0 | tar_filter_config("tar.tar.gz.command", internal_gzip_command, NULL); |
543 | 0 | tar_filter_config("tar.tar.gz.remote", "true", NULL); |
544 | 0 | git_config(git_tar_config, NULL); |
545 | 0 | for (i = 0; i < nr_tar_filters; i++) { |
546 | | /* omit any filters that never had a command configured */ |
547 | 0 | if (tar_filters[i]->filter_command) |
548 | 0 | register_archiver(tar_filters[i]); |
549 | 0 | } |
550 | 0 | } |