diff mbox series

[v6,1/7] archive: optionally add "virtual" files

Message ID 0005cfae31d52a157d4df5ba3db9f9f5b2167ddc.1653145696.git.gitgitgadget@gmail.com (mailing list archive)
State New, archived
Headers show
Series scalar: implement the subcommand "diagnose" | expand

Commit Message

Johannes Schindelin May 21, 2022, 3:08 p.m. UTC
From: Johannes Schindelin <johannes.schindelin@gmx.de>

With the `--add-virtual-file=<path>:<content>` option, `git archive` now
supports use cases where relatively trivial files need to be added that
do not exist on disk.

This will allow us to generate `.zip` files with generated content,
without having to add said content to the object database and without
having to write it out to disk.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Documentation/git-archive.txt | 11 ++++++++
 archive.c                     | 53 +++++++++++++++++++++++++++++------
 t/t5003-archive-zip.sh        | 12 ++++++++
 3 files changed, 68 insertions(+), 8 deletions(-)

Comments

Junio C Hamano May 25, 2022, 9:11 p.m. UTC | #1
"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> @@ -61,6 +61,17 @@ OPTIONS
>  	by concatenating the value for `--prefix` (if any) and the
>  	basename of <file>.
>  
> +--add-virtual-file=<path>:<content>::
> +	Add the specified contents to the archive.  Can be repeated to add
> +	multiple files.  The path of the file in the archive is built
> +	by concatenating the value for `--prefix` (if any) and the
> +	basename of <file>.

This sentence was copy-pasted from --add-file without adjusting.
There is no <file>; this new feature gives <path>.

Also, I suspect that the feature is losing end-user supplied
information without a good reason.  --add-file=<file> may have
prepared an input in a randomly named temporary directory and it
would make quite a lot of sense to strip the leading directory
components from <file> and use only the basename part.  But the
<path> given to "--add-virtual-file" does not refer to anything on
the filesystem.  Its ONLY use is to be used as the path in the
archive to store the content.  There is no justification why we
would discard the leading path components from it.  I am not
decided, but I am inclined to say that we should not honor
"--prefix".

   $ git archive --prefix=2.36.0 v2.36.0

would be a way to create a single directory and put everything in
the tree-ish in there, but there probably are cases where the user
of an "extra file" feature wants to add untracked cruft _in_ that
directory, and there are other cases where an extra file wants to go
to the top-level next to the 2.36.0 directory.  A user can use the
same string as --prefix=<base> in front of <path> if the extra file
should go next to the top-level of the tree-ish, or without such
prefixing to place the extra file at the top-level.

Hence

	Add the specified contents to the archive.  Can be repeated
	to add multiple files.  `<path>` is used as the path of the
	file in the archive.
	
would be what I would expect in a version of this feature that is
reasonably designed.

> ++
> +The `<path>` cannot contain any colon, the file mode is limited to
> +a regular file, and the option may be subject to platform-dependent
> +command-line limits. For non-trivial cases, write an untracked file
> +and use `--add-file` instead.

OK.

