{"thread":{"id":"9765","subject":"[PATCH 2/3] archive: specfile support (--pretty=format: in archive files)","startedAt":"2007-09-03T18:07:01Z","lastAt":"2007-09-07T10:45:10Z","messageCount":25,"participants":["René Scharfe","Johannes Schindelin","David Kastrup","Junio C Hamano","Andreas Ericsson"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"52369","messageId":"46DC4D45.4030208@lsrfire.ath.cx","threadId":"9765","inReplyTo":null,"subject":"[PATCH 2/3] archive: specfile support (--pretty=format: in archive files)","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-09-03T18:07:01Z","receivedAt":"2007-09-03T18:07:01Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Add support for a new attribute, specfile.  Files marked as being\nspecfiles are expanded by git-archive when they are written to an\narchive.  It has no effect on worktree files.  The same placeholders\nas those for the option --pretty=format: of git-log et al. can be\nused.\n\nThe attribute is useful for creating auto-updating specfiles.  It is\nlimited by the underlying function format_commit_message(), though.\nE.g. currently there is no placeholder for git-describe like output,\nand expanded specfiles can't contain NUL bytes.  That can be fixed\nin format_commit_message() later and will then benefit users of\ngit-log, too.\n\nSigned-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>\n---\n Documentation/gitattributes.txt |   14 ++++++++++\n archive-tar.c                   |    5 +++-\n archive-zip.c                   |    5 +++-\n archive.h                       |    3 ++\n builtin-archive.c               |   55 ++++++++++++++++++++++++++++++++++++++-\n t/t5000-tar-tree.sh             |   19 +++++++++++++\n 6 files changed, 98 insertions(+), 3 deletions(-)\n\nThis should already be sufficient for the use case which Michael and\nThomas described a while ago, viz. adding a commit ID file to\ngenerated archives.\n\nWhy did it take me that long to come up with such a simple patch?\nThere was a vacation and a feature freeze in between, but above all\nI was only recently able to convince myself (using ugly code) that\nformat_commit_message() can indeed be made to expand placeholders\nto git-describe strings..\n\ndiff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt\nindex 46f9d59..47a621b 100644\n--- a/Documentation/gitattributes.txt\n+++ b/Documentation/gitattributes.txt\n@@ -421,6 +421,20 @@ frotz\tunspecified\n ----------------------------------------------------------------\n \n \n+Creating an archive\n+~~~~~~~~~~~~~~~~~~~\n+\n+`specfile`\n+^^^^^^^^^^\n+\n+If the attribute `specfile` is set for a file then git will expand\n+several placeholders when adding this file to an archive.  The\n+expansion depends on the availability of a commit ID, i.e. if\n+gitlink:git-archive[1] has been given a tree instead of a commit or a\n+tag then no replacement will be done.  The placeholders are the same\n+as those for the option `--pretty=format:` of gitlink:git-log[1].\n+\n+\n GIT\n ---\n Part of the gitlink:git[7] suite\ndiff --git a/archive-tar.c b/archive-tar.c\nindex 66fe3e3..c0d95da 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -17,6 +17,7 @@ static unsigned long offset;\n static time_t archive_time;\n static int tar_umask = 002;\n static int verbose;\n+static const struct commit *commit;\n \n /* writes out the whole block, but only if it is full */\n static void write_if_needed(void)\n@@ -285,7 +286,8 @@ static int write_tar_entry(const unsigned char *sha1,\n \t\tbuffer = NULL;\n \t\tsize = 0;\n \t} else {\n-\t\tbuffer = convert_sha1_file(path.buf, sha1, mode, &type, &size);\n+\t\tbuffer = sha1_file_to_archive(path.buf, sha1, mode, &type,\n+\t\t                              &size, commit);\n \t\tif (!buffer)\n \t\t\tdie(\"cannot read %s\", sha1_to_hex(sha1));\n \t}\n@@ -304,6 +306,7 @@ int write_tar_archive(struct archiver_args *args)\n \n \tarchive_time = args->time;\n \tverbose = args->verbose;\n+\tcommit = args->commit;\n \n \tif (args->commit_sha1)\n \t\twrite_global_extended_header(args->commit_sha1);\ndiff --git a/archive-zip.c b/archive-zip.c\nindex 444e162..f63dff3 100644\n--- a/archive-zip.c\n+++ b/archive-zip.c\n@@ -12,6 +12,7 @@\n static int verbose;\n static int zip_date;\n static int zip_time;\n+static const struct commit *commit;\n \n static unsigned char *zip_dir;\n static unsigned int zip_dir_size;\n@@ -195,7 +196,8 @@ static int write_zip_entry(const unsigned char *sha1,\n \t\tif (S_ISREG(mode) && zlib_compression_level != 0)\n \t\t\tmethod = 8;\n \t\tresult = 0;\n-\t\tbuffer = convert_sha1_file(path, sha1, mode, &type, &size);\n+\t\tbuffer = sha1_file_to_archive(path, sha1, mode, &type, &size,\n+\t\t                              commit);\n \t\tif (!buffer)\n \t\t\tdie(\"cannot read %s\", sha1_to_hex(sha1));\n \t\tcrc = crc32(crc, buffer, size);\n@@ -316,6 +318,7 @@ int write_zip_archive(struct archiver_args *args)\n \tzip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE);\n \tzip_dir_size = ZIP_DIRECTORY_MIN_SIZE;\n \tverbose = args->verbose;\n+\tcommit = args->commit;\n \n \tif (args->base && plen > 0 && args->base[plen - 1] == '/') {\n \t\tchar *base = xstrdup(args->base);\ndiff --git a/archive.h b/archive.h\nindex 6838dc7..5791e65 100644\n--- a/archive.h\n+++ b/archive.h\n@@ -8,6 +8,7 @@ struct archiver_args {\n \tconst char *base;\n \tstruct tree *tree;\n \tconst unsigned char *commit_sha1;\n+\tconst struct commit *commit;\n \ttime_t time;\n \tconst char **pathspec;\n \tunsigned int verbose : 1;\n@@ -42,4 +43,6 @@ extern int write_tar_archive(struct archiver_args *);\n extern int write_zip_archive(struct archiver_args *);\n extern void *parse_extra_zip_args(int argc, const char **argv);\n \n+extern void *sha1_file_to_archive(const char *path, const unsigned char *sha1, unsigned int mode, enum object_type *type, unsigned long *size, const struct commit *commit);\n+\n #endif\t/* ARCHIVE_H */\ndiff --git a/builtin-archive.c b/builtin-archive.c\nindex 187491b..faccce3 100644\n--- a/builtin-archive.c\n+++ b/builtin-archive.c\n@@ -10,6 +10,7 @@\n #include \"exec_cmd.h\"\n #include \"pkt-line.h\"\n #include \"sideband.h\"\n+#include \"attr.h\"\n \n static const char archive_usage[] = \\\n \"git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]\";\n@@ -80,6 +81,57 @@ static int run_remote_archiver(const char *remote, int argc,\n \treturn !!rv;\n }\n \n+static void *convert_to_archive(const char *path,\n+                                const void *src, unsigned long *sizep,\n+                                const struct commit *commit)\n+{\n+\tstatic struct git_attr *attr_specfile;\n+\tstruct git_attr_check check[1];\n+\tchar *interpolated = NULL;\n+\tunsigned long allocated = 0;\n+\n+\tif (!commit)\n+\t\treturn NULL;\n+\n+        if (!attr_specfile)\n+                attr_specfile = git_attr(\"specfile\", 8);\n+\n+\tcheck[0].attr = attr_specfile;\n+\tif (git_checkattr(path, ARRAY_SIZE(check), check))\n+\t\treturn NULL;\n+\tif (!ATTR_TRUE(check[0].value))\n+\t\treturn NULL;\n+\n+\t*sizep = format_commit_message(commit, src, &interpolated, &allocated);\n+\n+\treturn interpolated;\n+}\n+\n+void *sha1_file_to_archive(const char *path, const unsigned char *sha1,\n+                           unsigned int mode, enum object_type *type,\n+                           unsigned long *size,\n+                           const struct commit *commit)\n+{\n+\tvoid *buffer, *converted;\n+\n+\tbuffer = read_sha1_file(sha1, type, size);\n+\tif (buffer && S_ISREG(mode)) {\n+\t\tconverted = convert_to_working_tree(path, buffer, size);\n+\t\tif (converted) {\n+\t\t\tfree(buffer);\n+\t\t\tbuffer = converted;\n+\t\t}\n+\n+\t\tconverted = convert_to_archive(path, buffer, size, commit);\n+\t\tif (converted) {\n+\t\t\tfree(buffer);\n+\t\t\tbuffer = converted;\n+\t\t}\n+\t}\n+\n+\treturn buffer;\n+}\n+\n static int init_archiver(const char *name, struct archiver *ar)\n {\n \tint rv = -1, i;\n@@ -109,7 +161,7 @@ void parse_treeish_arg(const char **argv, struct archiver_args *ar_args,\n \tconst unsigned char *commit_sha1;\n \ttime_t archive_time;\n \tstruct tree *tree;\n-\tstruct commit *commit;\n+\tconst struct commit *commit;\n \tunsigned char sha1[20];\n \n \tif (get_sha1(name, sha1))\n@@ -142,6 +194,7 @@ void parse_treeish_arg(const char **argv, struct archiver_args *ar_args,\n \t}\n \tar_args->tree = tree;\n \tar_args->commit_sha1 = commit_sha1;\n+\tar_args->commit = commit;\n \tar_args->time = archive_time;\n }\n \ndiff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh\nindex 1a4c53a..3d5d01b 100755\n--- a/t/t5000-tar-tree.sh\n+++ b/t/t5000-tar-tree.sh\n@@ -28,12 +28,15 @@ commit id embedding:\n TAR=${TAR:-tar}\n UNZIP=${UNZIP:-unzip}\n \n+SPECFILEFORMAT=%H%n\n+\n test_expect_success \\\n     'populate workdir' \\\n     'mkdir a b c &&\n      echo simple textfile >a/a &&\n      mkdir a/bin &&\n      cp /bin/sh a/bin &&\n+     printf \"%s\" \"$SPECFILEFORMAT\" >a/specfile &&\n      ln -s a a/l1 &&\n      (p=long_path_to_a_file && cd a &&\n       for depth in 1 2 3 4 5; do mkdir $p && cd $p; done &&\n@@ -105,6 +108,22 @@ test_expect_success \\\n     'diff -r a c/prefix/a'\n \n test_expect_success \\\n+    'create an archive with a specfile' \\\n+    'echo specfile specfile >a/.gitattributes &&\n+     git archive HEAD >f.tar &&\n+     rm a/.gitattributes'\n+\n+test_expect_success \\\n+    'extract specfile' \\\n+    '(mkdir f && cd f && $TAR xf -) <f.tar'\n+\n+test_expect_success \\\n+     'validate specfile contents' \\\n+     'git log --max-count=1 \"--pretty=format:$SPECFILEFORMAT\" HEAD \\\n+      >f/a/specfile.expected &&\n+      diff f/a/specfile.expected f/a/specfile'\n+\n+test_expect_success \\\n     'git archive --format=zip' \\\n     'git archive --format=zip HEAD >d.zip'\n \n-- \n1.5.3\n"},{"id":"52372","messageId":"Pine.LNX.4.64.0709031935540.28586@racer.site","threadId":"9765","inReplyTo":"46DC4D45.4030208@lsrfire.ath.cx","subject":"Re: [PATCH 2/3] archive: specfile support (--pretty=format: in archive files)","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-03T18:40:04Z","receivedAt":"2007-09-03T18:40:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 3 Sep 2007, Ren? Scharfe wrote:\n\n> Add support for a new attribute, specfile.  Files marked as being\n> specfiles are expanded by git-archive when they are written to an\n> archive.  It has no effect on worktree files.  The same placeholders\n> as those for the option --pretty=format: of git-log et al. can be\n> used.\n\nI almost like this approach.  Would it not be a little more useful if you \ncould mark the placeholders with something like \"$Format: xyz %c$\"?  \nBecause then we could just shut up all those complainers that want to \ninsert some revision specific information into the files, without \naffecting formats for printf().\n\nOf course, the idea to keep the worktree unaffected is brilliant.\n\nCiao,\nDscho\n"},{"id":"52376","messageId":"85ps0zmrhs.fsf@lola.goethe.zz","threadId":"9765","inReplyTo":"Pine.LNX.4.64.0709031935540.28586@racer.site","subject":"Re: [PATCH 2/3] archive: specfile support (--pretty=format: in archive files)","fromName":"David Kastrup","fromEmail":"dak@gnu.org","sentAt":"2007-09-03T20:19:27Z","receivedAt":"2007-09-03T20:19:27Z","isPatch":true,"sender":{"key":"dak@gnu.org","avatar":"https://avatars.githubusercontent.com/u/52141349?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> On Mon, 3 Sep 2007, Ren? Scharfe wrote:\n>\n>> Add support for a new attribute, specfile.  Files marked as being\n>> specfiles are expanded by git-archive when they are written to an\n>> archive.  It has no effect on worktree files.  The same placeholders\n>> as those for the option --pretty=format: of git-log et al. can be\n>> used.\n>\n> I almost like this approach.  Would it not be a little more useful if you \n> could mark the placeholders with something like \"$Format: xyz %c$\"?  \n> Because then we could just shut up all those complainers that want to \n> insert some revision specific information into the files, without \n> affecting formats for printf().\n>\n> Of course, the idea to keep the worktree unaffected is brilliant.\n\nI think a bit more layering would be helpful: when using git-svn, one\nwould want to have things like $Id$ and $Date$ expanded, so maybe\nattribute specs like\n\nsomefile: expandmarkers=\"$Date: %aD$ $Id: ....$\"\n\nwould be nice having.  In the case of git-svn, I would expect them to\nbe generated from git-svn from the respective svn properties, so that\nthe user is not bothered with figuring out the awful $Id$ and whatever\nstrings.\n\n-- \nDavid Kastrup, Kriemhildstr. 15, 44793 Bochum\n"},{"id":"52380","messageId":"7vtzqb8fw2.fsf@gitster.siamese.dyndns.org","threadId":"9765","inReplyTo":"46DC4D45.4030208@lsrfire.ath.cx","subject":"Re: [PATCH 2/3] archive: specfile support (--pretty=format: in archive files)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-03T23:53:49Z","receivedAt":"2007-09-03T23:53:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:\n\n> The attribute is useful for creating auto-updating specfiles.  It is\n> limited by the underlying function format_commit_message(), though.\n> E.g. currently there is no placeholder for git-describe like output,\n> and expanded specfiles can't contain NUL bytes.  That can be fixed\n> in format_commit_message() later and will then benefit users of\n> git-log, too.\n\nInteresting.  I however wonder if \"specfile\" is a good name for\nthis attribute, although I admit I do not think of anything\nbetter offhand.\n\n> Why did it take me that long to come up with such a simple patch?\n> There was a vacation and a feature freeze in between, but above all\n> I was only recently able to convince myself (using ugly code) that\n> format_commit_message() can indeed be made to expand placeholders\n> to git-describe strings..\n\nThanks.  Will take a look.\n"},{"id":"52394","messageId":"46DCF0EF.9020604@op5.se","threadId":"9765","inReplyTo":"7vtzqb8fw2.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 2/3] archive: specfile support (--pretty=format: in archive files)","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2007-09-04T05:45:19Z","receivedAt":"2007-09-04T05:45:19Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"Junio C Hamano wrote:\n> René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:\n> \n>> The attribute is useful for creating auto-updating specfiles.  It is\n>> limited by the underlying function format_commit_message(), though.\n>> E.g. currently there is no placeholder for git-describe like output,\n>> and expanded specfiles can't contain NUL bytes.  That can be fixed\n>> in format_commit_message() later and will then benefit users of\n>> git-log, too.\n> \n> Interesting.  I however wonder if \"specfile\" is a good name for\n> this attribute, although I admit I do not think of anything\n> better offhand.\n> \n\n\"releasefile\", perhaps?\n\n-- \nAndreas Ericsson                   andreas.ericsson@op5.se\nOP5 AB                             www.op5.se\nTel: +46 8-230225                  Fax: +46 8-230231\n"},{"id":"52418","messageId":"Pine.LNX.4.64.0709041139140.28586@racer.site","threadId":"9765","inReplyTo":"46DCF0EF.9020604@op5.se","subject":"Re: [PATCH 2/3] archive: specfile support (--pretty=format: in archive files)","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-04T10:41:15Z","receivedAt":"2007-09-04T10:41:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Tue, 4 Sep 2007, Andreas Ericsson wrote:\n\n> Junio C Hamano wrote:\n> > Ren? Scharfe <rene.scharfe@lsrfire.ath.cx> writes:\n> > \n> > > The attribute is useful for creating auto-updating specfiles.  It is \n> > > limited by the underlying function format_commit_message(), though. \n> > > E.g. currently there is no placeholder for git-describe like output, \n> > > and expanded specfiles can't contain NUL bytes.  That can be fixed \n> > > in format_commit_message() later and will then benefit users of \n> > > git-log, too.\n> > \n> > Interesting. I however wonder if \"specfile\" is a good name for this \n> > attribute, although I admit I do not think of anything better offhand.\n> \n> \"releasefile\", perhaps?\n\nMaybe we should not so much name it by purpose, but by function.  How \nabout \"substformat\" for the attribute name, and replacing any \n$Format:blablub$ inside those files with something a la \n--pretty=format:blablub?\n\nCiao,\nDscho\n"},{"id":"52526","messageId":"46DDE69C.1080908@lsrfire.ath.cx","threadId":"9765","inReplyTo":"Pine.LNX.4.64.0709041139140.28586@racer.site","subject":"Re: [PATCH 2/3] archive: specfile support (--pretty=format: in archive files)","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-09-04T23:13:32Z","receivedAt":"2007-09-04T23:13:32Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Johannes Schindelin schrieb:\n> Hi,\n> \n> On Tue, 4 Sep 2007, Andreas Ericsson wrote:\n> \n>> Junio C Hamano wrote:\n>>> René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:\n>>>\n>>>> The attribute is useful for creating auto-updating specfiles.  It is \n>>>> limited by the underlying function format_commit_message(), though. \n>>>> E.g. currently there is no placeholder for git-describe like output, \n>>>> and expanded specfiles can't contain NUL bytes.  That can be fixed \n>>>> in format_commit_message() later and will then benefit users of \n>>>> git-log, too.\n>>> Interesting. I however wonder if \"specfile\" is a good name for this \n>>> attribute, although I admit I do not think of anything better offhand.\n>> \"releasefile\", perhaps?\n> \n> Maybe we should not so much name it by purpose, but by function.  How \n> about \"substformat\" for the attribute name, and replacing any \n> $Format:blablub$ inside those files with something a la \n> --pretty=format:blablub?\n\nI like the $Format:...$ notation.  How about naming the attribute\n\"template\", as that's what a thus marked file is?\n\nRené\n"},{"id":"52527","messageId":"46DDE6AE.10107@lsrfire.ath.cx","threadId":"9765","inReplyTo":"85ps0zmrhs.fsf@lola.goethe.zz","subject":"Re: [PATCH 2/3] archive: specfile support (--pretty=format: in archive files)","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-09-04T23:13:50Z","receivedAt":"2007-09-04T23:13:50Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"David Kastrup schrieb:\n> I think a bit more layering would be helpful: when using git-svn, one\n> would want to have things like $Id$ and $Date$ expanded, so maybe\n> attribute specs like\n> \n> somefile: expandmarkers=\"$Date: %aD$ $Id: ....$\"\n> \n> would be nice having.  In the case of git-svn, I would expect them to\n> be generated from git-svn from the respective svn properties, so that\n> the user is not bothered with figuring out the awful $Id$ and whatever\n> strings.\n\nEek!  I'd rather shove this into a new layer below specfile/template\nexpansion, which would do something like that:\n\n   s/\\$Date:.*\\$/$Format:$$Date: %aD$/\n\nThis way A) the complexity and ugliness affects only the users of these\nfields, while $Format:...$ users are not adversely affected, and B) we\ncan first implement $Format:...$ handling and add the rest later.\n\nIn order to expand $Id$, git-archive would need to run some kind of\ncallback, right? :-/  Let's first see if such a thing is really needed.\n\nRené\n"},{"id":"52528","messageId":"46DDE6B3.8010507@lsrfire.ath.cx","threadId":"9765","inReplyTo":"7vtzqb8fw2.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 2/3] archive: specfile support (--pretty=format: in archive files)","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-09-04T23:13:55Z","receivedAt":"2007-09-04T23:13:55Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Junio C Hamano schrieb:\n>> Why did it take me that long to come up with such a simple patch?\n>> There was a vacation and a feature freeze in between, but above all\n>> I was only recently able to convince myself (using ugly code) that\n>> format_commit_message() can indeed be made to expand placeholders\n>> to git-describe strings..\n> \n> Thanks.  Will take a look.\n\nHere's that ugly code, by the way.  It adds two placeholders, %d for\ndescription and %D for description depth.  Shortcomings of this code:\nit adds three members to struct commit, it unconditionally computes\nthe description when format_commit_message() -- even if the format\nstring doesn't contain %d and %D, the patch is not nicely split up.\nBut it convinced me that describe *can* indeed be librarified. :-)\n\nRené\n\n\n Makefile           |    1 +\n builtin-describe.c |  219 +++------------------------------------------------\n cache.h            |    5 +\n commit.c           |   31 ++++++++\n commit.h           |    5 +\n describe.c         |  170 ++++++++++++++++++++++++++++++++++++++++\n 6 files changed, 225 insertions(+), 206 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 51af531..7ec95f8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -296,6 +296,7 @@ DIFF_OBJS = \\\n LIB_OBJS = \\\n \tblob.o commit.o connect.o csum-file.o cache-tree.o base85.o \\\n \tdate.o diff-delta.o entry.o exec_cmd.o ident.o \\\n+\tdescribe.o \\\n \tinterpolate.o \\\n \tlockfile.o \\\n \tpatch-ids.o \\\ndiff --git a/builtin-describe.c b/builtin-describe.c\nindex 669110c..50ec08f 100644\n--- a/builtin-describe.c\n+++ b/builtin-describe.c\n@@ -17,124 +17,13 @@ static int tags;\t/* But allow any tags if --tags is specified */\n static int abbrev = DEFAULT_ABBREV;\n static int max_candidates = 10;\n \n-struct commit_name {\n-\tint prio; /* annotated tag = 2, tag = 1, head = 0 */\n-\tchar path[FLEX_ARRAY]; /* more */\n-};\n-static const char *prio_names[] = {\n-\t\"head\", \"lightweight\", \"annotated\",\n-};\n-\n-static void add_to_known_names(const char *path,\n-\t\t\t       struct commit *commit,\n-\t\t\t       int prio)\n-{\n-\tstruct commit_name *e = commit->util;\n-\tif (!e || e->prio < prio) {\n-\t\tsize_t len = strlen(path)+1;\n-\t\tfree(e);\n-\t\te = xmalloc(sizeof(struct commit_name) + len);\n-\t\te->prio = prio;\n-\t\tmemcpy(e->path, path, len);\n-\t\tcommit->util = e;\n-\t}\n-}\n-\n-static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)\n-{\n-\tstruct commit *commit = lookup_commit_reference_gently(sha1, 1);\n-\tstruct object *object;\n-\tint prio;\n-\n-\tif (!commit)\n-\t\treturn 0;\n-\tobject = parse_object(sha1);\n-\t/* If --all, then any refs are used.\n-\t * If --tags, then any tags are used.\n-\t * Otherwise only annotated tags are used.\n-\t */\n-\tif (!prefixcmp(path, \"refs/tags/\")) {\n-\t\tif (object->type == OBJ_TAG)\n-\t\t\tprio = 2;\n-\t\telse\n-\t\t\tprio = 1;\n-\t}\n-\telse\n-\t\tprio = 0;\n-\n-\tif (!all) {\n-\t\tif (!prio)\n-\t\t\treturn 0;\n-\t\tif (!tags && prio < 2)\n-\t\t\treturn 0;\n-\t}\n-\tadd_to_known_names(all ? path + 5 : path + 10, commit, prio);\n-\treturn 0;\n-}\n-\n-struct possible_tag {\n-\tstruct commit_name *name;\n-\tint depth;\n-\tint found_order;\n-\tunsigned flag_within;\n-};\n-\n-static int compare_pt(const void *a_, const void *b_)\n-{\n-\tstruct possible_tag *a = (struct possible_tag *)a_;\n-\tstruct possible_tag *b = (struct possible_tag *)b_;\n-\tif (a->name->prio != b->name->prio)\n-\t\treturn b->name->prio - a->name->prio;\n-\tif (a->depth != b->depth)\n-\t\treturn a->depth - b->depth;\n-\tif (a->found_order != b->found_order)\n-\t\treturn a->found_order - b->found_order;\n-\treturn 0;\n-}\n-\n-static unsigned long finish_depth_computation(\n-\tstruct commit_list **list,\n-\tstruct possible_tag *best)\n-{\n-\tunsigned long seen_commits = 0;\n-\twhile (*list) {\n-\t\tstruct commit *c = pop_commit(list);\n-\t\tstruct commit_list *parents = c->parents;\n-\t\tseen_commits++;\n-\t\tif (c->object.flags & best->flag_within) {\n-\t\t\tstruct commit_list *a = *list;\n-\t\t\twhile (a) {\n-\t\t\t\tstruct commit *i = a->item;\n-\t\t\t\tif (!(i->object.flags & best->flag_within))\n-\t\t\t\t\tbreak;\n-\t\t\t\ta = a->next;\n-\t\t\t}\n-\t\t\tif (!a)\n-\t\t\t\tbreak;\n-\t\t} else\n-\t\t\tbest->depth++;\n-\t\twhile (parents) {\n-\t\t\tstruct commit *p = parents->item;\n-\t\t\tparse_commit(p);\n-\t\t\tif (!(p->object.flags & SEEN))\n-\t\t\t\tinsert_by_date(p, list);\n-\t\t\tp->object.flags |= c->object.flags;\n-\t\t\tparents = parents->next;\n-\t\t}\n-\t}\n-\treturn seen_commits;\n-}\n-\n static void describe(const char *arg, int last_one)\n {\n \tunsigned char sha1[20];\n-\tstruct commit *cmit, *gave_up_on = NULL;\n-\tstruct commit_list *list;\n+\tstruct commit *cmit;\n \tstatic int initialized = 0;\n-\tstruct commit_name *n;\n-\tstruct possible_tag all_matches[MAX_TAGS];\n-\tunsigned int match_cnt = 0, annotated_cnt = 0, cur_match;\n-\tunsigned long seen_commits = 0;\n+\tchar *name;\n+\tint depth = 0;\n \n \tif (get_sha1(arg, sha1))\n \t\tdie(\"Not a valid object name %s\", arg);\n@@ -144,100 +33,23 @@ static void describe(const char *arg, int last_one)\n \n \tif (!initialized) {\n \t\tinitialized = 1;\n-\t\tfor_each_ref(get_name, NULL);\n-\t}\n-\n-\tn = cmit->util;\n-\tif (n) {\n-\t\tprintf(\"%s\\n\", n->path);\n-\t\treturn;\n+\t\tload_commit_names(all ? 0 : (tags ? 1 : 2));\n \t}\n \n-\tif (debug)\n-\t\tfprintf(stderr, \"searching to describe %s\\n\", arg);\n-\n-\tlist = NULL;\n-\tcmit->object.flags = SEEN;\n-\tcommit_list_insert(cmit, &list);\n-\twhile (list) {\n-\t\tstruct commit *c = pop_commit(&list);\n-\t\tstruct commit_list *parents = c->parents;\n-\t\tseen_commits++;\n-\t\tn = c->util;\n-\t\tif (n) {\n-\t\t\tif (match_cnt < max_candidates) {\n-\t\t\t\tstruct possible_tag *t = &all_matches[match_cnt++];\n-\t\t\t\tt->name = n;\n-\t\t\t\tt->depth = seen_commits - 1;\n-\t\t\t\tt->flag_within = 1u << match_cnt;\n-\t\t\t\tt->found_order = match_cnt;\n-\t\t\t\tc->object.flags |= t->flag_within;\n-\t\t\t\tif (n->prio == 2)\n-\t\t\t\t\tannotated_cnt++;\n-\t\t\t}\n-\t\t\telse {\n-\t\t\t\tgave_up_on = c;\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t}\n-\t\tfor (cur_match = 0; cur_match < match_cnt; cur_match++) {\n-\t\t\tstruct possible_tag *t = &all_matches[cur_match];\n-\t\t\tif (!(c->object.flags & t->flag_within))\n-\t\t\t\tt->depth++;\n-\t\t}\n-\t\tif (annotated_cnt && !list) {\n-\t\t\tif (debug)\n-\t\t\t\tfprintf(stderr, \"finished search at %s\\n\",\n-\t\t\t\t\tsha1_to_hex(c->object.sha1));\n-\t\t\tbreak;\n-\t\t}\n-\t\twhile (parents) {\n-\t\t\tstruct commit *p = parents->item;\n-\t\t\tparse_commit(p);\n-\t\t\tif (!(p->object.flags & SEEN))\n-\t\t\t\tinsert_by_date(p, &list);\n-\t\t\tp->object.flags |= c->object.flags;\n-\t\t\tparents = parents->next;\n-\t\t}\n-\t}\n-\n-\tif (!match_cnt)\n+\tname = describe_commit(cmit, max_candidates, &depth);\n+\tif (!name)\n \t\tdie(\"cannot describe '%s'\", sha1_to_hex(cmit->object.sha1));\n+\tif (!all)\n+\t\tname += 5;\n \n-\tqsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);\n-\n-\tif (gave_up_on) {\n-\t\tinsert_by_date(gave_up_on, &list);\n-\t\tseen_commits--;\n-\t}\n-\tseen_commits += finish_depth_computation(&list, &all_matches[0]);\n-\tfree_commit_list(list);\n-\n-\tif (debug) {\n-\t\tfor (cur_match = 0; cur_match < match_cnt; cur_match++) {\n-\t\t\tstruct possible_tag *t = &all_matches[cur_match];\n-\t\t\tfprintf(stderr, \" %-11s %8d %s\\n\",\n-\t\t\t\tprio_names[t->name->prio],\n-\t\t\t\tt->depth, t->name->path);\n-\t\t}\n-\t\tfprintf(stderr, \"traversed %lu commits\\n\", seen_commits);\n-\t\tif (gave_up_on) {\n-\t\t\tfprintf(stderr,\n-\t\t\t\t\"more than %i tags found; listed %i most recent\\n\"\n-\t\t\t\t\"gave up search at %s\\n\",\n-\t\t\t\tmax_candidates, max_candidates,\n-\t\t\t\tsha1_to_hex(gave_up_on->object.sha1));\n-\t\t}\n-\t}\n-\tif (abbrev == 0)\n-\t\tprintf(\"%s\\n\", all_matches[0].name->path );\n+\tif (abbrev == 0 || depth == 0)\n+\t\tprintf(\"%s\\n\", name);\n \telse\n-\t\tprintf(\"%s-%d-g%s\\n\", all_matches[0].name->path,\n-\t\t       all_matches[0].depth,\n+\t\tprintf(\"%s-%d-g%s\\n\", name, depth,\n \t\t       find_unique_abbrev(cmit->object.sha1, abbrev));\n \n \tif (!last_one)\n-\t\tclear_commit_marks(cmit, -1);\n+\t\tclear_commit_name_flags(cmit);\n }\n \n int cmd_describe(int argc, const char **argv, const char *prefix)\n@@ -263,13 +75,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix)\n \t\t\tif (abbrev != 0 && (abbrev < MINIMUM_ABBREV || 40 < abbrev))\n \t\t\t\tabbrev = DEFAULT_ABBREV;\n \t\t}\n-\t\telse if (!prefixcmp(arg, \"--candidates=\")) {\n+\t\telse if (!prefixcmp(arg, \"--candidates=\"))\n \t\t\tmax_candidates = strtoul(arg + 13, NULL, 10);\n-\t\t\tif (max_candidates < 1)\n-\t\t\t\tmax_candidates = 1;\n-\t\t\telse if (max_candidates > MAX_TAGS)\n-\t\t\t\tmax_candidates = MAX_TAGS;\n-\t\t}\n \t\telse\n \t\t\tusage(describe_usage);\n \t}\ndiff --git a/cache.h b/cache.h\nindex 70abbd5..07ee149 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -600,4 +600,9 @@ extern int diff_auto_refresh_index;\n /* match-trees.c */\n void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);\n \n+/* describe.c */\n+struct commit;\n+extern void load_commit_names(int min_prio);\n+extern char *describe_commit(struct commit *cmit, int max_candidates, int *depthp);\n+\n #endif /* CACHE_H */\ndiff --git a/commit.c b/commit.c\nindex dc5a064..d2343f2 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -455,6 +455,19 @@ void clear_commit_marks(struct commit *commit, unsigned int mark)\n \t}\n }\n \n+void clear_commit_name_flags(struct commit *commit)\n+{\n+\tstruct commit_list *parents;\n+\n+\tcommit->name_flags = 0;\n+\n+\tfor (parents = commit->parents; parents; parents = parents->next) {\n+\t\t/* Have we already cleared this? */\n+\t\tif (parents->item->name_flags)\n+\t\t\tclear_commit_name_flags(parents->item);\n+\t}\n+}\n+\n /*\n  * Generic support for pretty-printing the header\n  */\n@@ -819,6 +832,8 @@ static long format_commit_message(const struct commit *commit,\n \t\t{ \"%Cblue\" },\t/* blue */\n \t\t{ \"%Creset\" },\t/* reset color */\n \t\t{ \"%n\" },\t/* newline */\n+\t\t{ \"%d\" },\t/* description */\n+\t\t{ \"%D\" },\t/* description depth */\n \t\t{ \"%m\" },\t/* left/right/bottom */\n \t};\n \tenum interp_index {\n@@ -837,6 +852,7 @@ static long format_commit_message(const struct commit *commit,\n \t\tIBODY,\n \t\tIRED, IGREEN, IBLUE, IRESET_COLOR,\n \t\tINEWLINE,\n+\t\tIDESC, IDESC_DEPTH,\n \t\tILEFT_RIGHT,\n \t};\n \tstruct commit_list *p;\n@@ -920,6 +936,21 @@ static long format_commit_message(const struct commit *commit,\n \t\tif (!table[i].value)\n \t\t\tinterp_set_entry(table, i, \"<unknown>\");\n \n+\t{\n+\tstruct commit *cmit = (struct commit *)commit;\n+\tchar *name;\n+\tchar tmp[20];\n+\tint depth;\n+\tload_commit_names(2);\n+\tname = describe_commit(cmit, 10, &depth);\n+\tif (!name)\n+\t\tname = \"\";\n+\tsprintf(tmp, \"%d\", depth);\n+\tinterp_set_entry(table, IDESC, name);\n+\tinterp_set_entry(table, IDESC_DEPTH, tmp);\n+\tclear_commit_name_flags(cmit);\n+\t}\n+\n \tdo {\n \t\tchar *buf = *buf_p;\n \t\tunsigned long space = *space_p;\ndiff --git a/commit.h b/commit.h\nindex 467872e..69b6d67 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -17,6 +17,9 @@ struct commit {\n \tstruct commit_list *parents;\n \tstruct tree *tree;\n \tchar *buffer;\n+\tchar *name;\n+\tunsigned int name_flags;\n+\tchar name_prio;\n };\n \n extern int save_commit_buffer;\n@@ -72,6 +75,8 @@ struct commit *pop_most_recent_commit(struct commit_list **list,\n struct commit *pop_commit(struct commit_list **stack);\n \n void clear_commit_marks(struct commit *commit, unsigned int mark);\n+void clear_commit_name_flags(struct commit *commit);\n+\n \n /*\n  * Performs an in-place topological sort of list supplied.\ndiff --git a/describe.c b/describe.c\nnew file mode 100644\nindex 0000000..acd4517\n--- /dev/null\n+++ b/describe.c\n@@ -0,0 +1,170 @@\n+#include \"cache.h\"\n+#include \"commit.h\"\n+#include \"tag.h\"\n+#include \"refs.h\"\n+\n+#define SEEN\t\t(1u<<0)\n+#define MAX_TAGS\t(FLAG_BITS - 1)\n+\n+struct possible_tag {\n+\tchar *name;\n+\tint name_prio;\n+\tint depth;\n+\tint found_order;\n+\tunsigned flag_within;\n+};\n+\n+static int get_name(const char *path, const unsigned char *sha1, int flag,\n+                    void *cb_data)\n+{\n+\tstruct commit *commit = lookup_commit_reference_gently(sha1, 1);\n+\tif (commit) {\n+\t\tstruct object *object = parse_object(sha1);\n+\t\tint min_prio = *((int *)cb_data);\n+\t\tint prio = 0;\n+\n+\t\tif (!prefixcmp(path, \"refs/tags/\")) {\n+\t\t\tif (object->type == OBJ_TAG)\n+\t\t\t\tprio = 2;\n+\t\t\telse\n+\t\t\t\tprio = 1;\n+\t\t}\n+\n+\t\tif (prio >= min_prio &&\n+\t\t    (!commit->name || commit->name_prio < prio)) {\n+\t\t\tfree(commit->name);\n+\t\t\tcommit->name = xstrdup(path + 5);\n+\t\t\tcommit->name_prio = prio;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+void load_commit_names(int min_prio)\n+{\n+\tfor_each_ref(get_name, &min_prio);\n+}\n+\n+static int compare_pt(const void *a_, const void *b_)\n+{\n+\tstruct possible_tag *a = (struct possible_tag *)a_;\n+\tstruct possible_tag *b = (struct possible_tag *)b_;\n+\tif (a->name_prio != b->name_prio)\n+\t\treturn b->name_prio - a->name_prio;\n+\tif (a->depth != b->depth)\n+\t\treturn a->depth - b->depth;\n+\tif (a->found_order != b->found_order)\n+\t\treturn a->found_order - b->found_order;\n+\treturn 0;\n+}\n+\n+static unsigned long finish_depth_computation(\n+\tstruct commit_list **list,\n+\tstruct possible_tag *best)\n+{\n+\tunsigned long seen_commits = 0;\n+\twhile (*list) {\n+\t\tstruct commit *c = pop_commit(list);\n+\t\tstruct commit_list *parents = c->parents;\n+\t\tseen_commits++;\n+\t\tif (c->name_flags & best->flag_within) {\n+\t\t\tstruct commit_list *a = *list;\n+\t\t\twhile (a) {\n+\t\t\t\tstruct commit *i = a->item;\n+\t\t\t\tif (!(i->name_flags & best->flag_within))\n+\t\t\t\t\tbreak;\n+\t\t\t\ta = a->next;\n+\t\t\t}\n+\t\t\tif (!a)\n+\t\t\t\tbreak;\n+\t\t} else\n+\t\t\tbest->depth++;\n+\t\twhile (parents) {\n+\t\t\tstruct commit *p = parents->item;\n+\t\t\tparse_commit(p);\n+\t\t\tif (!(p->name_flags & SEEN))\n+\t\t\t\tinsert_by_date(p, list);\n+\t\t\tp->name_flags |= c->name_flags;\n+\t\t\tparents = parents->next;\n+\t\t}\n+\t}\n+\treturn seen_commits;\n+}\n+\n+char *describe_commit(struct commit *cmit, int max_candidates, int *depthp)\n+{\n+\tstruct commit *gave_up_on = NULL;\n+\tstruct commit_list *list;\n+\tstruct possible_tag all_matches[MAX_TAGS];\n+\tunsigned int match_cnt = 0, annotated_cnt = 0, cur_match;\n+\tunsigned long seen_commits = 0;\n+\n+\tif (cmit->name) {\n+\t\t*depthp = 0;\n+\t\treturn cmit->name;\n+\t}\n+\n+\tif (max_candidates < 1)\n+\t\tmax_candidates = 1;\n+\telse if (max_candidates > MAX_TAGS)\n+\t\tmax_candidates = MAX_TAGS;\n+\n+\tlist = NULL;\n+\tcmit->name_flags = SEEN;\n+\tcommit_list_insert(cmit, &list);\n+\twhile (list) {\n+\t\tstruct commit *c = pop_commit(&list);\n+\t\tstruct commit_list *parents = c->parents;\n+\t\tseen_commits++;\n+\t\tif (c->name) {\n+\t\t\tif (match_cnt < max_candidates) {\n+\t\t\t\tstruct possible_tag *t = &all_matches[match_cnt++];\n+\t\t\t\tt->name = c->name;\n+\t\t\t\tt->name_prio = c->name_prio;\n+\t\t\t\tt->depth = seen_commits - 1;\n+\t\t\t\tt->flag_within = 1u << match_cnt;\n+\t\t\t\tt->found_order = match_cnt;\n+\t\t\t\tc->name_flags |= t->flag_within;\n+\t\t\t\tif (c->name_prio == 2)\n+\t\t\t\t\tannotated_cnt++;\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\tgave_up_on = c;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t\tfor (cur_match = 0; cur_match < match_cnt; cur_match++) {\n+\t\t\tstruct possible_tag *t = &all_matches[cur_match];\n+\t\t\tif (!(c->name_flags & t->flag_within))\n+\t\t\t\tt->depth++;\n+\t\t}\n+\t\tif (annotated_cnt && !list)\n+\t\t\tbreak;\n+\t\twhile (parents) {\n+\t\t\tstruct commit *p = parents->item;\n+\t\t\tparse_commit(p);\n+\t\t\tif (!(p->name_flags & SEEN))\n+\t\t\t\tinsert_by_date(p, &list);\n+\t\t\tp->name_flags |= c->name_flags;\n+\t\t\tparents = parents->next;\n+\t\t}\n+\t}\n+\n+\tif (!match_cnt) {\n+\t\tfree_commit_list(list);\n+\t\t*depthp = -1;\n+\t\treturn NULL;\n+\t}\n+\n+\tqsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);\n+\n+\tif (gave_up_on) {\n+\t\tinsert_by_date(gave_up_on, &list);\n+\t\tseen_commits--;\n+\t}\n+\tseen_commits += finish_depth_computation(&list, &all_matches[0]);\n+\tfree_commit_list(list);\n+\n+\t*depthp = all_matches[0].depth;\n+\treturn all_matches[0].name;\n+}\n"},{"id":"52537","messageId":"Pine.LNX.4.64.0709050111220.28586@racer.site","threadId":"9765","inReplyTo":"46DDE69C.1080908@lsrfire.ath.cx","subject":"Re: [PATCH 2/3] archive: specfile support (--pretty=format: in archive files)","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-05T00:12:30Z","receivedAt":"2007-09-05T00:12:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Wed, 5 Sep 2007, Ren? Scharfe wrote:\n\n> Johannes Schindelin schrieb:\n> \n> > On Tue, 4 Sep 2007, Andreas Ericsson wrote:\n> > \n> >> Junio C Hamano wrote:\n> >>> Ren? Scharfe <rene.scharfe@lsrfire.ath.cx> writes:\n> >>>\n> >>>> The attribute is useful for creating auto-updating specfiles.  It is \n> >>>> limited by the underlying function format_commit_message(), though. \n> >>>> E.g. currently there is no placeholder for git-describe like output, \n> >>>> and expanded specfiles can't contain NUL bytes.  That can be fixed \n> >>>> in format_commit_message() later and will then benefit users of \n> >>>> git-log, too.\n> >>> Interesting. I however wonder if \"specfile\" is a good name for this \n> >>> attribute, although I admit I do not think of anything better offhand.\n> >> \"releasefile\", perhaps?\n> > \n> > Maybe we should not so much name it by purpose, but by function.  How \n> > about \"substformat\" for the attribute name, and replacing any \n> > $Format:blablub$ inside those files with something a la \n> > --pretty=format:blablub?\n> \n> I like the $Format:...$ notation.  How about naming the attribute\n> \"template\", as that's what a thus marked file is?\n\nUnless somebody comes up with an even better name, yes (I like \"template\" \nbetter than \"substformat\" or \"releasefile\", but I still think that \n\"template\" is not descriptive enough).\n\nCiao,\nDscho\n"},{"id":"52534","messageId":"7v642qm0aw.fsf@gitster.siamese.dyndns.org","threadId":"9765","inReplyTo":"46DDE6B3.8010507@lsrfire.ath.cx","subject":"Re: [PATCH 2/3] archive: specfile support (--pretty=format: in archive files)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-05T00:19:03Z","receivedAt":"2007-09-05T00:19:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:\n\n> Here's that ugly code, by the way.  It adds two placeholders, %d for\n> description and %D for description depth.  Shortcomings of this code:\n> it adds three members to struct commit, it unconditionally computes\n> the description when format_commit_message() -- even if the format\n> string doesn't contain %d and %D, the patch is not nicely split up.\n> But it convinced me that describe *can* indeed be librarified. :-)\n\nYeah, unconditional computation for %d/%D is really a killer.\nOtherwise the change itself does not necessarily look too ugly,\nthough.\n"},{"id":"52535","messageId":"7vzm02klip.fsf@gitster.siamese.dyndns.org","threadId":"9765","inReplyTo":"46DDE69C.1080908@lsrfire.ath.cx","subject":"Re: [PATCH 2/3] archive: specfile support (--pretty=format: in archive files)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-05T00:23:42Z","receivedAt":"2007-09-05T00:23:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:\n\n>> Maybe we should not so much name it by purpose, but by function.  How \n>> about \"substformat\" for the attribute name, and replacing any \n>> $Format:blablub$ inside those files with something a la \n>> --pretty=format:blablub?\n>\n> I like the $Format:...$ notation.  How about naming the attribute\n> \"template\", as that's what a thus marked file is?\n\nSounds good, although I suspect \"template\" might confuse newbies\nthat checkout may apply the substitution as well.  How about\nsomething with \"export\" in it?  export-subst, perhaps?\n"},{"id":"52768","messageId":"46E028B9.2090908@lsrfire.ath.cx","threadId":"9765","inReplyTo":"7vzm02klip.fsf@gitster.siamese.dyndns.org","subject":"[PATCH 4/3] archive: specfile syntax change: \"$Format:%PLCHLDR$\" instead of just \"%PLCHLDR\"","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-09-06T16:20:09Z","receivedAt":"2007-09-06T16:20:09Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"As suggested by Johannes, --pretty=format: placeholders in specfiles\nneed to be wrapped in $Format:...$ now.  This syntax change restricts\nthe expansion of placeholders and makes it easier to use with files\nthat contain non-placeholder percent signs.\n\nSigned-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>\n---\n Documentation/gitattributes.txt |    5 +++-\n builtin-archive.c               |   52 +++++++++++++++++++++++++++++++++++---\n t/t5000-tar-tree.sh             |    4 +-\n 3 files changed, 53 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt\nindex 47a621b..37b3be8 100644\n--- a/Documentation/gitattributes.txt\n+++ b/Documentation/gitattributes.txt\n@@ -432,7 +432,10 @@ several placeholders when adding this file to an archive.  The\n expansion depends on the availability of a commit ID, i.e. if\n gitlink:git-archive[1] has been given a tree instead of a commit or a\n tag then no replacement will be done.  The placeholders are the same\n-as those for the option `--pretty=format:` of gitlink:git-log[1].\n+as those for the option `--pretty=format:` of gitlink:git-log[1],\n+except that they need to be wrapped like this: `$Format:PLACEHOLDERS$`\n+in the file.  E.g. the string `$Format:%H$` will be replaced by the\n+commit hash.\n \n \n GIT\ndiff --git a/builtin-archive.c b/builtin-archive.c\nindex faccce3..a8a0f01 100644\n--- a/builtin-archive.c\n+++ b/builtin-archive.c\n@@ -81,14 +81,58 @@ static int run_remote_archiver(const char *remote, int argc,\n \treturn !!rv;\n }\n \n+static void *format_specfile(const struct commit *commit, const char *format,\n+                             unsigned long *sizep)\n+{\n+\tunsigned long len = *sizep, result_len = 0;\n+\tconst char *a = format;\n+\tchar *result = NULL;\n+\n+\tfor (;;) {\n+\t\tconst char *b, *c;\n+\t\tchar *fmt, *formatted = NULL;\n+\t\tunsigned long a_len, fmt_len, formatted_len, allocated = 0;\n+\n+\t\tb = memchr(a, '$', len);\n+\t\tif (!b || a + len < b + 9 || memcmp(b + 1, \"Format:\", 7))\n+\t\t\tbreak;\n+\t\tc = memchr(b + 8, '$', len - 8);\n+\t\tif (!c)\n+\t\t\tbreak;\n+\n+\t\ta_len = b - a;\n+\t\tfmt_len = c - b - 8;\n+\t\tfmt = xmalloc(fmt_len + 1);\n+\t\tmemcpy(fmt, b + 8, fmt_len);\n+\t\tfmt[fmt_len] = '\\0';\n+\n+\t\tformatted_len = format_commit_message(commit, fmt, &formatted,\n+\t\t                                      &allocated);\n+\t\tresult = xrealloc(result, result_len + a_len + formatted_len);\n+\t\tmemcpy(result + result_len, a, a_len);\n+\t\tmemcpy(result + result_len + a_len, formatted, formatted_len);\n+\t\tresult_len += a_len + formatted_len;\n+\t\tlen -= c + 1 - a;\n+\t\ta = c + 1;\n+\t}\n+\n+\tif (result && len) {\n+\t\tresult = xrealloc(result, result_len + len);\n+\t\tmemcpy(result + result_len, a, len);\n+\t\tresult_len += len;\n+\t}\n+\n+\t*sizep = result_len;\n+\n+\treturn result;\n+}\n+\n static void *convert_to_archive(const char *path,\n                                 const void *src, unsigned long *sizep,\n                                 const struct commit *commit)\n {\n \tstatic struct git_attr *attr_specfile;\n \tstruct git_attr_check check[1];\n-\tchar *interpolated = NULL;\n-\tunsigned long allocated = 0;\n \n \tif (!commit)\n \t\treturn NULL;\n@@ -102,9 +146,7 @@ static void *convert_to_archive(const char *path,\n \tif (!ATTR_TRUE(check[0].value))\n \t\treturn NULL;\n \n-\t*sizep = format_commit_message(commit, src, &interpolated, &allocated);\n-\n-\treturn interpolated;\n+\treturn format_specfile(commit, src, sizep);\n }\n \n void *sha1_file_to_archive(const char *path, const unsigned char *sha1,\ndiff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh\nindex 3d5d01b..6e89e07 100755\n--- a/t/t5000-tar-tree.sh\n+++ b/t/t5000-tar-tree.sh\n@@ -36,7 +36,7 @@ test_expect_success \\\n      echo simple textfile >a/a &&\n      mkdir a/bin &&\n      cp /bin/sh a/bin &&\n-     printf \"%s\" \"$SPECFILEFORMAT\" >a/specfile &&\n+     printf \"A\\$Format:%s\\$O\" \"$SPECFILEFORMAT\" >a/specfile &&\n      ln -s a a/l1 &&\n      (p=long_path_to_a_file && cd a &&\n       for depth in 1 2 3 4 5; do mkdir $p && cd $p; done &&\n@@ -119,7 +119,7 @@ test_expect_success \\\n \n test_expect_success \\\n      'validate specfile contents' \\\n-     'git log --max-count=1 \"--pretty=format:$SPECFILEFORMAT\" HEAD \\\n+     'git log --max-count=1 \"--pretty=format:A${SPECFILEFORMAT}O\" HEAD \\\n       >f/a/specfile.expected &&\n       diff f/a/specfile.expected f/a/specfile'\n \n-- \n1.5.3\n"},{"id":"52777","messageId":"46E02FFF.8090902@lsrfire.ath.cx","threadId":"9765","inReplyTo":"7vzm02klip.fsf@gitster.siamese.dyndns.org","subject":"[PATCH 5/3] archive: rename attribute specfile to export-subst","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-09-06T16:51:11Z","receivedAt":"2007-09-06T16:51:11Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Junio C Hamano schrieb:\n> René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:\n> \n>>> Maybe we should not so much name it by purpose, but by function.  How \n>>> about \"substformat\" for the attribute name, and replacing any \n>>> $Format:blablub$ inside those files with something a la \n>>> --pretty=format:blablub?\n>> I like the $Format:...$ notation.  How about naming the attribute\n>> \"template\", as that's what a thus marked file is?\n> \n> Sounds good, although I suspect \"template\" might confuse newbies\n> that checkout may apply the substitution as well.  How about\n> something with \"export\" in it?  export-subst, perhaps?\n\nWell, including \"export\" in the name makes sense, yes.  I can't come up\nwith a better name, let's take this.\n\n--- snip! ---\nAs suggested by Junio and Johannes, change the name of the former\nattribute specfile to export-subst to indicate its function rather\nthan purpose and to make clear that it is not applied to working tree\nfiles.\n\nSigned-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>\n---\n\n Documentation/gitattributes.txt |    6 +++---\n builtin-archive.c               |   14 +++++++-------\n t/t5000-tar-tree.sh             |   18 +++++++++---------\n 3 files changed, 19 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt\nindex 37b3be8..d0e951e 100644\n--- a/Documentation/gitattributes.txt\n+++ b/Documentation/gitattributes.txt\n@@ -424,10 +424,10 @@ frotz\tunspecified\n Creating an archive\n ~~~~~~~~~~~~~~~~~~~\n \n-`specfile`\n-^^^^^^^^^^\n+`export-subst`\n+^^^^^^^^^^^^^^\n \n-If the attribute `specfile` is set for a file then git will expand\n+If the attribute `export-subst` is set for a file then git will expand\n several placeholders when adding this file to an archive.  The\n expansion depends on the availability of a commit ID, i.e. if\n gitlink:git-archive[1] has been given a tree instead of a commit or a\ndiff --git a/builtin-archive.c b/builtin-archive.c\nindex a8a0f01..af14837 100644\n--- a/builtin-archive.c\n+++ b/builtin-archive.c\n@@ -81,8 +81,8 @@ static int run_remote_archiver(const char *remote, int argc,\n \treturn !!rv;\n }\n \n-static void *format_specfile(const struct commit *commit, const char *format,\n-                             unsigned long *sizep)\n+static void *format_subst(const struct commit *commit, const char *format,\n+                          unsigned long *sizep)\n {\n \tunsigned long len = *sizep, result_len = 0;\n \tconst char *a = format;\n@@ -131,22 +131,22 @@ static void *convert_to_archive(const char *path,\n                                 const void *src, unsigned long *sizep,\n                                 const struct commit *commit)\n {\n-\tstatic struct git_attr *attr_specfile;\n+\tstatic struct git_attr *attr_export_subst;\n \tstruct git_attr_check check[1];\n \n \tif (!commit)\n \t\treturn NULL;\n \n-        if (!attr_specfile)\n-                attr_specfile = git_attr(\"specfile\", 8);\n+        if (!attr_export_subst)\n+                attr_export_subst = git_attr(\"export-subst\", 12);\n \n-\tcheck[0].attr = attr_specfile;\n+\tcheck[0].attr = attr_export_subst;\n \tif (git_checkattr(path, ARRAY_SIZE(check), check))\n \t\treturn NULL;\n \tif (!ATTR_TRUE(check[0].value))\n \t\treturn NULL;\n \n-\treturn format_specfile(commit, src, sizep);\n+\treturn format_subst(commit, src, sizep);\n }\n \n void *sha1_file_to_archive(const char *path, const unsigned char *sha1,\ndiff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh\nindex 6e89e07..42e28ab 100755\n--- a/t/t5000-tar-tree.sh\n+++ b/t/t5000-tar-tree.sh\n@@ -28,7 +28,7 @@ commit id embedding:\n TAR=${TAR:-tar}\n UNZIP=${UNZIP:-unzip}\n \n-SPECFILEFORMAT=%H%n\n+SUBSTFORMAT=%H%n\n \n test_expect_success \\\n     'populate workdir' \\\n@@ -36,7 +36,7 @@ test_expect_success \\\n      echo simple textfile >a/a &&\n      mkdir a/bin &&\n      cp /bin/sh a/bin &&\n-     printf \"A\\$Format:%s\\$O\" \"$SPECFILEFORMAT\" >a/specfile &&\n+     printf \"A\\$Format:%s\\$O\" \"$SUBSTFORMAT\" >a/substfile &&\n      ln -s a a/l1 &&\n      (p=long_path_to_a_file && cd a &&\n       for depth in 1 2 3 4 5; do mkdir $p && cd $p; done &&\n@@ -108,20 +108,20 @@ test_expect_success \\\n     'diff -r a c/prefix/a'\n \n test_expect_success \\\n-    'create an archive with a specfile' \\\n-    'echo specfile specfile >a/.gitattributes &&\n+    'create an archive with a substfile' \\\n+    'echo substfile export-subst >a/.gitattributes &&\n      git archive HEAD >f.tar &&\n      rm a/.gitattributes'\n \n test_expect_success \\\n-    'extract specfile' \\\n+    'extract substfile' \\\n     '(mkdir f && cd f && $TAR xf -) <f.tar'\n \n test_expect_success \\\n-     'validate specfile contents' \\\n-     'git log --max-count=1 \"--pretty=format:A${SPECFILEFORMAT}O\" HEAD \\\n-      >f/a/specfile.expected &&\n-      diff f/a/specfile.expected f/a/specfile'\n+     'validate substfile contents' \\\n+     'git log --max-count=1 \"--pretty=format:A${SUBSTFORMAT}O\" HEAD \\\n+      >f/a/substfile.expected &&\n+      diff f/a/substfile.expected f/a/substfile'\n \n test_expect_success \\\n     'git archive --format=zip' \\\n"},{"id":"52779","messageId":"Pine.LNX.4.64.0709061803590.28586@racer.site","threadId":"9765","inReplyTo":"46E028B9.2090908@lsrfire.ath.cx","subject":"Re: [PATCH 4/3] archive: specfile syntax change: \"$Format:%PLCHLDR$\" instead of just \"%PLCHLDR\"","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-06T17:11:04Z","receivedAt":"2007-09-06T17:11:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 6 Sep 2007, René Scharfe wrote:\n\n> As suggested by Johannes, --pretty=format: placeholders in specfiles \n> need to be wrapped in $Format:...$ now.\n\nThanks.\n\n> diff --git a/builtin-archive.c b/builtin-archive.c\n> index faccce3..a8a0f01 100644\n> --- a/builtin-archive.c\n> +++ b/builtin-archive.c\n> @@ -81,14 +81,58 @@ static int run_remote_archiver(const char *remote, int argc,\n>  \treturn !!rv;\n>  }\n>  \n> +static void *format_specfile(const struct commit *commit, const char *format,\n> +                             unsigned long *sizep)\n\nShould this not be \"char *buffer\" instead of \"const char *format\"?  Or \neven better: a \"struct strbuf *\"?\n\n> +{\n> +\tunsigned long len = *sizep, result_len = 0;\n> +\tconst char *a = format;\n> +\tchar *result = NULL;\n> +\n> +\tfor (;;) {\n> +\t\tconst char *b, *c;\n> +\t\tchar *fmt, *formatted = NULL;\n> +\t\tunsigned long a_len, fmt_len, formatted_len, allocated = 0;\n\nMaybe initialise formatted_len, just to be on the safe side?\n\n> +\n> +\t\tb = memchr(a, '$', len);\n> +\t\tif (!b || a + len < b + 9 || memcmp(b + 1, \"Format:\", 7))\n> +\t\t\tbreak;\n\nWouldn't memmem(buffer, len, \"$Format:\", 8) be better here?\n\nA general comment: since you plan to output the result into a file anyway, \nit should be even easier to avoid realloc(), and do a \nprint_formatted_specfile() instead of a format_specfile(), no?\n\nCiao,\nDscho\n"},{"id":"52780","messageId":"Pine.LNX.4.64.0709061811460.28586@racer.site","threadId":"9765","inReplyTo":"46E02FFF.8090902@lsrfire.ath.cx","subject":"Re: [PATCH 5/3] archive: rename attribute specfile to export-subst","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-06T17:13:29Z","receivedAt":"2007-09-06T17:13:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 6 Sep 2007, René Scharfe wrote:\n\n> As suggested by Junio and Johannes, change the name of the former\n> attribute specfile to export-subst to indicate its function rather\n> than purpose and to make clear that it is not applied to working tree\n> files.\n> \n> Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>\n\nACK!\n\n(Even if I did not really suggest \"export-subst\", which I like very \nmuch...)\n\nThe bigger question is now if these two patches should be folded back into \nyour original patch series, or stand alone as commits of their own...\n\nCiao,\nDscho"},{"id":"52798","messageId":"46E0647A.10000@lsrfire.ath.cx","threadId":"9765","inReplyTo":"Pine.LNX.4.64.0709061803590.28586@racer.site","subject":"Re: [PATCH 4/3] archive: specfile syntax change: \"$Format:%PLCHLDR$\" instead of just \"%PLCHLDR\"","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-09-06T20:35:06Z","receivedAt":"2007-09-06T20:35:06Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Johannes Schindelin schrieb:\n>> +static void *format_specfile(const struct commit *commit, const char *format,\n>> +                             unsigned long *sizep)\n> \n> Should this not be \"char *buffer\" instead of \"const char *format\"?  Or \n> even better: a \"struct strbuf *\"?\n\nconst is correct, because the data in the buffer shouldn't be (and\nisn't) modified.\n\n\"buffer\" is a generic term; \"format\" on the other hand indicates the\nmeaning of the data within the buffer.\n\nInput and output might indeed be passed along as struct strbuf, even\nmore so since the new API encourages its use as generic buffer (format\ncan contain NULs).\n\n>> +{\n>> +\tunsigned long len = *sizep, result_len = 0;\n>> +\tconst char *a = format;\n>> +\tchar *result = NULL;\n>> +\n>> +\tfor (;;) {\n>> +\t\tconst char *b, *c;\n>> +\t\tchar *fmt, *formatted = NULL;\n>> +\t\tunsigned long a_len, fmt_len, formatted_len, allocated = 0;\n> \n> Maybe initialise formatted_len, just to be on the safe side?\n\nI wish I could use C99 syntax and move its declaration down to its first\nuse, then it would be obvious that formatted_len is never used without\ninitialization.\n\n>> +\n>> +\t\tb = memchr(a, '$', len);\n>> +\t\tif (!b || a + len < b + 9 || memcmp(b + 1, \"Format:\", 7))\n>> +\t\t\tbreak;\n> \n> Wouldn't memmem(buffer, len, \"$Format:\", 8) be better here?\n\nOh, that's a nice GNU extension, didn't know it before.  We might import\nit to compat etc., but I think that's better left for a follow-up patch.\n\n> A general comment: since you plan to output the result into a file anyway, \n> it should be even easier to avoid realloc(), and do a \n> print_formatted_specfile() instead of a format_specfile(), no?\n\nHmm, not sure what you mean.  At least archive-tar needs the expanded\ncontents in a buffer (not immediately written to stdout) because it\ntries to mimic a real tar and always writes in blocks of 10k and\ntherefore needs to buffer the output.\n\nThanks!\nRené\n"},{"id":"52799","messageId":"46E06561.4020904@lsrfire.ath.cx","threadId":"9765","inReplyTo":"Pine.LNX.4.64.0709061811460.28586@racer.site","subject":"Re: [PATCH 5/3] archive: rename attribute specfile to export-subst","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-09-06T20:38:57Z","receivedAt":"2007-09-06T20:38:57Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Johannes Schindelin schrieb:\n> The bigger question is now if these two patches should be folded back\n> into your original patch series, or stand alone as commits of their\n> own...\n\nYes, indeed.  I submitted them as add-on patches because I noticed the\nfirst three are already in next, but I sure can make a fresh three-part\nseries based on master.  Junio?\n\nThanks,\nRené\n"},{"id":"52800","messageId":"46E068BB.5080202@lsrfire.ath.cx","threadId":"9765","inReplyTo":"46E0647A.10000@lsrfire.ath.cx","subject":"Re: [PATCH 4/3] archive: specfile syntax change: \"$Format:%PLCHLDR$\" instead of just \"%PLCHLDR\"","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-09-06T20:53:15Z","receivedAt":"2007-09-06T20:53:15Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"René Scharfe schrieb:\n> Johannes Schindelin schrieb:\n>>> +\n>>> +\t\tb = memchr(a, '$', len);\n>>> +\t\tif (!b || a + len < b + 9 || memcmp(b + 1, \"Format:\", 7))\n>>> +\t\t\tbreak;\n>> Wouldn't memmem(buffer, len, \"$Format:\", 8) be better here?\n> \n> Oh, that's a nice GNU extension, didn't know it before.  We might import\n> it to compat etc., but I think that's better left for a follow-up patch.\n\nJust noticed: if the memcmp() above finds a difference, the code should\n*not* break out of the loop.  Ahem.  Perhaps I should first add memmem()\nafter all...\n\nRené\n"},{"id":"52803","messageId":"7vd4wva4lv.fsf@gitster.siamese.dyndns.org","threadId":"9765","inReplyTo":"Pine.LNX.4.64.0709061811460.28586@racer.site","subject":"Re: [PATCH 5/3] archive: rename attribute specfile to export-subst","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-06T21:03:40Z","receivedAt":"2007-09-06T21:03:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> The bigger question is now if these two patches should be folded back into \n> your original patch series, or stand alone as commits of their own...\n\nThat is no brainer, as there is a simple and hard rule that any\ntopic already in 'next' are not to be rewound ever.  Follow-up\npatches are the right thing to do in this case.\n"},{"id":"52808","messageId":"46E08016.9030604@lsrfire.ath.cx","threadId":"9765","inReplyTo":"46E028B9.2090908@lsrfire.ath.cx","subject":"[PATCH 3.5/3] add memmem()","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-09-06T22:32:54Z","receivedAt":"2007-09-06T22:32:54Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"memmem() is a nice GNU extension for searching a length limited string\nin another one.\n\nThis compat version is based on the version found in glibc 2.2 (GPL 2);\nI only removed the optimization of checking the first char by hand, and\ngenerally tried to keep the code simple.  We can add it back if memcmp\nshows up high in a profile, but for now I prefer to keep it (almost\ntrivially) simple.\n\nSince I don't really know which platforms beside those with a glibc\nhave their own memmem(), I used a heuristic: if NO_STRCASESTR is set,\nthen NO_MEMMEM is set, too.\n\nSigned-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>\n---\n Makefile          |   11 +++++++++++\n compat/memmem.c   |   29 +++++++++++++++++++++++++++++\n git-compat-util.h |    6 ++++++\n 3 files changed, 46 insertions(+), 0 deletions(-)\n create mode 100644 compat/memmem.c\n\ndiff --git a/Makefile b/Makefile\nindex 51af531..bae073f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -28,6 +28,8 @@ all::\n #\n # Define NO_STRCASESTR if you don't have strcasestr.\n #\n+# Define NO_MEMMEM if you don't have memmem.\n+#\n # Define NO_STRLCPY if you don't have strlcpy.\n #\n # Define NO_STRTOUMAX if you don't have strtoumax in the C library.\n@@ -402,6 +404,7 @@ ifeq ($(uname_S),SunOS)\n \tNEEDS_NSL = YesPlease\n \tSHELL_PATH = /bin/bash\n \tNO_STRCASESTR = YesPlease\n+\tNO_MEMMEM = YesPlease\n \tNO_HSTRERROR = YesPlease\n \tifeq ($(uname_R),5.8)\n \t\tNEEDS_LIBICONV = YesPlease\n@@ -424,6 +427,7 @@ ifeq ($(uname_O),Cygwin)\n \tNO_D_TYPE_IN_DIRENT = YesPlease\n \tNO_D_INO_IN_DIRENT = YesPlease\n \tNO_STRCASESTR = YesPlease\n+\tNO_MEMMEM = YesPlease\n \tNO_SYMLINK_HEAD = YesPlease\n \tNEEDS_LIBICONV = YesPlease\n \tNO_FAST_WORKING_DIRECTORY = UnfortunatelyYes\n@@ -442,6 +446,7 @@ ifeq ($(uname_S),FreeBSD)\n endif\n ifeq ($(uname_S),OpenBSD)\n \tNO_STRCASESTR = YesPlease\n+\tNO_MEMMEM = YesPlease\n \tNEEDS_LIBICONV = YesPlease\n \tBASIC_CFLAGS += -I/usr/local/include\n \tBASIC_LDFLAGS += -L/usr/local/lib\n@@ -456,6 +461,7 @@ ifeq ($(uname_S),NetBSD)\n endif\n ifeq ($(uname_S),AIX)\n \tNO_STRCASESTR=YesPlease\n+\tNO_MEMMEM = YesPlease\n \tNO_STRLCPY = YesPlease\n \tNEEDS_LIBICONV=YesPlease\n endif\n@@ -467,6 +473,7 @@ ifeq ($(uname_S),IRIX64)\n \tNO_IPV6=YesPlease\n \tNO_SETENV=YesPlease\n \tNO_STRCASESTR=YesPlease\n+\tNO_MEMMEM = YesPlease\n \tNO_STRLCPY = YesPlease\n \tNO_SOCKADDR_STORAGE=YesPlease\n \tSHELL_PATH=/usr/gnu/bin/bash\n@@ -661,6 +668,10 @@ ifdef NO_HSTRERROR\n \tCOMPAT_CFLAGS += -DNO_HSTRERROR\n \tCOMPAT_OBJS += compat/hstrerror.o\n endif\n+ifdef NO_MEMMEM\n+\tCOMPAT_CFLAGS += -DNO_MEMMEM\n+\tCOMPAT_OBJS += compat/memmem.o\n+endif\n \n ifeq ($(TCLTK_PATH),)\n NO_TCLTK=NoThanks\ndiff --git a/compat/memmem.c b/compat/memmem.c\nnew file mode 100644\nindex 0000000..cd0d877\n--- /dev/null\n+++ b/compat/memmem.c\n@@ -0,0 +1,29 @@\n+#include \"../git-compat-util.h\"\n+\n+void *gitmemmem(const void *haystack, size_t haystack_len,\n+                const void *needle, size_t needle_len)\n+{\n+\tconst char *begin = haystack;\n+\tconst char *last_possible = begin + haystack_len - needle_len;\n+\n+\t/*\n+\t * The first occurrence of the empty string is deemed to occur at\n+\t * the beginning of the string.\n+\t */\n+\tif (needle_len == 0)\n+\t\treturn (void *)begin;\n+\n+\t/*\n+\t * Sanity check, otherwise the loop might search through the whole\n+\t * memory.\n+\t */\n+\tif (haystack_len < needle_len)\n+\t\treturn NULL;\n+\n+\tfor (; begin <= last_possible; begin++) {\n+\t\tif (!memcmp(begin, needle, needle_len))\n+\t\t\treturn (void *)begin;\n+\t}\n+\n+\treturn NULL;\n+}\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex ca0a597..1bfbdeb 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -172,6 +172,12 @@ extern uintmax_t gitstrtoumax(const char *, char **, int);\n extern const char *githstrerror(int herror);\n #endif\n \n+#ifdef NO_MEMMEM\n+#define memmem gitmemmem\n+void *gitmemmem(const void *haystack, size_t haystacklen,\n+                const void *needle, size_t needlelen);\n+#endif\n+\n extern void release_pack_memory(size_t, int);\n \n static inline char* xstrdup(const char *str)\n-- \n1.5.3\n"},{"id":"52809","messageId":"46E0805E.303@lsrfire.ath.cx","threadId":"9765","inReplyTo":"46E028B9.2090908@lsrfire.ath.cx","subject":"[PATCH 4/3] archive: specfile syntax change: \"$Format:%PLCHLDR$\" instead of just \"%PLCHLDR\" (take 2)","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-09-06T22:34:06Z","receivedAt":"2007-09-06T22:34:06Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"As suggested by Johannes, --pretty=format: placeholders in specfiles\nneed to be wrapped in $Format:...$ now.  This syntax change restricts\nthe expansion of placeholders and makes it easier to use with files\nthat contain non-placeholder percent signs.\n\nSigned-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>\n---\nThis replacement patch fixes a bug in the \"$Format:\" search logic.  It\nnow uses memmem() and thus depends on patch 3.5 which introduces this\nfunction.  Patch 5 still applies unchanged.\n\n Documentation/gitattributes.txt |    5 +++-\n builtin-archive.c               |   52 +++++++++++++++++++++++++++++++++++---\n t/t5000-tar-tree.sh             |    4 +-\n 3 files changed, 53 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt\nindex 47a621b..37b3be8 100644\n--- a/Documentation/gitattributes.txt\n+++ b/Documentation/gitattributes.txt\n@@ -432,7 +432,10 @@ several placeholders when adding this file to an archive.  The\n expansion depends on the availability of a commit ID, i.e. if\n gitlink:git-archive[1] has been given a tree instead of a commit or a\n tag then no replacement will be done.  The placeholders are the same\n-as those for the option `--pretty=format:` of gitlink:git-log[1].\n+as those for the option `--pretty=format:` of gitlink:git-log[1],\n+except that they need to be wrapped like this: `$Format:PLACEHOLDERS$`\n+in the file.  E.g. the string `$Format:%H$` will be replaced by the\n+commit hash.\n \n \n GIT\ndiff --git a/builtin-archive.c b/builtin-archive.c\nindex faccce3..65bf9cb 100644\n--- a/builtin-archive.c\n+++ b/builtin-archive.c\n@@ -81,14 +81,58 @@ static int run_remote_archiver(const char *remote, int argc,\n \treturn !!rv;\n }\n \n+static void *format_specfile(const struct commit *commit, const char *format,\n+                             unsigned long *sizep)\n+{\n+\tunsigned long len = *sizep, result_len = 0;\n+\tconst char *a = format;\n+\tchar *result = NULL;\n+\n+\tfor (;;) {\n+\t\tconst char *b, *c;\n+\t\tchar *fmt, *formatted = NULL;\n+\t\tunsigned long a_len, fmt_len, formatted_len, allocated = 0;\n+\n+\t\tb = memmem(a, len, \"$Format:\", 8);\n+\t\tif (!b || a + len < b + 9)\n+\t\t\tbreak;\n+\t\tc = memchr(b + 8, '$', len - 8);\n+\t\tif (!c)\n+\t\t\tbreak;\n+\n+\t\ta_len = b - a;\n+\t\tfmt_len = c - b - 8;\n+\t\tfmt = xmalloc(fmt_len + 1);\n+\t\tmemcpy(fmt, b + 8, fmt_len);\n+\t\tfmt[fmt_len] = '\\0';\n+\n+\t\tformatted_len = format_commit_message(commit, fmt, &formatted,\n+\t\t                                      &allocated);\n+\t\tresult = xrealloc(result, result_len + a_len + formatted_len);\n+\t\tmemcpy(result + result_len, a, a_len);\n+\t\tmemcpy(result + result_len + a_len, formatted, formatted_len);\n+\t\tresult_len += a_len + formatted_len;\n+\t\tlen -= c + 1 - a;\n+\t\ta = c + 1;\n+\t}\n+\n+\tif (result && len) {\n+\t\tresult = xrealloc(result, result_len + len);\n+\t\tmemcpy(result + result_len, a, len);\n+\t\tresult_len += len;\n+\t}\n+\n+\t*sizep = result_len;\n+\n+\treturn result;\n+}\n+\n static void *convert_to_archive(const char *path,\n                                 const void *src, unsigned long *sizep,\n                                 const struct commit *commit)\n {\n \tstatic struct git_attr *attr_specfile;\n \tstruct git_attr_check check[1];\n-\tchar *interpolated = NULL;\n-\tunsigned long allocated = 0;\n \n \tif (!commit)\n \t\treturn NULL;\n@@ -102,9 +146,7 @@ static void *convert_to_archive(const char *path,\n \tif (!ATTR_TRUE(check[0].value))\n \t\treturn NULL;\n \n-\t*sizep = format_commit_message(commit, src, &interpolated, &allocated);\n-\n-\treturn interpolated;\n+\treturn format_specfile(commit, src, sizep);\n }\n \n void *sha1_file_to_archive(const char *path, const unsigned char *sha1,\ndiff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh\nindex 3d5d01b..6e89e07 100755\n--- a/t/t5000-tar-tree.sh\n+++ b/t/t5000-tar-tree.sh\n@@ -36,7 +36,7 @@ test_expect_success \\\n      echo simple textfile >a/a &&\n      mkdir a/bin &&\n      cp /bin/sh a/bin &&\n-     printf \"%s\" \"$SPECFILEFORMAT\" >a/specfile &&\n+     printf \"A\\$Format:%s\\$O\" \"$SPECFILEFORMAT\" >a/specfile &&\n      ln -s a a/l1 &&\n      (p=long_path_to_a_file && cd a &&\n       for depth in 1 2 3 4 5; do mkdir $p && cd $p; done &&\n@@ -119,7 +119,7 @@ test_expect_success \\\n \n test_expect_success \\\n      'validate specfile contents' \\\n-     'git log --max-count=1 \"--pretty=format:$SPECFILEFORMAT\" HEAD \\\n+     'git log --max-count=1 \"--pretty=format:A${SPECFILEFORMAT}O\" HEAD \\\n       >f/a/specfile.expected &&\n       diff f/a/specfile.expected f/a/specfile'\n \n-- \n1.5.3\n"},{"id":"52815","messageId":"7vwsv38juq.fsf@gitster.siamese.dyndns.org","threadId":"9765","inReplyTo":"46E068BB.5080202@lsrfire.ath.cx","subject":"Re: [PATCH 4/3] archive: specfile syntax change: \"$Format:%PLCHLDR$\" instead of just \"%PLCHLDR\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-06T23:17:17Z","receivedAt":"2007-09-06T23:17:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:\n\n> Just noticed: if the memcmp() above finds a difference, the code should\n> *not* break out of the loop.  Ahem.  Perhaps I should first add memmem()\n> after all...\n\nThat sounds like a sensible thing to do.  \n\nWill drop this copy and use updated 4/3 and 3.5/3; thanks.\n"},{"id":"52890","messageId":"Pine.LNX.4.64.0709071142340.28586@racer.site","threadId":"9765","inReplyTo":"46E0647A.10000@lsrfire.ath.cx","subject":"Re: [PATCH 4/3] archive: specfile syntax change: \"$Format:%PLCHLDR$\" instead of just \"%PLCHLDR\"","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-07T10:44:05Z","receivedAt":"2007-09-07T10:44:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 6 Sep 2007, René Scharfe wrote:\n\n> Johannes Schindelin schrieb:\n> >> +\n> >> +\t\tb = memchr(a, '$', len);\n> >> +\t\tif (!b || a + len < b + 9 || memcmp(b + 1, \"Format:\", 7))\n> >> +\t\t\tbreak;\n> > \n> > Wouldn't memmem(buffer, len, \"$Format:\", 8) be better here?\n> \n> Oh, that's a nice GNU extension, didn't know it before.\n\nOh sorry, I didn't even realise that this is a GNU extension...\n\n> > A general comment: since you plan to output the result into a file \n> > anyway, it should be even easier to avoid realloc(), and do a \n> > print_formatted_specfile() instead of a format_specfile(), no?\n> \n> Hmm, not sure what you mean.  At least archive-tar needs the expanded \n> contents in a buffer (not immediately written to stdout) because it \n> tries to mimic a real tar and always writes in blocks of 10k and \n> therefore needs to buffer the output.\n\nYeah, I missed that.  Thanks for explaining it to me!\n\nCiao,\nDscho\n"},{"id":"52891","messageId":"Pine.LNX.4.64.0709071144450.28586@racer.site","threadId":"9765","inReplyTo":"7vd4wva4lv.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 5/3] archive: rename attribute specfile to export-subst","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-07T10:45:10Z","receivedAt":"2007-09-07T10:45:10Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 6 Sep 2007, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > The bigger question is now if these two patches should be folded back \n> > into your original patch series, or stand alone as commits of their \n> > own...\n> \n> That is no brainer, as there is a simple and hard rule that any topic \n> already in 'next' are not to be rewound ever.  Follow-up patches are the \n> right thing to do in this case.\n\nRight.  At the time I suggested it, I was not aware that the patches were \nin next already.\n\nSorry,\nDscho\n"}]}