From: Eli Schwartz <hidden> Date: 2021-01-25 00:39:46
Periodically, I wonder if there is some better way for managing tagged
releases for software in git. Current state of the art seems to be
"write a custom Makefile that takes a version and seds out the existing
version, then runs git tag for you". Inelegant solutions also abound;
people release code that does not build properly unless you build it
from a git checkout so it can run git describe. (There are half a dozen
individually popular but mutually exclusive python ecosystems for this,
in fact, all of them varying degrees of broken.)
git does have a way to automatically insert metadata via the
export-subst attribute on a file, but it's very awkward to use and you
cannot get much info out of it.
# get tags into a file, only on exact match:
$ cat VERSION
$Format:%d$
$Format:%D$
$ git archive HEAD | bsdtar -xOf - VERSION
(HEAD -> master, tag: 1.0)
HEAD -> master, tag: 1.0
With sufficient regex, you can get a release out of this, but it doesn't
work if you try getting an autogenerated tarball for a commit that isn't
exactly a release.
$ git commit --allow-empty -m ...
$ git archive HEAD | bsdtar -xOf - VERSION
(HEAD -> master)
HEAD -> master
I think it would be much, much nicer if there was a format placeholder
for git describe.
It doesn't even need option support -- the default output in most cases
could be a replacement for or fall back to existing invocations of the
"git" program, followed by post-processing with e.g. "sed".
However, the existence of current pretty formats such as %C() or
%(trailer:options) implies that options could be passed in a
git-describe format too. e.g. %(describe:--long --tags --match="v*")
Thoughts?
--
Eli Schwartz
Arch Linux Bug Wrangler and Trusted User
From: René Scharfe <hidden> Date: 2021-02-08 19:48:41
Am 25.01.21 um 01:32 schrieb Eli Schwartz:
Periodically, I wonder if there is some better way for managing
tagged releases for software in git. Current state of the art seems
to be "write a custom Makefile that takes a version and seds out the
existing version, then runs git tag for you". Inelegant solutions
also abound; people release code that does not build properly unless
you build it from a git checkout so it can run git describe. (There
are half a dozen individually popular but mutually exclusive python
ecosystems for this, in fact, all of them varying degrees of
broken.)
git does have a way to automatically insert metadata via the
export-subst attribute on a file, but it's very awkward to use and
you cannot get much info out of it.
# get tags into a file, only on exact match:
$ cat VERSION $Format:%d$ $Format:%D$
$ git archive HEAD | bsdtar -xOf - VERSION (HEAD -> master, tag:
1.0) HEAD -> master, tag: 1.0
With sufficient regex, you can get a release out of this, but it
doesn't work if you try getting an autogenerated tarball for a commit
that isn't exactly a release.
$ git commit --allow-empty -m ... $ git archive HEAD | bsdtar -xOf -
VERSION (HEAD -> master) HEAD -> master
I think it would be much, much nicer if there was a format
placeholder for git describe.
Totally.
It doesn't even need option support -- the default output in most
cases could be a replacement for or fall back to existing invocations
of the "git" program, followed by post-processing with e.g. "sed".
However, the existence of current pretty formats such as %C() or
%(trailer:options) implies that options could be passed in a
git-describe format too. e.g. %(describe:--long --tags --match="v*")
Thoughts?
git archive uses the pretty format code for export-subst. It is used by
git log and others as well. git describe uses all object flags to find
the best description. Simply plugging it into the pretty format code
would clash with the object flag use of git log.
And replacing the flags with a commit slab doesn't seem to be enough,
either -- I get good results lots of commits, but for some git log with
the new placeholder would just show some nonsensical output, as it
seems to get the depth calculation wrong for them somehow.
Anyway, we can of course do something like in the patch below. It
works, it's easy, it's fast enough for git archive, and it's quite
hideous. Hopefully it's bad enough to motivate someone to come up with
a cleaner, faster solution.
René
---
pretty.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
@@ -1213,6 +1214,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */returnparse_padding_placeholder(placeholder,c);}+if(skip_prefix(placeholder,"(describe)",&arg)){+structchild_processcmd=CHILD_PROCESS_INIT;+structstrbufout=STRBUF_INIT;++cmd.git_cmd=1;+strvec_push(&cmd.args,"describe");+strvec_push(&cmd.args,"--always");+strvec_push(&cmd.args,oid_to_hex(&commit->object.oid));+pipe_command(&cmd,NULL,0,&out,0,NULL,0);+strbuf_rtrim(&out);+strbuf_addbuf(sb,&out);+strbuf_release(&out);+returnarg-placeholder;+}+/* these depend on the commit */if(!commit->object.parsed)parse_object(the_repository,&commit->object.oid);--
From: Eli Schwartz <hidden> Date: 2021-02-09 00:20:54
On 2/8/21 2:46 PM, René Scharfe wrote:
git archive uses the pretty format code for export-subst. It is used by
git log and others as well. git describe uses all object flags to find
the best description. Simply plugging it into the pretty format code
would clash with the object flag use of git log.
Yeah, I was afraid there might be bad interactions with a pretty
archive-centric placeholder and something that isn't git-archive.
On the other hand, with my zero knowledge of the code but having read
lots of man pages... %S documents that it "only works with git log", so
maybe it is possible to add an option that is documented to only work
for git archive?
e.g. if you do use it,
$ cat VERSION
$Format:%d$
$Format:%S$
$ git archive HEAD | bsdtar -xOf - VERSION
(HEAD -> master, tag: 1.0)
%S
It's apparently completely ignored and treated as raw characters. The
same restriction could theoretically be added in the other direction for
a new placeholder.
This would neatly resolve Junio's concern about the resource-intensive
nature of "describe" not being a good fit for "log".
And replacing the flags with a commit slab doesn't seem to be enough,
either -- I get good results lots of commits, but for some git log with
the new placeholder would just show some nonsensical output, as it
seems to get the depth calculation wrong for them somehow.
You mean git describe <commit> produces wrong results for those?
Anyway, we can of course do something like in the patch below. It
works, it's easy, it's fast enough for git archive, and it's quite
hideous. Hopefully it's bad enough to motivate someone to come up with
a cleaner, faster solution.
:D :D an important part of the resolution process, to be sure.
@@ -1213,6 +1214,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */returnparse_padding_placeholder(placeholder,c);}+if(skip_prefix(placeholder,"(describe)",&arg)){+structchild_processcmd=CHILD_PROCESS_INIT;+structstrbufout=STRBUF_INIT;++cmd.git_cmd=1;+strvec_push(&cmd.args,"describe");+strvec_push(&cmd.args,"--always");+strvec_push(&cmd.args,oid_to_hex(&commit->object.oid));+pipe_command(&cmd,NULL,0,&out,0,NULL,0);+strbuf_rtrim(&out);+strbuf_addbuf(sb,&out);+strbuf_release(&out);+returnarg-placeholder;+}+/* these depend on the commit */if(!commit->object.parsed)parse_object(the_repository,&commit->object.oid);--
2.30.0
--
Eli Schwartz
Arch Linux Bug Wrangler and Trusted User
From: René Scharfe <hidden> Date: 2021-02-14 10:06:33
Am 09.02.21 um 01:19 schrieb Eli Schwartz:
On the other hand, with my zero knowledge of the code but having read
lots of man pages... %S documents that it "only works with git log", so
maybe it is possible to add an option that is documented to only work
for git archive?
e.g. if you do use it,
$ cat VERSION
$Format:%d$
$Format:%S$
$ git archive HEAD | bsdtar -xOf - VERSION
(HEAD -> master, tag: 1.0)
%S
It's apparently completely ignored and treated as raw characters. The
same restriction could theoretically be added in the other direction for
a new placeholder.
That's a curious case. Supporting %S in git archive would be easy --
it's just a matter of recording the name given at the command line for
the pretty format code to find, like in the sloppy patch below (missing
doc update, missing test, leaks memory, adds a static variable to
library code).
The inconsistent support of %S is not ideal and I think that set a bad
precedent, but on the other hand I don't see its usefulness for git
archive in particular. So I dunno. Perhaps worth doing once rev-list
gains support, for completeness.
René
---
archive.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
From: René Scharfe <hidden> Date: 2021-02-14 10:06:33
Add a format placeholder for describe output. Implement it by actually
calling git describe, which is simple and guarantees correctness. It's
intended to be used with $Format:...$ in files with the attribute
export-subst and git archive. It can also be used with git log etc.,
even though that's going to be slow due to the fork for each commit.
Suggested-by: Eli Schwartz <redacted>
Signed-off-by: René Scharfe <redacted>
---
Documentation/pretty-formats.txt | 2 ++
pretty.c | 17 +++++++++++++++++
t/t4205-log-pretty-formats.sh | 10 ++++++++++
3 files changed, 29 insertions(+)
@@ -208,6 +208,8 @@ The placeholders are: '%cs':: committer date, short format (`YYYY-MM-DD`) '%d':: ref names, like the --decorate option of linkgit:git-log[1] '%D':: ref names without the " (", ")" wrapping.+'%(describe)':: human-readable name, like linkgit:git-describe[1];+ empty string for undescribable commits '%S':: ref name given on the command line by which the commit was reached (like `git log --source`), only works with `git log` '%e':: encoding
@@ -1214,6 +1215,22 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */returnparse_padding_placeholder(placeholder,c);}+if(skip_prefix(placeholder,"(describe)",&arg)){+structchild_processcmd=CHILD_PROCESS_INIT;+structstrbufout=STRBUF_INIT;+structstrbuferr=STRBUF_INIT;++cmd.git_cmd=1;+strvec_push(&cmd.args,"describe");+strvec_push(&cmd.args,oid_to_hex(&commit->object.oid));+pipe_command(&cmd,NULL,0,&out,0,&err,0);+strbuf_rtrim(&out);+strbuf_addbuf(sb,&out);+strbuf_release(&out);+strbuf_release(&err);+returnarg-placeholder;+}+/* these depend on the commit */if(!commit->object.parsed)parse_object(the_repository,&commit->object.oid);
From: René Scharfe. <hidden> Date: 2021-02-14 10:13:23
Allow restricting the tags used by the placeholder %(describe) with the
options match and exclude. E.g. the following command describes the
current commit using official version tags, without those for release
candidates:
$ git log -1 --format='%(describe:match=v[0-9]*,exclude=*rc*)'
Signed-off-by: René Scharfe <redacted>
---
Documentation/pretty-formats.txt | 13 ++++++++--
pretty.c | 43 ++++++++++++++++++++++++++++++--
t/t4205-log-pretty-formats.sh | 16 ++++++++++++
3 files changed, 68 insertions(+), 4 deletions(-)
@@ -208,8 +208,17 @@ The placeholders are: '%cs':: committer date, short format (`YYYY-MM-DD`) '%d':: ref names, like the --decorate option of linkgit:git-log[1] '%D':: ref names without the " (", ")" wrapping.-'%(describe)':: human-readable name, like linkgit:git-describe[1];- empty string for undescribable commits+'%(describe[:options])':: human-readable name, like+ linkgit:git-describe[1]; empty string for+ undescribable commits. The `describe` string+ may be followed by a colon and zero or more+ comma-separated options.+++** 'match=<pattern>': Only consider tags matching the given+ `glob(7)` pattern, excluding the "refs/tags/" prefix.+** 'exclude=<pattern>': Do not consider tags matching the given+ `glob(7)` pattern, excluding the "refs/tags/" prefix.+ '%S':: ref name given on the command line by which the commit was reached (like `git log --source`), only works with `git log` '%e':: encoding
@@ -1150,6 +1150,34 @@ static int format_trailer_match_cb(const struct strbuf *key, void *ud)return0;}+staticsize_tparse_describe_args(constchar*start,structstrvec*args)+{+constchar*options[]={"match","exclude"};+constchar*arg=start;++for(;;){+constchar*matched=NULL;+constchar*argval;+size_targlen=0;+inti;++for(i=0;i<ARRAY_SIZE(options);i++){+if(match_placeholder_arg_value(arg,options[i],&arg,+&argval,&arglen)){+matched=options[i];+break;+}+}+if(!matched)+break;++if(!arglen)+return0;+strvec_pushf(args,"--%s=%.*s",matched,(int)arglen,argval);+}+returnarg-start;+}+staticsize_tformat_commit_one(structstrbuf*sb,/* in UTF-8 */constchar*placeholder,void*context)
@@ -1215,20 +1243,31 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */returnparse_padding_placeholder(placeholder,c);}-if(skip_prefix(placeholder,"(describe)",&arg)){+if(skip_prefix(placeholder,"(describe",&arg)){structchild_processcmd=CHILD_PROCESS_INIT;structstrbufout=STRBUF_INIT;structstrbuferr=STRBUF_INIT;cmd.git_cmd=1;strvec_push(&cmd.args,"describe");++if(*arg==':'){+arg++;+arg+=parse_describe_args(arg,&cmd.args);+}++if(*arg!=')'){+child_process_clear(&cmd);+return0;+}+strvec_push(&cmd.args,oid_to_hex(&commit->object.oid));pipe_command(&cmd,NULL,0,&out,0,&err,0);strbuf_rtrim(&out);strbuf_addbuf(sb,&out);strbuf_release(&out);strbuf_release(&err);-returnarg-placeholder;+returnarg-placeholder+1;}/* these depend on the commit */
From: Eli Schwartz <hidden> Date: 2021-02-16 05:07:59
On 2/14/21 5:04 AM, René Scharfe wrote:
Add a format placeholder for describe output. Implement it by actually
calling git describe, which is simple and guarantees correctness. It's
intended to be used with $Format:...$ in files with the attribute
export-subst and git archive. It can also be used with git log etc.,
even though that's going to be slow due to the fork for each commit.
This patch works great for me.
In fact, it even works fast enough for git log to not noticeably slow
down unless I really stomp on the "Page Down" button. At least on Linux...
Thanks for working on this!
@@ -208,6 +208,8 @@ The placeholders are: '%cs':: committer date, short format (`YYYY-MM-DD`) '%d':: ref names, like the --decorate option of linkgit:git-log[1] '%D':: ref names without the " (", ")" wrapping.+'%(describe)':: human-readable name, like linkgit:git-describe[1];+ empty string for undescribable commits '%S':: ref name given on the command line by which the commit was reached (like `git log --source`), only works with `git log` '%e':: encoding
@@ -1214,6 +1215,22 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */returnparse_padding_placeholder(placeholder,c);}+if(skip_prefix(placeholder,"(describe)",&arg)){+structchild_processcmd=CHILD_PROCESS_INIT;+structstrbufout=STRBUF_INIT;+structstrbuferr=STRBUF_INIT;++cmd.git_cmd=1;+strvec_push(&cmd.args,"describe");+strvec_push(&cmd.args,oid_to_hex(&commit->object.oid));+pipe_command(&cmd,NULL,0,&out,0,&err,0);+strbuf_rtrim(&out);+strbuf_addbuf(sb,&out);+strbuf_release(&out);+strbuf_release(&err);+returnarg-placeholder;+}+/* these depend on the commit */if(!commit->object.parsed)parse_object(the_repository,&commit->object.oid);
Add a format placeholder for describe output. Implement it by actually
calling git describe, which is simple and guarantees correctness. It's
intended to be used with $Format:...$ in files with the attribute
export-subst and git archive.
Does it really guarantee correctness though? In "builtin/describe.c" we
first walk over the refs and use that to format all N items we're asked
about.
Under "git log" this is presumably in a race where refs added/deleted
during the run of "git log" will change the describe output to be
inconsistent with earlier lines.
It can also be used with git log etc., even though that's going to be
slow due to the fork for each commit.
From: René Scharfe. <hidden> Date: 2021-02-16 17:15:37
Am 16.02.21 um 14:00 schrieb Ævar Arnfjörð Bjarmason:
On Sun, Feb 14 2021, René Scharfe wrote:
quoted
Add a format placeholder for describe output. Implement it by actually
calling git describe, which is simple and guarantees correctness. It's
intended to be used with $Format:...$ in files with the attribute
export-subst and git archive.
Does it really guarantee correctness though? In "builtin/describe.c" we
first walk over the refs and use that to format all N items we're asked
about.
Under "git log" this is presumably in a race where refs added/deleted
during the run of "git log" will change the describe output to be
inconsistent with earlier lines.
Right, didn't think of that aspect. So we'd better warn about the
raciness in the documentation.
We could improve that by keeping a single describe process around and
feeding it object names through a pipe as we go. The results would
still become outdated after a ref is added or removed, but they'd be
consistent. This would be faster as well.
René
+'%(describe)':: human-readable name, like linkgit:git-describe[1];
+ empty string for undescribable commits
In the case of undescribable we've got the subcommand exiting non-zero
and we ignore it. The right thing in this case given how the rest of
format arguments work, but maybe something to explicitly test for?
There's another edge case in this: if you do "%(describe)%(describe)"
it'll be run twice for the rev, 3 times if you add another "%(describe)"
etc. I don't know if pretty.c has an easy way to cache/avoid that.
From: Jeff King <hidden> Date: 2021-02-17 18:32:42
On Sun, Feb 14, 2021 at 11:10:57AM +0100, René Scharfe. wrote:
Allow restricting the tags used by the placeholder %(describe) with the
options match and exclude. E.g. the following command describes the
current commit using official version tags, without those for release
candidates:
$ git log -1 --format='%(describe:match=v[0-9]*,exclude=*rc*)'
An interesting side effect of this series is that it allows remote users
asking for archives to fill in this data, too (by using export-subst
placeholders). That includes servers allowing "git archive --remote",
but also services like GitHub that will run git-archive on behalf of
clients.
I wonder what avenues for mischief this provides. Certainly using extra
CPU to run git-describe. But I guess also probing at otherwise hidden
refs using the match/exclude system (though since it's limited to
refs/tags/, that's pretty unlikely).
I present this mostly as an observation, not an objection. Certainly we
already have %D which can look at hidden refs. And I strongly suspect
that the server-side git-upload-archive does not respect hidden refs
when resolving object names in the first place.
Kind of an interesting thought as we extend the formatting language,
though. I generally think of them as something that is always under
control of caller, but export-subst's $Format$ will come from the repo
contents.
-Peff
From: René Scharfe. <hidden> Date: 2021-02-28 11:24:43
Am 17.02.21 um 01:58 schrieb Ævar Arnfjörð Bjarmason:
On Sun, Feb 14 2021, René Scharfe wrote:
quoted
+'%(describe)':: human-readable name, like linkgit:git-describe[1];
+ empty string for undescribable commits
In the case of undescribable we've got the subcommand exiting non-zero
and we ignore it. The right thing in this case given how the rest of
format arguments work, but maybe something to explicitly test for?
The test convers it, but we can surely make that easier to see.
-- >8 --
Subject: [PATCH] t4205: assert %(describe) test coverage
Document that the test is covering both describable and
undescribable commits.
Suggested-by: Ævar Arnfjörð Bjarmason <redacted>
Signed-off-by: René Scharfe <redacted>
---
t/t4205-log-pretty-formats.sh | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
From: René Scharfe. <hidden> Date: 2021-02-28 11:25:01
Am 17.02.21 um 19:31 schrieb Jeff King:
On Sun, Feb 14, 2021 at 11:10:57AM +0100, René Scharfe. wrote:
quoted
Allow restricting the tags used by the placeholder %(describe) with the
options match and exclude. E.g. the following command describes the
current commit using official version tags, without those for release
candidates:
$ git log -1 --format='%(describe:match=v[0-9]*,exclude=*rc*)'
An interesting side effect of this series is that it allows remote users
asking for archives to fill in this data, too (by using export-subst
placeholders). That includes servers allowing "git archive --remote",
but also services like GitHub that will run git-archive on behalf of
clients.
I wonder what avenues for mischief this provides. Certainly using extra
CPU to run git-describe.
A repository can contain millions of files, each file can contain
millions of $Format:...$ sequences and each of them can contain millions
of %(describe) placeholders. Each of them could have different match or
exclude args to prevent caching. Allowing a single request to cause
trillions of calls of git describe sounds excessive. Let's limit this.
-- >8 --
Subject: [PATCH] archive: expand only a single %(describe) per archive
Every %(describe) placeholder in $Format:...$ strings in files with the
attribute export-subst is expanded by calling git describe. This can
potentially result in a lot of such calls per archive. That's OK for
local repositories under control of the user of git archive, but could
be a problem for hosted repositories.
Expand only a single %(describe) placeholder per archive for now to
avoid denial-of-service attacks. We can make this limit configurable
later if needed, but let's start out simple.
Reported-by: Jeff King <redacted>
Signed-off-by: René Scharfe <redacted>
---
Documentation/gitattributes.txt | 3 ++-
archive.c | 16 ++++++++++------
archive.h | 2 ++
pretty.c | 8 ++++++++
pretty.h | 5 +++++
t/t5001-archive-attr.sh | 14 ++++++++++++++
6 files changed, 41 insertions(+), 7 deletions(-)
@@ -1174,7 +1174,8 @@ tag then no replacement will be done. The placeholders are the same as those for the option `--pretty=format:` of linkgit:git-log[1], except that they need to be wrapped like this: `$Format:PLACEHOLDERS$` in the file. E.g. the string `$Format:%H$` will be replaced by the-commit hash.+commit hash. However, only one `%(describe)` placeholder is expanded+per archive to avoid denial-of-service attacks. Packing objects
@@ -22,6 +23,7 @@ struct archiver_args {unsignedintconvert:1;intcompression_level;structstring_listextra_files;+structpretty_print_context*pretty_ctx;};/* main api */
On Sun, Feb 14, 2021 at 11:10:57AM +0100, René Scharfe. wrote:
quoted
Allow restricting the tags used by the placeholder %(describe) with the
options match and exclude. E.g. the following command describes the
current commit using official version tags, without those for release
candidates:
$ git log -1 --format='%(describe:match=v[0-9]*,exclude=*rc*)'
An interesting side effect of this series is that it allows remote users
asking for archives to fill in this data, too (by using export-subst
placeholders). That includes servers allowing "git archive --remote",
but also services like GitHub that will run git-archive on behalf of
clients.
I wonder what avenues for mischief this provides. Certainly using extra
CPU to run git-describe.
A repository can contain millions of files, each file can contain
millions of $Format:...$ sequences and each of them can contain millions
of %(describe) placeholders. Each of them could have different match or
exclude args to prevent caching. Allowing a single request to cause
trillions of calls of git describe sounds excessive. Let's limit this.
-- >8 --
Subject: [PATCH] archive: expand only a single %(describe) per archive
Every %(describe) placeholder in $Format:...$ strings in files with the
attribute export-subst is expanded by calling git describe. This can
potentially result in a lot of such calls per archive. That's OK for
local repositories under control of the user of git archive, but could
be a problem for hosted repositories.
Expand only a single %(describe) placeholder per archive for now to
avoid denial-of-service attacks. We can make this limit configurable
later if needed, but let's start out simple.
Reported-by: Jeff King <redacted>
Signed-off-by: René Scharfe <redacted>
---
Documentation/gitattributes.txt | 3 ++-
archive.c | 16 ++++++++++------
archive.h | 2 ++
pretty.c | 8 ++++++++
pretty.h | 5 +++++
t/t5001-archive-attr.sh | 14 ++++++++++++++
6 files changed, 41 insertions(+), 7 deletions(-)
@@ -1174,7 +1174,8 @@ tag then no replacement will be done. The placeholders are the same as those for the option `--pretty=format:` of linkgit:git-log[1], except that they need to be wrapped like this: `$Format:PLACEHOLDERS$` in the file. E.g. the string `$Format:%H$` will be replaced by the-commit hash.+commit hash. However, only one `%(describe)` placeholder is expanded+per archive to avoid denial-of-service attacks. Packing objects
@@ -22,6 +23,7 @@ struct archiver_args {unsignedintconvert:1;intcompression_level;structstring_listextra_files;+structpretty_print_context*pretty_ctx;};/* main api */
This whole thing seems rather backwards as a solution to the "we run it
N times" problem I mentioned in [off-list ref].
Instead of taking the trouble of putting a limit in the
pretty_print_context so we don't call it N times for the same commit,
why not just put the strbuf with the result in that same struct?
Then you can have it millions of times, and it won't be any more
expensive than the other existing %(format) specifiers (actually cheaper
than most).
Or is there some edge case I'm missing here where "git archive" can
either be fed N commits and we share the context, or we share the
context across formatting different revisions in some cases?
From: René Scharfe. <hidden> Date: 2021-03-03 06:41:56
Am 28.02.21 um 16:41 schrieb Ævar Arnfjörð Bjarmason:
On Sun, Feb 28 2021, René Scharfe. wrote:
quoted
Am 17.02.21 um 19:31 schrieb Jeff King:
quoted
On Sun, Feb 14, 2021 at 11:10:57AM +0100, René Scharfe. wrote:
quoted
Allow restricting the tags used by the placeholder %(describe) with the
options match and exclude. E.g. the following command describes the
current commit using official version tags, without those for release
candidates:
$ git log -1 --format='%(describe:match=v[0-9]*,exclude=*rc*)'
An interesting side effect of this series is that it allows remote users
asking for archives to fill in this data, too (by using export-subst
placeholders). That includes servers allowing "git archive --remote",
but also services like GitHub that will run git-archive on behalf of
clients.
I wonder what avenues for mischief this provides. Certainly using extra
CPU to run git-describe.
A repository can contain millions of files, each file can contain
millions of $Format:...$ sequences and each of them can contain millions
of %(describe) placeholders. Each of them could have different match or
exclude args to prevent caching. Allowing a single request to cause
trillions of calls of git describe sounds excessive. Let's limit this.
-- >8 --
Subject: [PATCH] archive: expand only a single %(describe) per archive
Every %(describe) placeholder in $Format:...$ strings in files with the
attribute export-subst is expanded by calling git describe. This can
potentially result in a lot of such calls per archive. That's OK for
local repositories under control of the user of git archive, but could
be a problem for hosted repositories.
Expand only a single %(describe) placeholder per archive for now to
avoid denial-of-service attacks. We can make this limit configurable
later if needed, but let's start out simple.
Reported-by: Jeff King <redacted>
Signed-off-by: René Scharfe <redacted>
---
Documentation/gitattributes.txt | 3 ++-
archive.c | 16 ++++++++++------
archive.h | 2 ++
pretty.c | 8 ++++++++
pretty.h | 5 +++++
t/t5001-archive-attr.sh | 14 ++++++++++++++
6 files changed, 41 insertions(+), 7 deletions(-)
@@ -1174,7 +1174,8 @@ tag then no replacement will be done. The placeholders are the same as those for the option `--pretty=format:` of linkgit:git-log[1], except that they need to be wrapped like this: `$Format:PLACEHOLDERS$` in the file. E.g. the string `$Format:%H$` will be replaced by the-commit hash.+commit hash. However, only one `%(describe)` placeholder is expanded+per archive to avoid denial-of-service attacks. Packing objects
@@ -22,6 +23,7 @@ struct archiver_args {unsignedintconvert:1;intcompression_level;structstring_listextra_files;+structpretty_print_context*pretty_ctx;};/* main api */
This whole thing seems rather backwards as a solution to the "we run it
N times" problem I mentioned in [off-list ref].
In the referenced message you pointed out that using %(describe) more
than once incurs the cost of a call to "git describe" each time and
asked for a way to avoid that.
This patch here prevents the second and later calls for "git archive".
It's not intended to speed up expanding repeated placeholders, but
rather to reject maliciously crafted placeholders even if they are not
the same.
So does that mean you have the requirement to use %(describe) more
than once in the same archive, and this DoS protection would be too
strict for you? In that case we'd need a way to increase the limit,
e.g. a configuration variable or command line option. Otherwise I
don't see the connection between your message and this patch.
Side note: Keeping the "git describe" running and feeding it commits
one by one through a pipe (with a new --stdin option, I suppose) as
mentioned in [off-list ref] would
speed up expanding repeated placeholders as well.
Instead of taking the trouble of putting a limit in the
pretty_print_context so we don't call it N times for the same commit,
why not just put the strbuf with the result in that same struct?
Then you can have it millions of times, and it won't be any more
expensive than the other existing %(format) specifiers (actually cheaper
than most).
Each %(describe) placeholder can have a unique set of match or exclude
arguments. Caching them all would increase the strength of a DoS
attack. Caching a few of them would be OK, but is ineffective in
reducing the strength of the attack.
Or is there some edge case I'm missing here where "git archive" can
either be fed N commits and we share the context, or we share the
context across formatting different revisions in some cases?
git archive currently works only on a single commit.
René
From: René Scharfe. <hidden> Date: 2021-03-06 16:20:10
Am 02.03.21 um 17:00 schrieb René Scharfe.:
Am 28.02.21 um 16:41 schrieb Ævar Arnfjörð Bjarmason:
quoted
Instead of taking the trouble of putting a limit in the
pretty_print_context so we don't call it N times for the same commit,
why not just put the strbuf with the result in that same struct?
Then you can have it millions of times, and it won't be any more
expensive than the other existing %(format) specifiers (actually cheaper
than most).
Each %(describe) placeholder can have a unique set of match or exclude
arguments. Caching them all would increase the strength of a DoS
attack. Caching a few of them would be OK, but is ineffective in
reducing the strength of the attack.
The script at the bottom creates archives that illustrate the issue. On
a repo generated with parameter 10 (10 files with 10 $Format:...$ with
10 %(describe) placeholders, so 1000 total), I get the following number
with v2.30.1, which ignores %(describe):
Benchmark #1: git archive HEAD
Time (mean ± σ): 2.2 ms ± 0.2 ms [User: 0.9 ms, System: 1.0 ms]
Range (min … max): 1.8 ms … 2.8 ms 705 runs
Warning: Command took less than 5 ms to complete. Results might be inaccurate.
The version in next expands all placeholders and takes three orders of
magnitude longer:
Benchmark #1: git archive HEAD
Time (mean ± σ): 2.300 s ± 0.003 s [User: 819.0 ms, System: 1200.0 ms]
Range (min … max): 2.293 s … 2.305 s 10 runs
The proposed patch to expand only a single placeholder gets the runtime
back under control:
Benchmark #1: git archive HEAD
Time (mean ± σ): 4.7 ms ± 0.3 ms [User: 1.8 ms, System: 2.2 ms]
Range (min … max): 4.2 ms … 7.0 ms 451 runs
Warning: Command took less than 5 ms to complete. Results might be inaccurate.
Using parameter 100 takes about a second to create the repo, but the git
archive version in next already needs longer to tar it up than I'm
willing to wait.
#!/bin/sh
n=$1
mkdir $n
cd $n
git init
for i in $(seq $n)
do
awk -v i=$i -v n=$n 'END {
for (j = 0; j < n; j++) {
print "$Format:"
for (k = 0; k < n; k++) {
print "%(describe:exclude=x-" i "-" j "-" k ")"
}
print "$"
}
}' </dev/null >"file$i"
done
git add file*
echo "file* export-subst" >.gitattributes
git add .gitattributes
git commit -m initial
for tagno in $(seq $n)
do
git tag -m "$tagno" "tag$tagno"
done