> diff --git a/archive.c b/archive.c
> index a3bbb091256..d20e16fa819 100644
> --- a/archive.c
> +++ b/archive.c
> @@ -263,6 +263,7 @@ static int queue_or_write_archive_entry(const struct object_id *oid,
>  struct extra_file_info {
>  	char *base;
>  	struct stat stat;
> +	void *content;
>  };
>  
>  int write_archive_entries(struct archiver_args *args,
> @@ -337,7 +338,13 @@ int write_archive_entries(struct archiver_args *args,
>  		strbuf_addstr(&path_in_archive, basename(path));
>  
>  		strbuf_reset(&content);
> -		if (strbuf_read_file(&content, path, info->stat.st_size) < 0)
> +		if (info->content)

We ended up with the problematic "leading <path> components are
discarded" design only because the implementation reuses the logic
path_in_archive computation (the last line is seen in precontext),
which is a bit unfortunate.  I think we could rewrite the inside of
that "for each extra file" loop like so, instead:

	for (i = 0; i < args->extra_files.nr; i++) {
		struct string_list_item *item = args->extra_files.items + i;
		char *path = item->string;
		struct extra_file_info *info = item->util;

		put_be64(fake_oid.hash, i + 1);

		if (!info->content) {
			strbuf_reset(&path_in_archive);
			if (info->base)
				strbuf_addstr(&path_in_archive, info->base);
			strbuf_addstr(&path_in_archive, basename(path));

			strbuf_reset(&content);
			if (strbuf_read_file(&content, path, info->stat.st_size) < 0)
				err = error_errno(_("could not read '%s'"), path);
			else
				err = write_entry(args, &fake_oid, path_in_archive.buf,
						  path_in_archive.len,
						  info->stat.st_mode,
						  content.buf, content.len);
		} else {
			err = write_entry(args, &fake_oid,
					  path, strlen(path),
					  info->stat.st_mode,
					  info->content, info->stat.st_size);
		}

		if (err)
			break;
	}

The first half is the original code for "--add-file", which clears
info->content to NULL.  We mangle the filename to come up with the
name in the archive (i.e. take basename and prefix with info->base).

The "else" side is the new code.  "--add-virtual-file" has the
"<path>" thing in item->string, and info has the contents, so we
just write it out.
René Scharfe May 26, 2022, 9:09 a.m. UTC | #2
Am 25.05.22 um 23:11 schrieb Junio C Hamano:
> "Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
> writes:
>
>> @@ -61,6 +61,17 @@ OPTIONS
>>  	by concatenating the value for `--prefix` (if any) and the
>>  	basename of <file>.
>>
>> +--add-virtual-file=<path>:<content>::
>> +	Add the specified contents to the archive.  Can be repeated to add
>> +	multiple files.  The path of the file in the archive is built
>> +	by concatenating the value for `--prefix` (if any) and the
>> +	basename of <file>.
>
> This sentence was copy-pasted from --add-file without adjusting.
> There is no <file>; this new feature gives <path>.
>
> Also, I suspect that the feature is losing end-user supplied
> information without a good reason.  --add-file=<file> may have
> prepared an input in a randomly named temporary directory and it
> would make quite a lot of sense to strip the leading directory
> components from <file> and use only the basename part.  But the
> <path> given to "--add-virtual-file" does not refer to anything on
> the filesystem.  Its ONLY use is to be used as the path in the
> archive to store the content.  There is no justification why we
> would discard the leading path components from it.

Good point.

>  I am not
> decided, but I am inclined to say that we should not honor
> "--prefix".
>
>    $ git archive --prefix=2.36.0 v2.36.0
>
> would be a way to create a single directory and put everything in
> the tree-ish in there, but there probably are cases where the user
> of an "extra file" feature wants to add untracked cruft _in_ that
> directory, and there are other cases where an extra file wants to go
> to the top-level next to the 2.36.0 directory.  A user can use the
> same string as --prefix=<base> in front of <path> if the extra file
> should go next to the top-level of the tree-ish, or without such
> prefixing to place the extra file at the top-level.

If the prefix is applied then a prefix-less extra file can by had by
using --prefix= or --no-prefix for it and --prefix=... for the tree,
e.g.:

   $ git archive --add-file=extra --prefix=dir/ v2.36.0

puts "extra" at the root and the rest under "dir".  The order of
arguments matters here, and the default prefix is the empty string.

So extra files can be put anywhere even if --prefix is honored.

Keeping the whole path from --add-virtual-file makes sense to me; I
slightly prefer applying --prefix on top of that for consistency.

René
Junio C Hamano May 26, 2022, 5:10 p.m. UTC | #3
René Scharfe <l.s.r@web.de> writes:

> If the prefix is applied then a prefix-less extra file can by had by
> using --prefix= or --no-prefix for it and --prefix=... for the tree,
> e.g.:
>
>    $ git archive --add-file=extra --prefix=dir/ v2.36.0
>
> puts "extra" at the root and the rest under "dir".  The order of
> arguments matters here, and the default prefix is the empty string.

This was the part of the design for the original "--add-file" that I
was moderately unhappy with.  If "--add-file" were the only feature
that used "--prefix", I wouldn't have been unhappy, but this rule:

        The value of "--prefix" most recently seen at the point of
        "--add-file" is prepended.  (By the way, it is not clearly
        documented what happens when you give multiple prefix and
        when you give prefix before or after add-file)

makes the original use of "--prefix":

	The value given to "--prefix" is prepended to each filename
	in the archive.  (IOW "git archive --prefix=git-2.36.0/
	v2.36.0" is a way to prefix each and every path in the
	tree-ish with the given prefix)

confusing.  Does

	git archive --prefix=bonus-files/ --add-file=extra v2.36.0

place the main part of the archive also in bonus-files/ or at the
top level?  One reasonable interpretation is "yes", if we imagine
that each invocation of --add-file will consume and reset the prefix.
Another reasonable interpretation is "no", if we imagine that the
prefix last specified will stay around and equally affect both extra
ones and main part of the archive.

Unfortunately what the implmentation does is the latter, and those
who want to put the main part of the archive at the top-level must
add "--prefix=''" at the end (before the tree-ish).

Because of this potential for confusion ...

> So extra files can be put anywhere even if --prefix is honored.
>
> Keeping the whole path from --add-virtual-file makes sense to me; I
> slightly prefer applying --prefix on top of that for consistency.

... I was hoping that we can releave users from having to worry
about the interaction between "prefix" and contents coming from
outside the tree-ish by ignoring the "prefix".

But either is fine by me.

Thanks.
René Scharfe May 26, 2022, 6:57 p.m. UTC | #4
Am 26.05.22 um 19:10 schrieb Junio C Hamano:
> René Scharfe <l.s.r@web.de> writes:
>
>> If the prefix is applied then a prefix-less extra file can by had by
>> using --prefix= or --no-prefix for it and --prefix=... for the tree,
>> e.g.:
>>
>>    $ git archive --add-file=extra --prefix=dir/ v2.36.0
>>
>> puts "extra" at the root and the rest under "dir".  The order of
>> arguments matters here, and the default prefix is the empty string.
>
> This was the part of the design for the original "--add-file" that I
> was moderately unhappy with.  If "--add-file" were the only feature
> that used "--prefix", I wouldn't have been unhappy, but this rule:
>
>         The value of "--prefix" most recently seen at the point of
>         "--add-file" is prepended.  (By the way, it is not clearly
>         documented what happens when you give multiple prefix and
>         when you give prefix before or after add-file)

Regarding documentation: I wonder what's missing; a guess is below.

>
> makes the original use of "--prefix":
>
> 	The value given to "--prefix" is prepended to each filename
> 	in the archive.  (IOW "git archive --prefix=git-2.36.0/
> 	v2.36.0" is a way to prefix each and every path in the
> 	tree-ish with the given prefix)
>
> confusing.  Does
>
> 	git archive --prefix=bonus-files/ --add-file=extra v2.36.0
>
> place the main part of the archive also in bonus-files/ or at the
> top level?  One reasonable interpretation is "yes", if we imagine
> that each invocation of --add-file will consume and reset the prefix.
> Another reasonable interpretation is "no", if we imagine that the
> prefix last specified will stay around and equally affect both extra
> ones and main part of the archive.
>
> Unfortunately what the implmentation does is the latter, and those
> who want to put the main part of the archive at the top-level must
> add "--prefix=''" at the end (before the tree-ish).

A one-shot --prefix would be surprising -- usually options keep their
value until they are specified again with a different value or negated
(--no-...).  That surprise could be documented away by using a
different name like --next-prefix or --single-use-prefix.  But a
sub-option to a single option like that would probably be better baked
into that option, e.g. allow --add-file=<path_in_archive>:<path_in_fs>.

>
> Because of this potential for confusion ...
>
>> So extra files can be put anywhere even if --prefix is honored.
>>
>> Keeping the whole path from --add-virtual-file makes sense to me; I
>> slightly prefer applying --prefix on top of that for consistency.
>
> ... I was hoping that we can releave users from having to worry
> about the interaction between "prefix" and contents coming from
> outside the tree-ish by ignoring the "prefix".
>
> But either is fine by me.

The unusual thing about the current --prefix implementation is that its
current value is captured along the way instead of just using its
right-most value.  Not sure ignoring it for one of the three archive
content sources helps.  (Really, it's hard for me to put me in the shoes
of someone who doesn't know how these options are supposed to be used.)


--- >8 ---
Subject: [PATCH] archive: improve documentation of --prefix

Document the interaction between --add-file and --prefix by giving an
example.

Signed-off-by: René Scharfe <l.s.r@web.de>
---
 Documentation/git-archive.txt | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt
index bc4e76a783..10a48ab5f8 100644
--- a/Documentation/git-archive.txt
+++ b/Documentation/git-archive.txt
@@ -49,7 +49,9 @@ OPTIONS
 	Report progress to stderr.

 --prefix=<prefix>/::
-	Prepend <prefix>/ to each filename in the archive.
+	Prepend <prefix>/ to each filename in the archive.  Can be
+	specified multiple times; the last one seen when reading from
+	left to right is applied.

 -o <file>::
 --output=<file>::
@@ -58,8 +60,8 @@ OPTIONS
 --add-file=<file>::
 	Add a non-tracked file to the archive.  Can be repeated to add
 	multiple files.  The path of the file in the archive is built
-	by concatenating the value for `--prefix` (if any) and the
-	basename of <file>.
+	by concatenating the current value for `--prefix` (if any) and
+	the basename of <file>.

 --worktree-attributes::
 	Look for attributes in .gitattributes files in the working tree
@@ -194,6 +196,12 @@ EXAMPLES
 	commit on the current branch. Note that the output format is
 	inferred by the extension of the output file.

+`git archive -o latest.tar --prefix=build/ --add-file=configure --prefix= HEAD`::
+
+	Creates a tar archive that contains the contents of the latest
+	commit on the current branch with no prefix and the untracked
+	file 'configure' with the prefix 'build/'.
+
 `git config tar.tar.xz.command "xz -c"`::

 	Configure a "tar.xz" format for making LZMA-compressed tarfiles.
--
2.35.3
Junio C Hamano May 26, 2022, 8:16 p.m. UTC | #5
René Scharfe <l.s.r@web.de> writes:

> diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt
> index bc4e76a783..10a48ab5f8 100644
> --- a/Documentation/git-archive.txt
> +++ b/Documentation/git-archive.txt
> @@ -49,7 +49,9 @@ OPTIONS
>  	Report progress to stderr.
>
>  --prefix=<prefix>/::
> -	Prepend <prefix>/ to each filename in the archive.
> +	Prepend <prefix>/ to each filename in the archive.  Can be
> +	specified multiple times; the last one seen when reading from
> +	left to right is applied.

That can be read to mean that we will use C consistently,

$ cmd --prefix=A other-args --prefix=B other-args --prefix=C other-args

which was what I am worried to be a source of confusion.

>  -o <file>::
>  --output=<file>::
> @@ -58,8 +60,8 @@ OPTIONS
>  --add-file=<file>::
>  	Add a non-tracked file to the archive.  Can be repeated to add
>  	multiple files.  The path of the file in the archive is built
> -	by concatenating the value for `--prefix` (if any) and the
> -	basename of <file>.
> +	by concatenating the current value for `--prefix` (if any) and
> +	the basename of <file>.

"the current value for `--prefix` (if any)" would work well once we
somehow make the reader form a mental model that there is "the
current" for the "prefix", which starts with an empty string, and
gets updated every time the "--prefix=<prefix>/" option is given.

So, perhaps with

	--prefix=<prefix>/::
		The paths of the files in the tree being archived,
		and untracked contents added via the `--add-file`
		and `--add-virtual-file` options, can be modified by
		prepending the "prefix" value that is in effect when
		these options or the tree object is seen on the
		command line.  The "prefix" value initially starts
		as an empty string, and it gets updated every time
		this option is given on the command line.

or something like that, with something like

> +	by concatenating the current value for "prefix" (see `--prefix`
> +	above) and the basename of <file>.

here, it might make it less misunderstanding-prone, hopefully?

> +`git archive -o latest.tar --prefix=build/ --add-file=configure --prefix= HEAD`::
> +
> +	Creates a tar archive that contains the contents of the latest
> +	commit on the current branch with no prefix and the untracked
> +	file 'configure' with the prefix 'build/'.

Great to have this example.

Thanks.
René Scharfe May 27, 2022, 5:02 p.m. UTC | #6
Am 26.05.22 um 22:16 schrieb Junio C Hamano:
> René Scharfe <l.s.r@web.de> writes:
>
>> diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt
>> index bc4e76a783..10a48ab5f8 100644
>> --- a/Documentation/git-archive.txt
>> +++ b/Documentation/git-archive.txt
>> @@ -49,7 +49,9 @@ OPTIONS
>>  	Report progress to stderr.
>>
>>  --prefix=<prefix>/::
>> -	Prepend <prefix>/ to each filename in the archive.
>> +	Prepend <prefix>/ to each filename in the archive.  Can be
>> +	specified multiple times; the last one seen when reading from
>> +	left to right is applied.
>
> That can be read to mean that we will use C consistently,
>
> $ cmd --prefix=A other-args --prefix=B other-args --prefix=C other-args
>
> which was what I am worried to be a source of confusion.
>
>>  -o <file>::
>>  --output=<file>::
>> @@ -58,8 +60,8 @@ OPTIONS
>>  --add-file=<file>::
>>  	Add a non-tracked file to the archive.  Can be repeated to add
>>  	multiple files.  The path of the file in the archive is built
>> -	by concatenating the value for `--prefix` (if any) and the
>> -	basename of <file>.
>> +	by concatenating the current value for `--prefix` (if any) and
>> +	the basename of <file>.
>
> "the current value for `--prefix` (if any)" would work well once we
> somehow make the reader form a mental model that there is "the
> current" for the "prefix", which starts with an empty string, and
> gets updated every time the "--prefix=<prefix>/" option is given.

Right, "current" has a well-known meaning, but its not enough to convey
that the non-standard concept of capturing option values in the middle of
the argument list is used here.

>
> So, perhaps with
>
> 	--prefix=<prefix>/::
> 		The paths of the files in the tree being archived,
> 		and untracked contents added via the `--add-file`
> 		and `--add-virtual-file` options, can be modified by
> 		prepending the "prefix" value that is in effect when
> 		these options or the tree object is seen on the
> 		command line.  The "prefix" value initially starts
> 		as an empty string, and it gets updated every time
> 		this option is given on the command line.
>
> or something like that, with something like
>
>> +	by concatenating the current value for "prefix" (see `--prefix`
>> +	above) and the basename of <file>.
>
> here, it might make it less misunderstanding-prone, hopefully?

So how about this, which avoids mentioning the idea of a "current"
option, or of updating its value (which implies an order that might not
be obvious)?

--- >8 ---
Subject: [PATCH v2] archive: improve documentation of --prefix

Document the interaction between --add-file and --prefix by giving an
example.

Signed-off-by: René Scharfe <l.s.r@web.de>
---
 Documentation/git-archive.txt | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt
index bc4e76a783..9c0e306c03 100644
--- a/Documentation/git-archive.txt
+++ b/Documentation/git-archive.txt
@@ -49,7 +49,9 @@ OPTIONS
 	Report progress to stderr.

 --prefix=<prefix>/::
-	Prepend <prefix>/ to each filename in the archive.
+	Prepend <prefix>/ to paths in the archive.  Can be repeated; its
+	leftmost value is used for all tracked files.  See below which
+	value gets used by `--add-file`.

 -o <file>::
 --output=<file>::
@@ -58,8 +60,9 @@ OPTIONS
 --add-file=<file>::
 	Add a non-tracked file to the archive.  Can be repeated to add
 	multiple files.  The path of the file in the archive is built
-	by concatenating the value for `--prefix` (if any) and the
-	basename of <file>.
+	by concatenating the value of the leftmost `--prefix` option to
+	the right of this `--add-file` (if any) and the basename of
+	<file>.

 --worktree-attributes::
 	Look for attributes in .gitattributes files in the working tree
@@ -194,6 +197,12 @@ EXAMPLES
 	commit on the current branch. Note that the output format is
 	inferred by the extension of the output file.

+`git archive -o latest.tar --prefix=build/ --add-file=configure --prefix= HEAD`::
+
+	Creates a tar archive that contains the contents of the latest
+	commit on the current branch with no prefix and the untracked
+	file 'configure' with the prefix 'build/'.
+
 `git config tar.tar.xz.command "xz -c"`::

 	Configure a "tar.xz" format for making LZMA-compressed tarfiles.
--
2.35.3
Junio C Hamano May 27, 2022, 7:01 p.m. UTC | #7
René Scharfe <l.s.r@web.de> writes:

>  --prefix=<prefix>/::
> -	Prepend <prefix>/ to each filename in the archive.
> +	Prepend <prefix>/ to paths in the archive.  Can be repeated; its
> +	leftmost value is used for all tracked files.  See below which
> +	value gets used by `--add-file`.

Doesn't "the last one wins" take the rightmost one?

> @@ -58,8 +60,9 @@ OPTIONS
>  --add-file=<file>::
>  	Add a non-tracked file to the archive.  Can be repeated to add
>  	multiple files.  The path of the file in the archive is built
> -	by concatenating the value for `--prefix` (if any) and the
> -	basename of <file>.
> +	by concatenating the value of the leftmost `--prefix` option to
> +	the right of this `--add-file` (if any) and the basename of
> +	<file>.

It is not what archive.c::add_file_cb() seems to be doing, though

It is passed the pointer to "base" that is on-stack of
parse_archive_args(), which is the same variable that is used to
remember the latest value that was given to "--prefix".  Then it
concatenates the argument it received after that base value, so

    by concatenating the value of the last "--prefix" seen on the
    command line (if any) before this `--add-file` and the basename
    of <file>.

probably.  I always get my left and right mixed up X-<.

> @@ -194,6 +197,12 @@ EXAMPLES
>  	commit on the current branch. Note that the output format is
>  	inferred by the extension of the output file.
>
> +`git archive -o latest.tar --prefix=build/ --add-file=configure --prefix= HEAD`::
> +
> +	Creates a tar archive that contains the contents of the latest
> +	commit on the current branch with no prefix and the untracked
> +	file 'configure' with the prefix 'build/'.
> +
>  `git config tar.tar.xz.command "xz -c"`::
>
>  	Configure a "tar.xz" format for making LZMA-compressed tarfiles.

Thanks.

This patch probably needs to come before the "scalar diagnose"
series, which we haven't heard much about recently (no, I am not
complaining---we all heard that Dscho is busy).
René Scharfe May 28, 2022, 6:57 a.m. UTC | #8
Am 27.05.22 um 21:01 schrieb Junio C Hamano:
> René Scharfe <l.s.r@web.de> writes:
>
>>  --prefix=<prefix>/::
>> -	Prepend <prefix>/ to each filename in the archive.
>> +	Prepend <prefix>/ to paths in the archive.  Can be repeated; its
>> +	leftmost value is used for all tracked files.  See below which
>> +	value gets used by `--add-file`.
>
> Doesn't "the last one wins" take the rightmost one?

Ha ha!  Classic mistake, I do that all the time, especially when in a
hurry. >_<

>
>> @@ -58,8 +60,9 @@ OPTIONS
>>  --add-file=<file>::
>>  	Add a non-tracked file to the archive.  Can be repeated to add
>>  	multiple files.  The path of the file in the archive is built
>> -	by concatenating the value for `--prefix` (if any) and the
>> -	basename of <file>.
>> +	by concatenating the value of the leftmost `--prefix` option to
>> +	the right of this `--add-file` (if any) and the basename of
>> +	<file>.
>
> It is not what archive.c::add_file_cb() seems to be doing, though
>
> It is passed the pointer to "base" that is on-stack of
> parse_archive_args(), which is the same variable that is used to
> remember the latest value that was given to "--prefix".  Then it
> concatenates the argument it received after that base value, so
>
>     by concatenating the value of the last "--prefix" seen on the
>     command line (if any) before this `--add-file` and the basename
>     of <file>.
>
> probably.  I always get my left and right mixed up X-<.

You too?  So yeah, avoiding the terms is appealing.

>
>> @@ -194,6 +197,12 @@ EXAMPLES
>>  	commit on the current branch. Note that the output format is
>>  	inferred by the extension of the output file.
>>
>> +`git archive -o latest.tar --prefix=build/ --add-file=configure --prefix= HEAD`::
>> +
>> +	Creates a tar archive that contains the contents of the latest
>> +	commit on the current branch with no prefix and the untracked
>> +	file 'configure' with the prefix 'build/'.
>> +
>>  `git config tar.tar.xz.command "xz -c"`::
>>
>>  	Configure a "tar.xz" format for making LZMA-compressed tarfiles.
>
> Thanks.
>
> This patch probably needs to come before the "scalar diagnose"
> series, which we haven't heard much about recently (no, I am not
> complaining---we all heard that Dscho is busy).
>
>

--- >8 ---
Subject: [PATCH v3] archive: improve documentation of --prefix

Document the interaction between --add-file and --prefix by giving an
example.

Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: René Scharfe <l.s.r@web.de>
---
 Documentation/git-archive.txt | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt
index bc4e76a783..94519aae23 100644
--- a/Documentation/git-archive.txt
+++ b/Documentation/git-archive.txt
@@ -49,7 +49,9 @@ OPTIONS
 	Report progress to stderr.

 --prefix=<prefix>/::
-	Prepend <prefix>/ to each filename in the archive.
+	Prepend <prefix>/ to paths in the archive.  Can be repeated; its
+	rightmost value is used for all tracked files.  See below which
+	value gets used by `--add-file`.

 -o <file>::
 --output=<file>::
@@ -57,9 +59,9 @@ OPTIONS

 --add-file=<file>::
 	Add a non-tracked file to the archive.  Can be repeated to add
-	multiple files.  The path of the file in the archive is built
-	by concatenating the value for `--prefix` (if any) and the
-	basename of <file>.
+	multiple files.  The path of the file in the archive is built by
+	concatenating the value of the last `--prefix` option (if any)
+	before this `--add-file` and the basename of <file>.

 --worktree-attributes::
 	Look for attributes in .gitattributes files in the working tree
@@ -194,6 +196,12 @@ EXAMPLES
 	commit on the current branch. Note that the output format is
 	inferred by the extension of the output file.

+`git archive -o latest.tar --prefix=build/ --add-file=configure --prefix= HEAD`::
+
+	Creates a tar archive that contains the contents of the latest
+	commit on the current branch with no prefix and the untracked
+	file 'configure' with the prefix 'build/'.
+
 `git config tar.tar.xz.command "xz -c"`::

 	Configure a "tar.xz" format for making LZMA-compressed tarfiles.
--
2.35.3
diff mbox series

Patch

diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt
index bc4e76a7834..893cb1075bf 100644
--- a/Documentation/git-archive.txt
+++ b/Documentation/git-archive.txt
@@ -61,6 +61,17 @@  OPTIONS
 	by concatenating the value for `--prefix` (if any) and the
 	basename of <file>.
 
+--add-virtual-file=<path>:<content>::
+	Add the specified contents to the archive.  Can be repeated to add
+	multiple files.  The path of the file in the archive is built
+	by concatenating the value for `--prefix` (if any) and the
+	basename of <file>.
++
+The `<path>` cannot contain any colon, the file mode is limited to
+a regular file, and the option may be subject to platform-dependent
+command-line limits. For non-trivial cases, write an untracked file
+and use `--add-file` instead.
+
 --worktree-attributes::
 	Look for attributes in .gitattributes files in the working tree
 	as well (see <<ATTRIBUTES>>).
diff --git a/archive.c b/archive.c
index a3bbb091256..d20e16fa819 100644
--- a/archive.c
+++ b/archive.c
@@ -263,6 +263,7 @@  static int queue_or_write_archive_entry(const struct object_id *oid,
 struct extra_file_info {
 	char *base;
 	struct stat stat;
+	void *content;
 };
 
 int write_archive_entries(struct archiver_args *args,
@@ -337,7 +338,13 @@  int write_archive_entries(struct archiver_args *args,
 		strbuf_addstr(&path_in_archive, basename(path));
 
 		strbuf_reset(&content);
-		if (strbuf_read_file(&content, path, info->stat.st_size) < 0)
+		if (info->content)
+			err = write_entry(args, &fake_oid, path_in_archive.buf,
+					  path_in_archive.len,
+					  info->stat.st_mode,
+					  info->content, info->stat.st_size);
+		else if (strbuf_read_file(&content, path,
+					  info->stat.st_size) < 0)
 			err = error_errno(_("could not read '%s'"), path);
 		else
 			err = write_entry(args, &fake_oid, path_in_archive.buf,
@@ -493,6 +500,7 @@  static void extra_file_info_clear(void *util, const char *str)
 {
 	struct extra_file_info *info = util;
 	free(info->base);
+	free(info->content);
 	free(info);
 }
 
@@ -514,14 +522,40 @@  static int add_file_cb(const struct option *opt, const char *arg, int unset)
 	if (!arg)
 		return -1;
 
-	path = prefix_filename(args->prefix, arg);
-	item = string_list_append_nodup(&args->extra_files, path);
-	item->util = info = xmalloc(sizeof(*info));
+	info = xmalloc(sizeof(*info));
 	info->base = xstrdup_or_null(base);
-	if (stat(path, &info->stat))
-		die(_("File not found: %s"), path);
-	if (!S_ISREG(info->stat.st_mode))
-		die(_("Not a regular file: %s"), path);
+
+	if (!strcmp(opt->long_name, "add-file")) {
+		path = prefix_filename(args->prefix, arg);
+		if (stat(path, &info->stat))
+			die(_("File not found: %s"), path);
+		if (!S_ISREG(info->stat.st_mode))
+			die(_("Not a regular file: %s"), path);
+		info->content = NULL; /* read the file later */
+	} else if (!strcmp(opt->long_name, "add-virtual-file")) {
+		const char *colon = strchr(arg, ':');
+		char *p;
+
+		if (!colon)
+			die(_("missing colon: '%s'"), arg);
+
+		p = xstrndup(arg, colon - arg);
+		if (!args->prefix)
+			path = p;
+		else {
+			path = prefix_filename(args->prefix, p);
+			free(p);
+		}
+		memset(&info->stat, 0, sizeof(info->stat));
+		info->stat.st_mode = S_IFREG | 0644;
+		info->content = xstrdup(colon + 1);
+		info->stat.st_size = strlen(info->content);
+	} else {
+		BUG("add_file_cb() called for %s", opt->long_name);
+	}
+	item = string_list_append_nodup(&args->extra_files, path);
+	item->util = info;
+
 	return 0;
 }
 
@@ -554,6 +588,9 @@  static int parse_archive_args(int argc, const char **argv,
 		{ OPTION_CALLBACK, 0, "add-file", args, N_("file"),
 		  N_("add untracked file to archive"), 0, add_file_cb,
 		  (intptr_t)&base },
+		{ OPTION_CALLBACK, 0, "add-virtual-file", args,
+		  N_("path:content"), N_("add untracked file to archive"), 0,
+		  add_file_cb, (intptr_t)&base },
 		OPT_STRING('o', "output", &output, N_("file"),
 			N_("write the archive to this file")),
 		OPT_BOOL(0, "worktree-attributes", &worktree_attributes,
diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh
index 1e6d18b140e..ebc26e89a9b 100755
--- a/t/t5003-archive-zip.sh
+++ b/t/t5003-archive-zip.sh
@@ -206,6 +206,18 @@  test_expect_success 'git archive --format=zip --add-file' '
 check_zip with_untracked
 check_added with_untracked untracked untracked
 
+test_expect_success UNZIP 'git archive --format=zip --add-virtual-file' '
+	git archive --format=zip >with_file_with_content.zip \
+		--add-virtual-file=hello:world $EMPTY_TREE &&
+	test_when_finished "rm -rf tmp-unpack" &&
+	mkdir tmp-unpack && (
+		cd tmp-unpack &&
+		"$GIT_UNZIP" ../with_file_with_content.zip &&
+		test_path_is_file hello &&
+		test world = $(cat hello)
+	)
+'
+
 test_expect_success 'git archive --format=zip --add-file twice' '
 	echo untracked >untracked &&
 	git archive --format=zip --prefix=one/ --add-file=untracked \