archive: implement configurable tar filters
It's common to pipe the tar output produce by "git archive" through gzip or some other compressor. Locally, this can easily be done by using a shell pipe. When requesting a remote archive, though, it cannot be done through the upload-archive interface. This patch allows configurable tar filters, so that one could define a "tar.gz" format that automatically pipes tar output through gzip. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
							parent
							
								
									08716b3c11
								
							
						
					
					
						commit
						767cf4579f
					
				|  | @ -101,6 +101,16 @@ tar.umask:: | |||
| 	details.  If `--remote` is used then only the configuration of | ||||
| 	the remote repository takes effect. | ||||
|  | ||||
| tar.<format>.command:: | ||||
| 	This variable specifies a shell command through which the tar | ||||
| 	output generated by `git archive` should be piped. The command | ||||
| 	is executed using the shell with the generated tar file on its | ||||
| 	standard input, and should produce the final output on its | ||||
| 	standard output. Any compression-level options will be passed | ||||
| 	to the command (e.g., "-9"). An output file with the same | ||||
| 	extension as `<format>` will be use this format if no other | ||||
| 	format is given. | ||||
|  | ||||
| ATTRIBUTES | ||||
| ---------- | ||||
|  | ||||
|  | @ -149,6 +159,12 @@ git archive -o latest.zip HEAD:: | |||
| 	commit on the current branch. Note that the output format is | ||||
| 	inferred by the extension of the output file. | ||||
|  | ||||
| git config tar.tar.xz.command "xz -c":: | ||||
|  | ||||
| 	Configure a "tar.xz" format for making LZMA-compressed tarfiles. | ||||
| 	You can use it specifying `--format=tar.xz`, or by creating an | ||||
| 	output file like `-o foo.tar.xz`. | ||||
|  | ||||
|  | ||||
| SEE ALSO | ||||
| -------- | ||||
|  |  | |||
							
								
								
									
										107
									
								
								archive-tar.c
								
								
								
								
							
							
						
						
									
										107
									
								
								archive-tar.c
								
								
								
								
							|  | @ -4,6 +4,7 @@ | |||
| #include "cache.h" | ||||
| #include "tar.h" | ||||
| #include "archive.h" | ||||
| #include "run-command.h" | ||||
|  | ||||
| #define RECORDSIZE	(512) | ||||
| #define BLOCKSIZE	(RECORDSIZE * 20) | ||||
|  | @ -13,6 +14,9 @@ static unsigned long offset; | |||
|  | ||||
| static int tar_umask = 002; | ||||
|  | ||||
| static int write_tar_filter_archive(const struct archiver *ar, | ||||
| 				    struct archiver_args *args); | ||||
|  | ||||
| /* writes out the whole block, but only if it is full */ | ||||
| static void write_if_needed(void) | ||||
| { | ||||
|  | @ -220,6 +224,60 @@ static int write_global_extended_header(struct archiver_args *args) | |||
| 	return err; | ||||
| } | ||||
|  | ||||
| static struct archiver **tar_filters; | ||||
| static int nr_tar_filters; | ||||
| static int alloc_tar_filters; | ||||
|  | ||||
| static struct archiver *find_tar_filter(const char *name, int len) | ||||
| { | ||||
| 	int i; | ||||
| 	for (i = 0; i < nr_tar_filters; i++) { | ||||
| 		struct archiver *ar = tar_filters[i]; | ||||
| 		if (!strncmp(ar->name, name, len) && !ar->name[len]) | ||||
| 			return ar; | ||||
| 	} | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| static int tar_filter_config(const char *var, const char *value, void *data) | ||||
| { | ||||
| 	struct archiver *ar; | ||||
| 	const char *dot; | ||||
| 	const char *name; | ||||
| 	const char *type; | ||||
| 	int namelen; | ||||
|  | ||||
| 	if (prefixcmp(var, "tar.")) | ||||
| 		return 0; | ||||
| 	dot = strrchr(var, '.'); | ||||
| 	if (dot == var + 9) | ||||
| 		return 0; | ||||
|  | ||||
| 	name = var + 4; | ||||
| 	namelen = dot - name; | ||||
| 	type = dot + 1; | ||||
|  | ||||
| 	ar = find_tar_filter(name, namelen); | ||||
| 	if (!ar) { | ||||
| 		ar = xcalloc(1, sizeof(*ar)); | ||||
| 		ar->name = xmemdupz(name, namelen); | ||||
| 		ar->write_archive = write_tar_filter_archive; | ||||
| 		ar->flags = ARCHIVER_WANT_COMPRESSION_LEVELS; | ||||
| 		ALLOC_GROW(tar_filters, nr_tar_filters + 1, alloc_tar_filters); | ||||
| 		tar_filters[nr_tar_filters++] = ar; | ||||
| 	} | ||||
|  | ||||
| 	if (!strcmp(type, "command")) { | ||||
| 		if (!value) | ||||
| 			return config_error_nonbool(var); | ||||
| 		free(ar->data); | ||||
| 		ar->data = xstrdup(value); | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int git_tar_config(const char *var, const char *value, void *cb) | ||||
| { | ||||
| 	if (!strcmp(var, "tar.umask")) { | ||||
|  | @ -231,7 +289,8 @@ static int git_tar_config(const char *var, const char *value, void *cb) | |||
| 		} | ||||
| 		return 0; | ||||
| 	} | ||||
| 	return 0; | ||||
|  | ||||
| 	return tar_filter_config(var, value, cb); | ||||
| } | ||||
|  | ||||
| static int write_tar_archive(const struct archiver *ar, | ||||
|  | @ -248,6 +307,45 @@ static int write_tar_archive(const struct archiver *ar, | |||
| 	return err; | ||||
| } | ||||
|  | ||||
| static int write_tar_filter_archive(const struct archiver *ar, | ||||
| 				    struct archiver_args *args) | ||||
| { | ||||
| 	struct strbuf cmd = STRBUF_INIT; | ||||
| 	struct child_process filter; | ||||
| 	const char *argv[2]; | ||||
| 	int r; | ||||
|  | ||||
| 	if (!ar->data) | ||||
| 		die("BUG: tar-filter archiver called with no filter defined"); | ||||
|  | ||||
| 	strbuf_addstr(&cmd, ar->data); | ||||
| 	if (args->compression_level >= 0) | ||||
| 		strbuf_addf(&cmd, " -%d", args->compression_level); | ||||
|  | ||||
| 	memset(&filter, 0, sizeof(filter)); | ||||
| 	argv[0] = cmd.buf; | ||||
| 	argv[1] = NULL; | ||||
| 	filter.argv = argv; | ||||
| 	filter.use_shell = 1; | ||||
| 	filter.in = -1; | ||||
|  | ||||
| 	if (start_command(&filter) < 0) | ||||
| 		die_errno("unable to start '%s' filter", argv[0]); | ||||
| 	close(1); | ||||
| 	if (dup2(filter.in, 1) < 0) | ||||
| 		die_errno("unable to redirect descriptor"); | ||||
| 	close(filter.in); | ||||
|  | ||||
| 	r = write_tar_archive(ar, args); | ||||
|  | ||||
| 	close(1); | ||||
| 	if (finish_command(&filter) != 0) | ||||
| 		die("'%s' filter reported error", argv[0]); | ||||
|  | ||||
| 	strbuf_release(&cmd); | ||||
| 	return r; | ||||
| } | ||||
|  | ||||
| static struct archiver tar_archiver = { | ||||
| 	"tar", | ||||
| 	write_tar_archive, | ||||
|  | @ -256,6 +354,13 @@ static struct archiver tar_archiver = { | |||
|  | ||||
| void init_tar_archiver(void) | ||||
| { | ||||
| 	int i; | ||||
| 	register_archiver(&tar_archiver); | ||||
|  | ||||
| 	git_config(git_tar_config, NULL); | ||||
| 	for (i = 0; i < nr_tar_filters; i++) { | ||||
| 		/* omit any filters that never had a command configured */ | ||||
| 		if (tar_filters[i]->data) | ||||
| 			register_archiver(tar_filters[i]); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -252,4 +252,47 @@ test_expect_success 'git-archive --prefix=olde-' ' | |||
| 	test -f h/olde-a/bin/sh | ||||
| ' | ||||
|  | ||||
| test_expect_success 'setup tar filters' ' | ||||
| 	git config tar.tar.foo.command "tr ab ba" && | ||||
| 	git config tar.bar.command "tr ab ba" | ||||
| ' | ||||
|  | ||||
| test_expect_success 'archive --list mentions user filter' ' | ||||
| 	git archive --list >output && | ||||
| 	grep "^tar\.foo\$" output && | ||||
| 	grep "^bar\$" output | ||||
| ' | ||||
|  | ||||
| test_expect_success 'archive --list shows remote user filters' ' | ||||
| 	git archive --list --remote=. >output && | ||||
| 	grep "^tar\.foo\$" output && | ||||
| 	grep "^bar\$" output | ||||
| ' | ||||
|  | ||||
| test_expect_success 'invoke tar filter by format' ' | ||||
| 	git archive --format=tar.foo HEAD >config.tar.foo && | ||||
| 	tr ab ba <config.tar.foo >config.tar && | ||||
| 	test_cmp b.tar config.tar && | ||||
| 	git archive --format=bar HEAD >config.bar && | ||||
| 	tr ab ba <config.bar >config.tar && | ||||
| 	test_cmp b.tar config.tar | ||||
| ' | ||||
|  | ||||
| test_expect_success 'invoke tar filter by extension' ' | ||||
| 	git archive -o config-implicit.tar.foo HEAD && | ||||
| 	test_cmp config.tar.foo config-implicit.tar.foo && | ||||
| 	git archive -o config-implicit.bar HEAD && | ||||
| 	test_cmp config.tar.foo config-implicit.bar | ||||
| ' | ||||
|  | ||||
| test_expect_success 'default output format remains tar' ' | ||||
| 	git archive -o config-implicit.baz HEAD && | ||||
| 	test_cmp b.tar config-implicit.baz | ||||
| ' | ||||
|  | ||||
| test_expect_success 'extension matching requires dot' ' | ||||
| 	git archive -o config-implicittar.foo HEAD && | ||||
| 	test_cmp b.tar config-implicittar.foo | ||||
| ' | ||||
|  | ||||
| test_done | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Jeff King
						Jeff King