{"thread":{"id":"65326","subject":"[GSoC PATCH 0/3] preserve promisor files content after repack","startedAt":"2026-03-21T21:28:24Z","lastAt":"2026-05-19T15:17:49Z","messageCount":79,"participants":["LorenzoPegorari","Eric Sunshine","Lorenzo Pegorari","Junio C Hamano","Tian Yuchen","Elijah Newren"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"539616","messageId":"cover.1774125871.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":null,"subject":"[GSoC PATCH 0/3] preserve promisor files content after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-21T21:28:20Z","receivedAt":"2026-03-21T21:28:24Z","isPatch":true,"body":"The goal of this patch is to solve the NEEDSWORK comment added by\n5374a290 (fetch-pack: write fetched refs to .promisor, 14/10/2019). This\nis done by adding a helper function that takes the content of all\n.promisor files in the `repository`, and copies it inside the first\n.promisor file created by the repack.\n\nAlso, I added a comment explaining what is the purpose of the content of\nthe .promisor files, since this wasn't explained anywhere (I found\ninformation regarding this only in the message of the previously cited\ncommit).\n\nI am not satisfied at all with this patch, and I would love to have some\nfeedback for the v2. The issues/questions that I had while writing these\npatches are the following:\n * Is there a way to not have to check line by line if the content of\n   .promisor files are already inside the destination .promisor file?\n * Does it make sense to copy everything inside the first .promisor file\n   created by the repack? Is it worth the effort make sure to copy each\n   ref (and associatde hash) inside the .promisor file of the packfile\n   that contains that ref?\n\n\nLorenzoPegorari (3):\n  pack-write: add explanation to promisor file content\n  pack-write: add helper to fill promisor file after repack\n  repack-promisor: preserve content of promisor files after repack\n\n Documentation/git-repack.adoc |  4 +-\n pack-write.c                  | 71 +++++++++++++++++++++++++++++++++++\n pack.h                        |  1 +\n repack-promisor.c             | 23 ++++++++----\n 4 files changed, 89 insertions(+), 10 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"539617","messageId":"9bba49563e809634e223c3b9d75496cde94707db.1774125871.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1774125871.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH 1/3] pack-write: add explanation to promisor file content","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-21T21:28:35Z","receivedAt":"2026-03-21T21:28:39Z","isPatch":true,"body":"In the entire codebase there is no explanation as to why the \".promisor\"\nfiles may contain the ref names (and their associated hashes) that were\nfetched at the time the corresponding packfile was downloaded.\n\nAdd comment explaining that these pieces of information are used only for\ndebugging reasons, and how they can be used while debugging.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n pack-write.c | 9 +++++++++\n 1 file changed, 9 insertions(+)\n\ndiff --git a/pack-write.c b/pack-write.c\nindex 83eaf88541..6a2023327e 100644\n--- a/pack-write.c\n+++ b/pack-write.c\n@@ -603,6 +603,15 @@ void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_\n \tint i, err;\n \tFILE *output = xfopen(promisor_name, \"w\");\n \n+\t/*\n+\t* Write in the .promisor file the ref names and associated hashes,\n+\t* obtained by fetch-pack, at the point of generation of the\n+\t* corresponding packfile. These pieces of info are only used to make\n+\t* it easier to debug issues with partial clones, as we can identify\n+\t* what refs (and their associated hashes) were fetched at the time\n+\t* the packfile was downloaded, and if necessary, compare those hashes\n+\t* against what the promisor remote reports now.\n+\t*/\n \tfor (i = 0; i < nr_sought; i++)\n \t\tfprintf(output, \"%s %s\\n\", oid_to_hex(&sought[i]->old_oid),\n \t\t\tsought[i]->name);\n-- \n2.43.0\n\n"},{"id":"539618","messageId":"3c0702f81b3e4a57cd38284ab90f8277da3fcd40.1774125871.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1774125871.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH 2/3] pack-write: add helper to fill promisor file after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-21T21:28:51Z","receivedAt":"2026-03-21T21:28:55Z","isPatch":true,"body":"Create a `copy_all_promisor_files()` helper function used to copy the\ncontents of all \".promisor\" files in a `repository` inside another\n\".promisor\" file.\n\nThis function can be used to preserve the contents of all \".promisor\"\nfiles inside a new \".promisor\" file, for example when a repack happens.\n\nThis function is written in such a way so that it will read all the\n\".promisor\" files inside the given `repository` line by line, and copy\nonly the lines that are not already present in the destination file. This\nis done to avoid copying the same lines multiple times that may come from\nmultiple (redundant) packfiles. A better way to achieve this might be (is\ndefinitely) possible.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n pack-write.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n pack.h       |  1 +\n 2 files changed, 63 insertions(+)\n\ndiff --git a/pack-write.c b/pack-write.c\nindex 6a2023327e..3620e6bd02 100644\n--- a/pack-write.c\n+++ b/pack-write.c\n@@ -621,3 +621,65 @@ void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_\n \tif (err)\n \t\tdie(_(\"could not write '%s' promisor file\"), promisor_name);\n }\n+\n+void copy_all_promisor_files(struct repository *repo, const char *promisor_name)\n+{\n+\tstruct strbuf promisor_source_name = STRBUF_INIT;\n+\tstruct strbuf read_source = STRBUF_INIT, read_dest = STRBUF_INIT;\n+\tstruct strbuf write_dest = STRBUF_INIT;\n+\tint err;\n+\n+\tFILE *dest = xfopen(promisor_name, \"r+\");\n+\n+\tstruct packed_git *p;\n+\trepo_for_each_pack(repo, p) {\n+\t\tif (!p->pack_promisor)\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&promisor_source_name);\n+\t\tstrbuf_addstr(&promisor_source_name, p->pack_name);\n+\t\tstrbuf_strip_suffix(&promisor_source_name, \".pack\");\n+\t\tstrbuf_addstr(&promisor_source_name, \".promisor\");\n+\t\tFILE *source = xfopen(promisor_source_name.buf, \"r\");\n+\n+\t\t/*\n+\t\t * For each line of the promisor source file, check if it already\n+\t\t * is in the promisor dest file. If not, add it to write_dest, so\n+\t\t * that it will be written in the dest file.\n+\t\t */\n+\t\twhile (strbuf_getline(&read_source, source) != EOF) {\n+\t\t\tif (fseek(dest, 0L, SEEK_SET))\n+\t\t\t\tdie_errno(_(\"fseek failed\"));\n+\t\t\tint is_source_in_dest = 0;\n+\t\t\twhile (strbuf_getline(&read_dest, dest) != EOF) {\n+\t\t\t\tif (!strbuf_cmp(&read_source, &read_dest)) {\n+\t\t\t\t\tis_source_in_dest = 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (!is_source_in_dest) {\n+\t\t\t\tstrbuf_addbuf(&write_dest, &read_source);\n+\t\t\t\tstrbuf_addstr(&write_dest, \"\\n\");\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (write_dest.len) {\n+\t\t\tstrbuf_strip_suffix(&write_dest, \"\\n\");\n+\t\t\tif (fseek(dest, 0L, SEEK_END))\n+\t\t\t\tdie_errno(_(\"fseek failed\"));\n+\t\t\tfprintf(dest, \"%s\\n\", write_dest.buf);\n+\t\t\tfflush(dest);\n+\t\t\tstrbuf_reset(&write_dest);\n+\t\t}\n+\n+\t\terr = ferror(source);\n+\t\terr |= fclose(source);\n+\t\tif (err)\n+\t\t\tdie(_(\"could not read '%s' promisor file\"), promisor_source_name.buf);\n+\t}\n+\n+\terr = ferror(dest);\n+\terr |= fclose(dest);\n+\tif (err)\n+\t\tdie(_(\"could not write '%s' promisor file\"), promisor_name);\n+}\ndiff --git a/pack.h b/pack.h\nindex ec76472e49..509e90edba 100644\n--- a/pack.h\n+++ b/pack.h\n@@ -105,6 +105,7 @@ char *index_pack_lockfile(struct repository *r, int fd, int *is_well_formed);\n struct ref;\n \n void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_sought);\n+void copy_all_promisor_files(struct repository *repo, const char *promisor_name);\n \n char *write_rev_file(struct repository *repo,\n \t\t     const char *rev_name,\n-- \n2.43.0\n\n"},{"id":"539619","messageId":"6967066fe347cc2377d0f5dce14cfa7e569a8b66.1774125871.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1774125871.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH 3/3] repack-promisor: preserve content of promisor files after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-21T21:29:01Z","receivedAt":"2026-03-21T21:29:05Z","isPatch":true,"body":"When a repack involving promisor packfiles happens, the new \".promisor\"\nfile is created empty, losing all the debug info that might be present\ninside the \".promisor\" files before the repack.\n\nUse the \"copy_all_promisor_files()\" function created previously to\npreserve the contents of all \".promisor\" files inside the first\n\".promisor\" file created by the repack.\n\nAlso, update the documentation accordingly.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n Documentation/git-repack.adoc |  4 ++--\n repack-promisor.c             | 23 +++++++++++++++--------\n 2 files changed, 17 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-repack.adoc b/Documentation/git-repack.adoc\nindex 673ce91083..33d3c8afbd 100644\n--- a/Documentation/git-repack.adoc\n+++ b/Documentation/git-repack.adoc\n@@ -45,8 +45,8 @@ other objects in that pack they already have locally.\n +\n Promisor packfiles are repacked separately: if there are packfiles that\n have an associated \".promisor\" file, these packfiles will be repacked\n-into another separate pack, and an empty \".promisor\" file corresponding\n-to the new separate pack will be written.\n+into another separate pack, and a \".promisor\" file corresponding to the\n+new separate pack will be written (with arbitrary contents).\n \n -A::\n \tSame as `-a`, unless `-d` is used.  Then any unreachable\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 90318ce150..6670728669 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -40,6 +40,7 @@ static void finish_repacking_promisor_objects(struct repository *repo,\n \t\t\t\t\t      const char *packtmp)\n {\n \tstruct strbuf line = STRBUF_INIT;\n+\tint is_first_promisor = 1;\n \tFILE *out;\n \n \tclose(cmd->in);\n@@ -55,19 +56,25 @@ static void finish_repacking_promisor_objects(struct repository *repo,\n \n \t\t/*\n \t\t * pack-objects creates the .pack and .idx files, but not the\n-\t\t * .promisor file. Create the .promisor file, which is empty.\n-\t\t *\n-\t\t * NEEDSWORK: fetch-pack sometimes generates non-empty\n-\t\t * .promisor files containing the ref names and associated\n-\t\t * hashes at the point of generation of the corresponding\n-\t\t * packfile, but this would not preserve their contents. Maybe\n-\t\t * concatenate the contents of all .promisor files instead of\n-\t\t * just creating a new empty file.\n+\t\t * .promisor file. Create the .promisor file.\n \t\t */\n \t\tpromisor_name = mkpathdup(\"%s-%s.promisor\", packtmp,\n \t\t\t\t\t  line.buf);\n \t\twrite_promisor_file(promisor_name, NULL, 0);\n \n+\t\t/*\n+\t\t * Fetch-pack sometimes generates non-empty .promisor files\n+\t\t * containing the ref names and associated hashes at the point of\n+\t\t * generation of the corresponding packfile. These pieces of info\n+\t\t * are only used for debugging reasons. In order to preserve\n+\t\t * these, let's copy the contents of all .promisor files in the\n+\t\t * first promisor file created.\n+\t\t */\n+\t\tif (is_first_promisor) {\n+\t\t\tcopy_all_promisor_files(repo, promisor_name);\n+\t\t\tis_first_promisor = 0;\n+\t\t}\n+\n \t\titem->util = generated_pack_populate(item->string, packtmp);\n \n \t\tfree(promisor_name);\n-- \n2.43.0\n\n"},{"id":"539628","messageId":"CAPig+cQSsMfvHJnwuXGQ1Je8ekz=Rqbaibn-3shbya5y-5xTKg@mail.gmail.com","threadId":"65326","inReplyTo":"3c0702f81b3e4a57cd38284ab90f8277da3fcd40.1774125871.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH 2/3] pack-write: add helper to fill promisor file after repack","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2026-03-22T02:04:01Z","receivedAt":"2026-03-22T02:04:18Z","isPatch":true,"body":"On Sat, Mar 21, 2026 at 5:29 PM LorenzoPegorari\n<lorenzo.pegorari2002@gmail.com> wrote:\n> Create a `copy_all_promisor_files()` helper function used to copy the\n> contents of all \".promisor\" files in a `repository` inside another\n> \".promisor\" file.\n>\n> This function can be used to preserve the contents of all \".promisor\"\n> files inside a new \".promisor\" file, for example when a repack happens.\n>\n> This function is written in such a way so that it will read all the\n> \".promisor\" files inside the given `repository` line by line, and copy\n> only the lines that are not already present in the destination file. This\n> is done to avoid copying the same lines multiple times that may come from\n> multiple (redundant) packfiles. A better way to achieve this might be (is\n> definitely) possible.\n>\n> Signed-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n> ---\n> diff --git a/pack-write.c b/pack-write.c\n> @@ -621,3 +621,65 @@ void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_\n> +void copy_all_promisor_files(struct repository *repo, const char *promisor_name)\n> +{\n> +       struct strbuf promisor_source_name = STRBUF_INIT;\n> +       struct strbuf read_source = STRBUF_INIT, read_dest = STRBUF_INIT;\n> +       struct strbuf write_dest = STRBUF_INIT;\n\nThese strbufs don't seem to be released, thus are leaked.\n\n> +       int err;\n> +\n> +       FILE *dest = xfopen(promisor_name, \"r+\");\n> +\n> +       struct packed_git *p;\n\nStyle nit: Place all the variable declarations together (without blank\nlines), followed by a blank line.\n\n> +       repo_for_each_pack(repo, p) {\n> +               if (!p->pack_promisor)\n> +                       continue;\n> +\n> +               strbuf_reset(&promisor_source_name);\n> +               strbuf_addstr(&promisor_source_name, p->pack_name);\n> +               strbuf_strip_suffix(&promisor_source_name, \".pack\");\n> +               strbuf_addstr(&promisor_source_name, \".promisor\");\n> +               FILE *source = xfopen(promisor_source_name.buf, \"r\");\n\nThis project still frowns upon variable declaration after code. You\nwill want to declare `FILE *source;` at the top of this loop body and\nthen assign `source = xfopen(...)` here.\n\n> +               /*\n> +                * For each line of the promisor source file, check if it already\n> +                * is in the promisor dest file. If not, add it to write_dest, so\n> +                * that it will be written in the dest file.\n> +                */\n> +               while (strbuf_getline(&read_source, source) != EOF) {\n> +                       if (fseek(dest, 0L, SEEK_SET))\n> +                               die_errno(_(\"fseek failed\"));\n> +                       int is_source_in_dest = 0;\n\nDitto regarding variable declaration following code.\n\n> +                       while (strbuf_getline(&read_dest, dest) != EOF) {\n> +                               if (!strbuf_cmp(&read_source, &read_dest)) {\n> +                                       is_source_in_dest = 1;\n> +                                       break;\n> +                               }\n> +                       }\n> +                       if (!is_source_in_dest) {\n> +                               strbuf_addbuf(&write_dest, &read_source);\n> +                               strbuf_addstr(&write_dest, \"\\n\");\n> +                       }\n\nThe commit message talks about this, and it is indeed very ugly that\nthis re-reads `dest` from the beginning for *every* `source` line. Is\nthere a reason you can't simply read `dest` into a `strset` (see Git's\n`strmap.h`) in its entirety before entering the repo_for_each_pack()\nloop and then merely check the strset for existence using\nstrset_add()?\n\n> +               }\n> +\n> +               if (write_dest.len) {\n> +                       strbuf_strip_suffix(&write_dest, \"\\n\");\n> +                       if (fseek(dest, 0L, SEEK_END))\n> +                               die_errno(_(\"fseek failed\"));\n> +                       fprintf(dest, \"%s\\n\", write_dest.buf);\n> +                       fflush(dest);\n> +                       strbuf_reset(&write_dest);\n> +               }\n> +\n> +               err = ferror(source);\n> +               err |= fclose(source);\n> +               if (err)\n> +                       die(_(\"could not read '%s' promisor file\"), promisor_source_name.buf);\n> +       }\n> +\n> +       err = ferror(dest);\n> +       err |= fclose(dest);\n> +       if (err)\n> +               die(_(\"could not write '%s' promisor file\"), promisor_name);\n> +}\n"},{"id":"539662","messageId":"acA5-pIc9zVbqY1e@lorenzo-VM","threadId":"65326","inReplyTo":"CAPig+cQSsMfvHJnwuXGQ1Je8ekz=Rqbaibn-3shbya5y-5xTKg@mail.gmail.com","subject":"Re: [GSoC PATCH 2/3] pack-write: add helper to fill promisor file after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-22T18:50:34Z","receivedAt":"2026-03-22T18:50:38Z","isPatch":true,"body":"On Sat, Mar 21, 2026 at 10:04:01PM -0400, Eric Sunshine wrote:\n> On Sat, Mar 21, 2026 at 5:29 PM LorenzoPegorari\n> <lorenzo.pegorari2002@gmail.com> wrote:\n> > Create a `copy_all_promisor_files()` helper function used to copy the\n> > contents of all \".promisor\" files in a `repository` inside another\n> > \".promisor\" file.\n> >\n> > This function can be used to preserve the contents of all \".promisor\"\n> > files inside a new \".promisor\" file, for example when a repack happens.\n> >\n> > This function is written in such a way so that it will read all the\n> > \".promisor\" files inside the given `repository` line by line, and copy\n> > only the lines that are not already present in the destination file. This\n> > is done to avoid copying the same lines multiple times that may come from\n> > multiple (redundant) packfiles. A better way to achieve this might be (is\n> > definitely) possible.\n> >\n> > Signed-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n> > ---\n> > diff --git a/pack-write.c b/pack-write.c\n> > @@ -621,3 +621,65 @@ void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_\n> > +void copy_all_promisor_files(struct repository *repo, const char *promisor_name)\n> > +{\n> > +       struct strbuf promisor_source_name = STRBUF_INIT;\n> > +       struct strbuf read_source = STRBUF_INIT, read_dest = STRBUF_INIT;\n> > +       struct strbuf write_dest = STRBUF_INIT;\n> \n> These strbufs don't seem to be released, thus are leaked.\n\nOf course... trivial mistake. Will fix it in v2.\n\n> > +       int err;\n> > +\n> > +       FILE *dest = xfopen(promisor_name, \"r+\");\n> > +\n> > +       struct packed_git *p;\n> \n> Style nit: Place all the variable declarations together (without blank\n> lines), followed by a blank line.\n\nAck.\n\n> > +       repo_for_each_pack(repo, p) {\n> > +               if (!p->pack_promisor)\n> > +                       continue;\n> > +\n> > +               strbuf_reset(&promisor_source_name);\n> > +               strbuf_addstr(&promisor_source_name, p->pack_name);\n> > +               strbuf_strip_suffix(&promisor_source_name, \".pack\");\n> > +               strbuf_addstr(&promisor_source_name, \".promisor\");\n> > +               FILE *source = xfopen(promisor_source_name.buf, \"r\");\n> \n> This project still frowns upon variable declaration after code. You\n> will want to declare `FILE *source;` at the top of this loop body and\n> then assign `source = xfopen(...)` here.\n\nAck.\n\n> > +               /*\n> > +                * For each line of the promisor source file, check if it already\n> > +                * is in the promisor dest file. If not, add it to write_dest, so\n> > +                * that it will be written in the dest file.\n> > +                */\n> > +               while (strbuf_getline(&read_source, source) != EOF) {\n> > +                       if (fseek(dest, 0L, SEEK_SET))\n> > +                               die_errno(_(\"fseek failed\"));\n> > +                       int is_source_in_dest = 0;\n> \n> Ditto regarding variable declaration following code.\n\nAck.\n\n> > +                       while (strbuf_getline(&read_dest, dest) != EOF) {\n> > +                               if (!strbuf_cmp(&read_source, &read_dest)) {\n> > +                                       is_source_in_dest = 1;\n> > +                                       break;\n> > +                               }\n> > +                       }\n> > +                       if (!is_source_in_dest) {\n> > +                               strbuf_addbuf(&write_dest, &read_source);\n> > +                               strbuf_addstr(&write_dest, \"\\n\");\n> > +                       }\n> \n> The commit message talks about this, and it is indeed very ugly that\n> this re-reads `dest` from the beginning for *every* `source` line. Is\n> there a reason you can't simply read `dest` into a `strset` (see Git's\n> `strmap.h`) in its entirety before entering the repo_for_each_pack()\n> loop and then merely check the strset for existence using\n> strset_add()?\n\nNo reason at all, except for me to knowing about `strset`! Thanks for\nsuggesting it to me. Will use it in v2.\n\n> > +               }\n> > +\n> > +               if (write_dest.len) {\n> > +                       strbuf_strip_suffix(&write_dest, \"\\n\");\n> > +                       if (fseek(dest, 0L, SEEK_END))\n> > +                               die_errno(_(\"fseek failed\"));\n> > +                       fprintf(dest, \"%s\\n\", write_dest.buf);\n> > +                       fflush(dest);\n> > +                       strbuf_reset(&write_dest);\n> > +               }\n> > +\n> > +               err = ferror(source);\n> > +               err |= fclose(source);\n> > +               if (err)\n> > +                       die(_(\"could not read '%s' promisor file\"), promisor_source_name.buf);\n> > +       }\n> > +\n> > +       err = ferror(dest);\n> > +       err |= fclose(dest);\n> > +       if (err)\n> > +               die(_(\"could not write '%s' promisor file\"), promisor_name);\n> > +}\n"},{"id":"539663","messageId":"cover.1774205661.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1774125871.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v2 0/4] preserve promisor files content after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-22T19:16:03Z","receivedAt":"2026-03-22T19:16:08Z","isPatch":true,"body":"The goal of this patch is to solve the NEEDSWORK comment added by\n5374a290 (fetch-pack: write fetched refs to .promisor, 14/10/2019). This\nis done by adding a helper function that takes the content of all\n.promisor files in the `repository`, and copies it inside the first\n.promisor file created by the repack.\n\nAlso, I added a comment explaining what is the purpose of the content of\nthe .promisor files, since this wasn't explained anywhere (I found\ninformation regarding this only in the message of the previously cited\ncommit).\n\nFinally, I added a test to \"t7700-repack.sh\" that checks if the content\nof .promisor files are correctly copied into the first .promisor file\ncreated by a repack.\n\n\nV2 DIFF:\n * changed how the `copy_all_promisor_files()` function works, so that it\n   reads `dest` into a `strset` in its entirety before entering the\n   `repo_for_each_pack()` loop, and then checks the `strset` for\n   existence using `strset_add()` (as suggested by Eric Sunshine)\n * correctly release `strbuf`s\n * added test\n\n\nLorenzoPegorari (4):\n  pack-write: add explanation to promisor file content\n  pack-write: add helper to fill promisor file after repack\n  repack-promisor: preserve content of promisor files after repack\n  t7700: test for promisor file content after repack\n\n Documentation/git-repack.adoc |  4 +-\n pack-write.c                  | 70 +++++++++++++++++++++++++++++++++++\n pack.h                        |  1 +\n repack-promisor.c             | 23 ++++++++----\n t/t7700-repack.sh             | 12 ++++++\n 5 files changed, 100 insertions(+), 10 deletions(-)\n\nRange-diff against v1:\n1:  9bba49563e = 1:  fec0c24897 pack-write: add explanation to promisor file content\n2:  3c0702f81b < -:  ---------- pack-write: add helper to fill promisor file after repack\n-:  ---------- > 2:  0bb031e744 pack-write: add helper to fill promisor file after repack\n3:  6967066fe3 = 3:  3dab969a39 repack-promisor: preserve content of promisor files after repack\n-:  ---------- > 4:  cb642d8225 t7700: test for promisor file content after repack\n-- \n2.43.0\n"},{"id":"539664","messageId":"fec0c24897092d19a718563ca4ef6e509ab104e6.1774205661.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1774205661.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v2 1/4] pack-write: add explanation to promisor file content","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-22T19:16:23Z","receivedAt":"2026-03-22T19:16:27Z","isPatch":true,"body":"In the entire codebase there is no explanation as to why the \".promisor\"\nfiles may contain the ref names (and their associated hashes) that were\nfetched at the time the corresponding packfile was downloaded.\n\nAdd comment explaining that these pieces of information are used only for\ndebugging reasons, and how they can be used while debugging.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n pack-write.c | 9 +++++++++\n 1 file changed, 9 insertions(+)\n\ndiff --git a/pack-write.c b/pack-write.c\nindex 83eaf88541..6a2023327e 100644\n--- a/pack-write.c\n+++ b/pack-write.c\n@@ -603,6 +603,15 @@ void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_\n \tint i, err;\n \tFILE *output = xfopen(promisor_name, \"w\");\n \n+\t/*\n+\t* Write in the .promisor file the ref names and associated hashes,\n+\t* obtained by fetch-pack, at the point of generation of the\n+\t* corresponding packfile. These pieces of info are only used to make\n+\t* it easier to debug issues with partial clones, as we can identify\n+\t* what refs (and their associated hashes) were fetched at the time\n+\t* the packfile was downloaded, and if necessary, compare those hashes\n+\t* against what the promisor remote reports now.\n+\t*/\n \tfor (i = 0; i < nr_sought; i++)\n \t\tfprintf(output, \"%s %s\\n\", oid_to_hex(&sought[i]->old_oid),\n \t\t\tsought[i]->name);\n-- \n2.43.0\n\n"},{"id":"539665","messageId":"0bb031e7443bb53abbbb0afaa347285d6d8cf7b8.1774205661.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1774205661.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v2 2/4] pack-write: add helper to fill promisor file after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-22T19:18:05Z","receivedAt":"2026-03-22T19:18:10Z","isPatch":true,"body":"Create a `copy_all_promisor_files()` helper function used to copy the\ncontents of all \".promisor\" files in a `repository` inside another\n\".promisor\" file.\n\nThis function can be used to preserve the contents of all \".promisor\"\nfiles inside a new \".promisor\" file, for example when a repack happens.\n\nThis function is written in such a way so that it will read all the\n\".promisor\" files inside the given `repository` line by line, and copy\nonly the lines that are not already present in the destination file. This\nis done to avoid copying the same lines multiple times that may come from\nmultiple (redundant) packfiles. There might be another better/cleaner way\nto achieve this.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n pack-write.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n pack.h       |  1 +\n 2 files changed, 62 insertions(+)\n\ndiff --git a/pack-write.c b/pack-write.c\nindex 6a2023327e..583e40b423 100644\n--- a/pack-write.c\n+++ b/pack-write.c\n@@ -13,6 +13,7 @@\n #include \"path.h\"\n #include \"repository.h\"\n #include \"strbuf.h\"\n+#include \"strmap.h\"\n \n void reset_pack_idx_option(struct pack_idx_option *opts)\n {\n@@ -621,3 +622,63 @@ void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_\n \tif (err)\n \t\tdie(_(\"could not write '%s' promisor file\"), promisor_name);\n }\n+\n+void copy_all_promisor_files(struct repository *repo, const char *promisor_name)\n+{\n+\tstruct strset dest_content = STRSET_INIT;\n+\tstruct strbuf read_line = STRBUF_INIT;\n+\tstruct strbuf promisor_source_name = STRBUF_INIT;\n+\tstruct strbuf write_dest = STRBUF_INIT;\n+\tFILE *dest, *source;\n+\tstruct packed_git *p;\n+\tint err;\n+\n+\tdest = xfopen(promisor_name, \"r+\");\n+\twhile (strbuf_getline(&read_line, dest) != EOF)\n+\t\tstrset_add(&dest_content, read_line.buf);\n+\n+\trepo_for_each_pack(repo, p) {\n+\t\tif (!p->pack_promisor)\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&promisor_source_name);\n+\t\tstrbuf_addstr(&promisor_source_name, p->pack_name);\n+\t\tstrbuf_strip_suffix(&promisor_source_name, \".pack\");\n+\t\tstrbuf_addstr(&promisor_source_name, \".promisor\");\n+\t\tsource = xfopen(promisor_source_name.buf, \"r\");\n+\n+\t\t/*\n+\t\t * For each line of the promisor source file, check if it already\n+\t\t * is in the promisor dest file. If not, add it to write_dest, so\n+\t\t * that it will be written in the dest file.\n+\t\t */\n+\t\twhile (strbuf_getline(&read_line, source) != EOF) {\n+\t\t\tif (strset_add(&dest_content, read_line.buf)) {\n+\t\t\t\tstrbuf_addbuf(&write_dest, &read_line);\n+\t\t\t\tstrbuf_addstr(&write_dest, \"\\n\");\n+\t\t\t}\n+\t\t}\n+\n+\t\terr = ferror(source);\n+\t\terr |= fclose(source);\n+\t\tif (err)\n+\t\t\tdie(_(\"could not read '%s' promisor file\"), promisor_source_name.buf);\n+\t}\n+\n+\tif (write_dest.len) {\n+\t\tstrbuf_strip_suffix(&write_dest, \"\\n\");\n+\t\tif (fseek(dest, 0L, SEEK_END))\n+\t\t\tdie_errno(_(\"fseek failed\"));\n+\t\tfprintf(dest, \"%s\\n\", write_dest.buf);\n+\t}\n+\n+\terr = ferror(dest);\n+\terr |= fclose(dest);\n+\tif (err)\n+\t\tdie(_(\"could not write '%s' promisor file\"), promisor_name);\n+\n+\tstrbuf_release(&read_line);\n+\tstrbuf_release(&promisor_source_name);\n+\tstrbuf_release(&write_dest);\n+\tstrset_clear(&dest_content);\n+}\ndiff --git a/pack.h b/pack.h\nindex ec76472e49..509e90edba 100644\n--- a/pack.h\n+++ b/pack.h\n@@ -105,6 +105,7 @@ char *index_pack_lockfile(struct repository *r, int fd, int *is_well_formed);\n struct ref;\n \n void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_sought);\n+void copy_all_promisor_files(struct repository *repo, const char *promisor_name);\n \n char *write_rev_file(struct repository *repo,\n \t\t     const char *rev_name,\n-- \n2.43.0\n\n"},{"id":"539666","messageId":"3dab969a3942532f49f6f9cdcddb5fb2be11e232.1774205661.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1774205661.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v2 3/4] repack-promisor: preserve content of promisor files after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-22T19:18:34Z","receivedAt":"2026-03-22T19:18:39Z","isPatch":true,"body":"When a repack involving promisor packfiles happens, the new \".promisor\"\nfile is created empty, losing all the debug info that might be present\ninside the \".promisor\" files before the repack.\n\nUse the \"copy_all_promisor_files()\" function created previously to\npreserve the contents of all \".promisor\" files inside the first\n\".promisor\" file created by the repack.\n\nAlso, update the documentation accordingly.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n Documentation/git-repack.adoc |  4 ++--\n repack-promisor.c             | 23 +++++++++++++++--------\n 2 files changed, 17 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-repack.adoc b/Documentation/git-repack.adoc\nindex 673ce91083..33d3c8afbd 100644\n--- a/Documentation/git-repack.adoc\n+++ b/Documentation/git-repack.adoc\n@@ -45,8 +45,8 @@ other objects in that pack they already have locally.\n +\n Promisor packfiles are repacked separately: if there are packfiles that\n have an associated \".promisor\" file, these packfiles will be repacked\n-into another separate pack, and an empty \".promisor\" file corresponding\n-to the new separate pack will be written.\n+into another separate pack, and a \".promisor\" file corresponding to the\n+new separate pack will be written (with arbitrary contents).\n \n -A::\n \tSame as `-a`, unless `-d` is used.  Then any unreachable\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 90318ce150..6670728669 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -40,6 +40,7 @@ static void finish_repacking_promisor_objects(struct repository *repo,\n \t\t\t\t\t      const char *packtmp)\n {\n \tstruct strbuf line = STRBUF_INIT;\n+\tint is_first_promisor = 1;\n \tFILE *out;\n \n \tclose(cmd->in);\n@@ -55,19 +56,25 @@ static void finish_repacking_promisor_objects(struct repository *repo,\n \n \t\t/*\n \t\t * pack-objects creates the .pack and .idx files, but not the\n-\t\t * .promisor file. Create the .promisor file, which is empty.\n-\t\t *\n-\t\t * NEEDSWORK: fetch-pack sometimes generates non-empty\n-\t\t * .promisor files containing the ref names and associated\n-\t\t * hashes at the point of generation of the corresponding\n-\t\t * packfile, but this would not preserve their contents. Maybe\n-\t\t * concatenate the contents of all .promisor files instead of\n-\t\t * just creating a new empty file.\n+\t\t * .promisor file. Create the .promisor file.\n \t\t */\n \t\tpromisor_name = mkpathdup(\"%s-%s.promisor\", packtmp,\n \t\t\t\t\t  line.buf);\n \t\twrite_promisor_file(promisor_name, NULL, 0);\n \n+\t\t/*\n+\t\t * Fetch-pack sometimes generates non-empty .promisor files\n+\t\t * containing the ref names and associated hashes at the point of\n+\t\t * generation of the corresponding packfile. These pieces of info\n+\t\t * are only used for debugging reasons. In order to preserve\n+\t\t * these, let's copy the contents of all .promisor files in the\n+\t\t * first promisor file created.\n+\t\t */\n+\t\tif (is_first_promisor) {\n+\t\t\tcopy_all_promisor_files(repo, promisor_name);\n+\t\t\tis_first_promisor = 0;\n+\t\t}\n+\n \t\titem->util = generated_pack_populate(item->string, packtmp);\n \n \t\tfree(promisor_name);\n-- \n2.43.0\n\n"},{"id":"539667","messageId":"cb642d82258c35ea85e4c18459d09473f23de373.1774205661.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1774205661.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v2 4/4] t7700: test for promisor file content after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-22T19:18:54Z","receivedAt":"2026-03-22T19:18:58Z","isPatch":true,"body":"Add test that checks if the content of all \".promisor\" files are copied\ninside the first \".promisor\" file created by a repack.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n t/t7700-repack.sh | 12 ++++++++++++\n 1 file changed, 12 insertions(+)\n\ndiff --git a/t/t7700-repack.sh b/t/t7700-repack.sh\nindex 63ef63fc50..10187d5954 100755\n--- a/t/t7700-repack.sh\n+++ b/t/t7700-repack.sh\n@@ -904,4 +904,16 @@ test_expect_success 'pending objects are repacked appropriately' '\n \t)\n '\n \n+test_expect_success 'check .promisor file content after repack' '\n+\tgit init prom_test1 &&\n+\ttest_commit -C prom_test1 temp &&\n+\tgit clone prom_test1 prom_test2 --filter=blob:none --no-local &&\n+\n+\tcp $(ls prom_test2/.git/objects/pack/pack-*.promisor) prom_content_before &&\n+\tgit -C prom_test2 repack -a -d &&\n+\tcp $(ls prom_test2/.git/objects/pack/pack-*.promisor) prom_content_after &&\n+\n+\ttest_cmp prom_content_before prom_content_after\n+'\n+\n test_done\n-- \n2.43.0\n\n"},{"id":"539779","messageId":"CAPig+cR7-3rdkHvGbzk8O7P=83pxBTvqXTUgMxYpf+OK9jNCgg@mail.gmail.com","threadId":"65326","inReplyTo":"0bb031e7443bb53abbbb0afaa347285d6d8cf7b8.1774205661.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v2 2/4] pack-write: add helper to fill promisor file after repack","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2026-03-23T20:27:44Z","receivedAt":"2026-03-23T20:27:56Z","isPatch":true,"body":"On Sun, Mar 22, 2026 at 3:18 PM LorenzoPegorari\n<lorenzo.pegorari2002@gmail.com> wrote:\n> Create a `copy_all_promisor_files()` helper function used to copy the\n> contents of all \".promisor\" files in a `repository` inside another\n> \".promisor\" file.\n>\n> This function can be used to preserve the contents of all \".promisor\"\n> files inside a new \".promisor\" file, for example when a repack happens.\n>\n> This function is written in such a way so that it will read all the\n> \".promisor\" files inside the given `repository` line by line, and copy\n> only the lines that are not already present in the destination file. This\n> is done to avoid copying the same lines multiple times that may come from\n> multiple (redundant) packfiles. There might be another better/cleaner way\n> to achieve this.\n>\n> Signed-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n> ---\n\nThanks, I think this version addresses all my review comments[*] and\nlooks much better overall. Use of `strset` makes a big difference over\nthe previous attempt. A couple minor comments below...\n\n[*]: https://lore.kernel.org/git/CAPig+cQSsMfvHJnwuXGQ1Je8ekz=Rqbaibn-3shbya5y-5xTKg@mail.gmail.com/\n\n> diff --git a/pack-write.c b/pack-write.c\n> @@ -621,3 +622,63 @@ void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_\n> +void copy_all_promisor_files(struct repository *repo, const char *promisor_name)\n> +{\n> +       struct strset dest_content = STRSET_INIT;\n> +       struct strbuf read_line = STRBUF_INIT;\n> +       struct strbuf promisor_source_name = STRBUF_INIT;\n> +       struct strbuf write_dest = STRBUF_INIT;\n> +       FILE *dest, *source;\n> +       struct packed_git *p;\n> +       int err;\n\nNit: I probably would have declared `FILE *dest` within the scope of\nthe repo_for_each_pack() loop as suggested in the review, but it's not\nworth a reroll.\n\n> +       dest = xfopen(promisor_name, \"r+\");\n> +       while (strbuf_getline(&read_line, dest) != EOF)\n> +               strset_add(&dest_content, read_line.buf);\n> +\n> +       repo_for_each_pack(repo, p) {\n> +               if (!p->pack_promisor)\n> +                       continue;\n> +\n> +               strbuf_reset(&promisor_source_name);\n> +               strbuf_addstr(&promisor_source_name, p->pack_name);\n> +               strbuf_strip_suffix(&promisor_source_name, \".pack\");\n> +               strbuf_addstr(&promisor_source_name, \".promisor\");\n> +               source = xfopen(promisor_source_name.buf, \"r\");\n> +\n> +               /*\n> +                * For each line of the promisor source file, check if it already\n> +                * is in the promisor dest file. If not, add it to write_dest, so\n> +                * that it will be written in the dest file.\n> +                */\n> +               while (strbuf_getline(&read_line, source) != EOF) {\n> +                       if (strset_add(&dest_content, read_line.buf)) {\n> +                               strbuf_addbuf(&write_dest, &read_line);\n> +                               strbuf_addstr(&write_dest, \"\\n\");\n\nNot worth a reroll, but this could also be:\n\n    strbuf_addch(&write_dest, '\\n');\n\n> +                       }\n> +               }\n> +\n> +               err = ferror(source);\n> +               err |= fclose(source);\n> +               if (err)\n> +                       die(_(\"could not read '%s' promisor file\"), promisor_source_name.buf);\n> +       }\n> +\n> +       if (write_dest.len) {\n> +               strbuf_strip_suffix(&write_dest, \"\\n\");\n> +               if (fseek(dest, 0L, SEEK_END))\n> +                       die_errno(_(\"fseek failed\"));\n> +               fprintf(dest, \"%s\\n\", write_dest.buf);\n> +       }\n\nCan you explain why you strip \"\\n\" and then re-add it via fprintf()?\nThe reason is not immediately obvious.\n\n> +       err = ferror(dest);\n> +       err |= fclose(dest);\n> +       if (err)\n> +               die(_(\"could not write '%s' promisor file\"), promisor_name);\n> +\n> +       strbuf_release(&read_line);\n> +       strbuf_release(&promisor_source_name);\n> +       strbuf_release(&write_dest);\n> +       strset_clear(&dest_content);\n> +}\n\nEverything appears to be released. Good.\n"},{"id":"539781","messageId":"xmqqmrzy45m4.fsf@gitster.g","threadId":"65326","inReplyTo":"fec0c24897092d19a718563ca4ef6e509ab104e6.1774205661.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v2 1/4] pack-write: add explanation to promisor file content","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-23T21:07:31Z","receivedAt":"2026-03-23T21:07:34Z","isPatch":true,"body":"LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> In the entire codebase there is no explanation as to why the \".promisor\"\n> files may contain the ref names (and their associated hashes) that were\n> fetched at the time the corresponding packfile was downloaded.\n>\n> Add comment explaining that these pieces of information are used only for\n> debugging reasons, and how they can be used while debugging.\n>\n> Signed-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n\nA natural question any reader of the above (and below) would be\nasking is: Who told you that these are only to aid debugging?\n\nPlease refer to the commit that brought in the reasoning behind the\ncomment to make it more convincing.  \n\nSomething like this replacing the second paragraph,\n\n    As explained in the log message of the commit 5374a290\n    (fetch-pack: write fetched refs to .promisor, 2019-10-14), where\n    this loop originally came from, these ref values are not\n    actually used for anything in the production, but are solely\n    there to help debugging.  Explain it in a new comment.\n\nperhaps?\n\n> +\t/*\n> +\t* Write in the .promisor file the ref names and associated hashes,\n> +\t* obtained by fetch-pack, at the point of generation of the\n> +\t* corresponding packfile. These pieces of info are only used to make\n> +\t* it easier to debug issues with partial clones, as we can identify\n> +\t* what refs (and their associated hashes) were fetched at the time\n> +\t* the packfile was downloaded, and if necessary, compare those hashes\n> +\t* against what the promisor remote reports now.\n> +\t*/\n\nI do not want to sound too pedantic, but we align '*' asterisks in\nour multi-line comments, assuming tabwidth=8 and monospace:\n\n\t/*\n\t * Write in the .promisor ...\n\t...\n\t * against what the promisor remote reports now.\n\t */\n\nYour second and subsequent lines lack a single whitespace after the\nleading tab used for indent.\n\n>  \tfor (i = 0; i < nr_sought; i++)\n>  \t\tfprintf(output, \"%s %s\\n\", oid_to_hex(&sought[i]->old_oid),\n>  \t\t\tsought[i]->name);\n"},{"id":"539782","messageId":"xmqqfr5q44jx.fsf@gitster.g","threadId":"65326","inReplyTo":"0bb031e7443bb53abbbb0afaa347285d6d8cf7b8.1774205661.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v2 2/4] pack-write: add helper to fill promisor file after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-23T21:30:26Z","receivedAt":"2026-03-23T21:30:29Z","isPatch":true,"body":"LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> Create a `copy_all_promisor_files()` helper function used to copy the\n> contents of all \".promisor\" files in a `repository` inside another\n> \".promisor\" file.\n>\n> This function can be used to preserve the contents of all \".promisor\"\n> files inside a new \".promisor\" file, for example when a repack happens.\n>\n> This function is written in such a way so that it will read all the\n> \".promisor\" files inside the given `repository` line by line, and copy\n> only the lines that are not already present in the destination file. This\n> is done to avoid copying the same lines multiple times that may come from\n> multiple (redundant) packfiles. There might be another better/cleaner way\n> to achieve this.\n\nIn the previous step, we extablished that these \"back then their ref\nX used to point at object Y\" records are there so that we can\nidentify which refs were fetched at the time the packfile was\ndownloaded to help debugging.  When repacking, losing these records\ncertainly would lose information.\n\nBut would concatenating all into a single file help preserve the\nuseful information?  Don't we need do better than that?\n\nA NEEDSWORK comment, as was discussed in another thread or two in\nthe recent past, is not necessarily a well thought out fully\nfinished specification of an additional piece of work.  \"We know\nthis has a problem, we may need to do something about it, like\nconcatenating to save the contents, perhaps?  We do not know the\nanswer, and we do not bother thinking it through right at this\nmoment.  It is left to the future developers to figure it out\" is\nwhat a NEEDSWORK comment is about.\n\nYour first response to such a comment may be \"yeah, I agree that it\nis bad to lose information we added to help debugging\", but the\nsecond one should be to wonder if the \"like concatenating...\" is the\nbest approach going forward.\n\nIn other words, we should take a NEEDSWORK comment as a mere\nstarting point, and what NEEDS your work begins at thinking what\nneeds to be done about the problem raised there.\n\nBy mixing them up all into a single list, you no longer can tell\nwhen their ref X was observed to be pointing at object Y anymore.\nYou may have two packs originally, with a record for \"ref X pointing\nat object Y\" in each of them, but by deduping them, you lose the\ninformation that you cloned at one time, and made an additional\nfetch on another day, and the fact the ref X was pointing at the\nsame value at both times.  I am not sure if it is a good\nimplementation if the objective of this topic is to preserve\ninformation that is useful for debugging.\n\nI wonder if it helps to append to each line the file timestamp of\nthe .promisor file we took the record originally?  For the sake of\ncompleteness, we could consider adding the filename as well, but we\ncan quickly dismiss it as not so useful ;-)\n\nIf repacking already repacked promisor packfile, the records would\nalready contain such a timestamp at the end, so the code to copy\nexisting records must be prepared to see if the records are the\n<ref, oid> tuple, or <ref, oid, timestamp> tuple, and act\naccordingly.\n\nI am *not* saying that without such a \"preserve timestamp\" column in\nthe record, copying existing records to a new .promisor file is\nuseless.  But we do not see any explanation why the author thinks\nthat it is sufficient to copy existing records while silently\ndeduping.  We can implement only one choice backed by series of\ndecisions like \"timestamp might help\" and \"original filenames would\nprobably not help\", and the design should describe what was\nconsidered and rejected (as opposed to \"we didn't think things\nthrough---we just did what the original NEEDSWORK comment suggested\ndoing\").\n\nThanks.\n"},{"id":"539783","messageId":"xmqqa4vy43q2.fsf@gitster.g","threadId":"65326","inReplyTo":"3dab969a3942532f49f6f9cdcddb5fb2be11e232.1774205661.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v2 3/4] repack-promisor: preserve content of promisor files after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-23T21:48:21Z","receivedAt":"2026-03-23T21:48:24Z","isPatch":true,"body":"LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> @@ -40,6 +40,7 @@ static void finish_repacking_promisor_objects(struct repository *repo,\n>  \t\t\t\t\t      const char *packtmp)\n>  {\n>  \tstruct strbuf line = STRBUF_INIT;\n> +\tint is_first_promisor = 1;\n>  \tFILE *out;\n> ... \n> +\t\t/*\n> +\t\t * Fetch-pack sometimes generates non-empty .promisor files\n> +\t\t * containing the ref names and associated hashes at the point of\n> +\t\t * generation of the corresponding packfile. These pieces of info\n> +\t\t * are only used for debugging reasons. In order to preserve\n> +\t\t * these, let's copy the contents of all .promisor files in the\n> +\t\t * first promisor file created.\n> +\t\t */\n> +\t\tif (is_first_promisor) {\n> +\t\t\tcopy_all_promisor_files(repo, promisor_name);\n> +\t\t\tis_first_promisor = 0;\n> +\t\t}\n> +\n\nHere the underlying assumption seems to be that whichever one of the\ntwo potential callers of this function, repack_promisor_objects()\nand pack_geometry_repack_promisors(), would handle all the existing\npacks with corresponding .promisor file so it is safe to coalesce\nall the debugging comments from all the existing .promisor files\ninto one?\n\nIs it really true, though?  Especially with geometry repacking\nenabled, wouldn't a regular repack coalesce only the smallish ones\ninto a single pack while leaving an already largeish ones intact, or\nsomething?\n\nThanks.\n"},{"id":"540017","messageId":"acRUjAG7QOD5kVMI@lorenzo-VM","threadId":"65326","inReplyTo":"xmqqmrzy45m4.fsf@gitster.g","subject":"Re: [GSoC PATCH v2 1/4] pack-write: add explanation to promisor file content","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-25T21:33:00Z","receivedAt":"2026-03-25T21:33:06Z","isPatch":true,"body":"On Mon, Mar 23, 2026 at 02:07:31PM -0700, Junio C Hamano wrote:\n> LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n> \n> > In the entire codebase there is no explanation as to why the \".promisor\"\n> > files may contain the ref names (and their associated hashes) that were\n> > fetched at the time the corresponding packfile was downloaded.\n> >\n> > Add comment explaining that these pieces of information are used only for\n> > debugging reasons, and how they can be used while debugging.\n> >\n> > Signed-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n> \n> A natural question any reader of the above (and below) would be\n> asking is: Who told you that these are only to aid debugging?\n> \n> Please refer to the commit that brought in the reasoning behind the\n> comment to make it more convincing.  \n> \n> Something like this replacing the second paragraph,\n> \n>     As explained in the log message of the commit 5374a290\n>     (fetch-pack: write fetched refs to .promisor, 2019-10-14), where\n>     this loop originally came from, these ref values are not\n>     actually used for anything in the production, but are solely\n>     there to help debugging.  Explain it in a new comment.\n> \n> perhaps?\n\nMakes perfect sense. I should have done this from the start. Thanks for\npointing that out.\n\n> > +\t/*\n> > +\t* Write in the .promisor file the ref names and associated hashes,\n> > +\t* obtained by fetch-pack, at the point of generation of the\n> > +\t* corresponding packfile. These pieces of info are only used to make\n> > +\t* it easier to debug issues with partial clones, as we can identify\n> > +\t* what refs (and their associated hashes) were fetched at the time\n> > +\t* the packfile was downloaded, and if necessary, compare those hashes\n> > +\t* against what the promisor remote reports now.\n> > +\t*/\n> \n> I do not want to sound too pedantic, but we align '*' asterisks in\n> our multi-line comments, assuming tabwidth=8 and monospace:\n> \n> \t/*\n> \t * Write in the .promisor ...\n> \t...\n> \t * against what the promisor remote reports now.\n> \t */\n> \n> Your second and subsequent lines lack a single whitespace after the\n> leading tab used for indent.\n\nYou are not too pedantic! Ack.\n\n> >  \tfor (i = 0; i < nr_sought; i++)\n> >  \t\tfprintf(output, \"%s %s\\n\", oid_to_hex(&sought[i]->old_oid),\n> >  \t\t\tsought[i]->name);\n"},{"id":"540033","messageId":"acSTY_i4zAueN9jD@lorenzo-VM","threadId":"65326","inReplyTo":"xmqqfr5q44jx.fsf@gitster.g","subject":"Re: [GSoC PATCH v2 2/4] pack-write: add helper to fill promisor file after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-26T02:01:07Z","receivedAt":"2026-03-26T02:01:11Z","isPatch":true,"body":"On Mon, Mar 23, 2026 at 02:30:26PM -0700, Junio C Hamano wrote:\n> LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n> \n> > Create a `copy_all_promisor_files()` helper function used to copy the\n> > contents of all \".promisor\" files in a `repository` inside another\n> > \".promisor\" file.\n> >\n> > This function can be used to preserve the contents of all \".promisor\"\n> > files inside a new \".promisor\" file, for example when a repack happens.\n> >\n> > This function is written in such a way so that it will read all the\n> > \".promisor\" files inside the given `repository` line by line, and copy\n> > only the lines that are not already present in the destination file. This\n> > is done to avoid copying the same lines multiple times that may come from\n> > multiple (redundant) packfiles. There might be another better/cleaner way\n> > to achieve this.\n> \n> In the previous step, we extablished that these \"back then their ref\n> X used to point at object Y\" records are there so that we can\n> identify which refs were fetched at the time the packfile was\n> downloaded to help debugging.  When repacking, losing these records\n> certainly would lose information.\n> \n> But would concatenating all into a single file help preserve the\n> useful information?  Don't we need do better than that?\n\nYeah, we absolutely need to! That's why I said that I was not satisfied\nat all with the patch (in cover letter of v1). I really needed some\nfeedback, because I knew that I was doing things wrong.\n\n> A NEEDSWORK comment, as was discussed in another thread or two in\n> the recent past, is not necessarily a well thought out fully\n> finished specification of an additional piece of work.  \"We know\n> this has a problem, we may need to do something about it, like\n> concatenating to save the contents, perhaps?  We do not know the\n> answer, and we do not bother thinking it through right at this\n> moment.  It is left to the future developers to figure it out\" is\n> what a NEEDSWORK comment is about.\n> \n> Your first response to such a comment may be \"yeah, I agree that it\n> is bad to lose information we added to help debugging\", but the\n> second one should be to wonder if the \"like concatenating...\" is the\n> best approach going forward.\n> \n> In other words, we should take a NEEDSWORK comment as a mere\n> starting point, and what NEEDS your work begins at thinking what\n> needs to be done about the problem raised there.\n\nI fully understand this! Honestly, my biggest weakness that I've\ndiscovered about myself as a dev (through past open-source experience,\ne.g. GSoC'25) is that I get hesitant when I have to work on and submit\na patch if I don't have a lot of experience with the codebase. This\nhappens particularly when I have to take a decision, and not only\ncomplete a task.\n\nIn fact, I decided to work on this specific NEEDSWORK issue to get more\nexperience on promisor remotes (the feature that I want to improve in my\nGSoC proposal) before the GSoC coding period... if I get selected, of\ncourse :-).\n\nI will try my best to improve!\n\n> By mixing them up all into a single list, you no longer can tell\n> when their ref X was observed to be pointing at object Y anymore.\n> You may have two packs originally, with a record for \"ref X pointing\n> at object Y\" in each of them, but by deduping them, you lose the\n> information that you cloned at one time, and made an additional\n> fetch on another day, and the fact the ref X was pointing at the\n> same value at both times.  I am not sure if it is a good\n> implementation if the objective of this topic is to preserve\n> information that is useful for debugging.\n\nMy reasoning was based on the (wrong) assumption that it was impossible\nfor the same record fo \"ref X is pointing at object Y\" to appear\nmultiple times. Obviously then, deduping them is the wrong solution, as\nit will lose some debugging information.\n\n> I wonder if it helps to append to each line the file timestamp of\n> the .promisor file we took the record originally?  For the sake of\n> completeness, we could consider adding the filename as well, but we\n> can quickly dismiss it as not so useful ;-)\n\nRelated to what I said before about getting hesitant... I actually\nthought about (pretty much) exactly this! My original idea was to add\na timestamp of the current time when the repack happened. I discarded it\nbecause I didn't want to add any new information (for no particular\nreason tbh) and because I didn't want \".promisor\" file content to\npotentially become too long if many repacks happen.\n\nYour solution is much cleaner compared to what I originally thought of.\n\n> If repacking already repacked promisor packfile, the records would\n> already contain such a timestamp at the end, so the code to copy\n> existing records must be prepared to see if the records are the\n> <ref, oid> tuple, or <ref, oid, timestamp> tuple, and act\n> accordingly.\n\nMakes perfect sense.\n\n> I am *not* saying that without such a \"preserve timestamp\" column in\n> the record, copying existing records to a new .promisor file is\n> useless.  But we do not see any explanation why the author thinks\n> that it is sufficient to copy existing records while silently\n> deduping.  We can implement only one choice backed by series of\n> decisions like \"timestamp might help\" and \"original filenames would\n> probably not help\", and the design should describe what was\n> considered and rejected (as opposed to \"we didn't think things\n> through---we just did what the original NEEDSWORK comment suggested\n> doing\").\n\nI 100% agree.\n\n> Thanks.\n\nThank you Junio for your time and feedback,\n\nLorenzo\n"},{"id":"540034","messageId":"acSWG-7Kj67zgzvZ@lorenzo-VM","threadId":"65326","inReplyTo":"xmqqa4vy43q2.fsf@gitster.g","subject":"Re: [GSoC PATCH v2 3/4] repack-promisor: preserve content of promisor files after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-26T02:12:43Z","receivedAt":"2026-03-26T02:12:46Z","isPatch":true,"body":"On Mon, Mar 23, 2026 at 02:48:21PM -0700, Junio C Hamano wrote:\n> LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n> \n> > @@ -40,6 +40,7 @@ static void finish_repacking_promisor_objects(struct repository *repo,\n> >  \t\t\t\t\t      const char *packtmp)\n> >  {\n> >  \tstruct strbuf line = STRBUF_INIT;\n> > +\tint is_first_promisor = 1;\n> >  \tFILE *out;\n> > ... \n> > +\t\t/*\n> > +\t\t * Fetch-pack sometimes generates non-empty .promisor files\n> > +\t\t * containing the ref names and associated hashes at the point of\n> > +\t\t * generation of the corresponding packfile. These pieces of info\n> > +\t\t * are only used for debugging reasons. In order to preserve\n> > +\t\t * these, let's copy the contents of all .promisor files in the\n> > +\t\t * first promisor file created.\n> > +\t\t */\n> > +\t\tif (is_first_promisor) {\n> > +\t\t\tcopy_all_promisor_files(repo, promisor_name);\n> > +\t\t\tis_first_promisor = 0;\n> > +\t\t}\n> > +\n> \n> Here the underlying assumption seems to be that whichever one of the\n> two potential callers of this function, repack_promisor_objects()\n> and pack_geometry_repack_promisors(), would handle all the existing\n> packs with corresponding .promisor file so it is safe to coalesce\n> all the debugging comments from all the existing .promisor files\n> into one?\n> \n> Is it really true, though?  Especially with geometry repacking\n> enabled, wouldn't a regular repack coalesce only the smallish ones\n> into a single pack while leaving an already largeish ones intact, or\n> something?\n> \n> Thanks.\n\nI will look into this. I'm going to drastically rework this patch\nseries, so that the next version will be much better and better\nexplained.\n\nThank you so much for the time,\n\nLorenzo\n"},{"id":"540101","messageId":"acVbhqGEc2826Cii@lorenzo-VM","threadId":"65326","inReplyTo":"CAPig+cR7-3rdkHvGbzk8O7P=83pxBTvqXTUgMxYpf+OK9jNCgg@mail.gmail.com","subject":"Re: [GSoC PATCH v2 2/4] pack-write: add helper to fill promisor file after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-03-26T16:15:02Z","receivedAt":"2026-03-26T16:15:07Z","isPatch":true,"body":"On Mon, Mar 23, 2026 at 04:27:44PM -0400, Eric Sunshine wrote:\n> On Sun, Mar 22, 2026 at 3:18 PM LorenzoPegorari\n> <lorenzo.pegorari2002@gmail.com> wrote:\n> > Create a `copy_all_promisor_files()` helper function used to copy the\n> > contents of all \".promisor\" files in a `repository` inside another\n> > \".promisor\" file.\n> >\n> > This function can be used to preserve the contents of all \".promisor\"\n> > files inside a new \".promisor\" file, for example when a repack happens.\n> >\n> > This function is written in such a way so that it will read all the\n> > \".promisor\" files inside the given `repository` line by line, and copy\n> > only the lines that are not already present in the destination file. This\n> > is done to avoid copying the same lines multiple times that may come from\n> > multiple (redundant) packfiles. There might be another better/cleaner way\n> > to achieve this.\n> >\n> > Signed-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n> > ---\n> \n> Thanks, I think this version addresses all my review comments[*] and\n> looks much better overall. Use of `strset` makes a big difference over\n> the previous attempt. A couple minor comments below...\n> \n> [*]: https://lore.kernel.org/git/CAPig+cQSsMfvHJnwuXGQ1Je8ekz=Rqbaibn-3shbya5y-5xTKg@mail.gmail.com/\n> \n> > diff --git a/pack-write.c b/pack-write.c\n> > @@ -621,3 +622,63 @@ void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_\n> > +void copy_all_promisor_files(struct repository *repo, const char *promisor_name)\n> > +{\n> > +       struct strset dest_content = STRSET_INIT;\n> > +       struct strbuf read_line = STRBUF_INIT;\n> > +       struct strbuf promisor_source_name = STRBUF_INIT;\n> > +       struct strbuf write_dest = STRBUF_INIT;\n> > +       FILE *dest, *source;\n> > +       struct packed_git *p;\n> > +       int err;\n> \n> Nit: I probably would have declared `FILE *dest` within the scope of\n> the repo_for_each_pack() loop as suggested in the review, but it's not\n> worth a reroll.\n\nAck. I will fix it. Thanks!\n\n> > +       dest = xfopen(promisor_name, \"r+\");\n> > +       while (strbuf_getline(&read_line, dest) != EOF)\n> > +               strset_add(&dest_content, read_line.buf);\n> > +\n> > +       repo_for_each_pack(repo, p) {\n> > +               if (!p->pack_promisor)\n> > +                       continue;\n> > +\n> > +               strbuf_reset(&promisor_source_name);\n> > +               strbuf_addstr(&promisor_source_name, p->pack_name);\n> > +               strbuf_strip_suffix(&promisor_source_name, \".pack\");\n> > +               strbuf_addstr(&promisor_source_name, \".promisor\");\n> > +               source = xfopen(promisor_source_name.buf, \"r\");\n> > +\n> > +               /*\n> > +                * For each line of the promisor source file, check if it already\n> > +                * is in the promisor dest file. If not, add it to write_dest, so\n> > +                * that it will be written in the dest file.\n> > +                */\n> > +               while (strbuf_getline(&read_line, source) != EOF) {\n> > +                       if (strset_add(&dest_content, read_line.buf)) {\n> > +                               strbuf_addbuf(&write_dest, &read_line);\n> > +                               strbuf_addstr(&write_dest, \"\\n\");\n> \n> Not worth a reroll, but this could also be:\n> \n>     strbuf_addch(&write_dest, '\\n');\n\nAck.\n\n> > +                       }\n> > +               }\n> > +\n> > +               err = ferror(source);\n> > +               err |= fclose(source);\n> > +               if (err)\n> > +                       die(_(\"could not read '%s' promisor file\"), promisor_source_name.buf);\n> > +       }\n> > +\n> > +       if (write_dest.len) {\n> > +               strbuf_strip_suffix(&write_dest, \"\\n\");\n> > +               if (fseek(dest, 0L, SEEK_END))\n> > +                       die_errno(_(\"fseek failed\"));\n> > +               fprintf(dest, \"%s\\n\", write_dest.buf);\n> > +       }\n> \n> Can you explain why you strip \"\\n\" and then re-add it via fprintf()?\n> The reason is not immediately obvious.\n\nStripping it and then adding it again is actually not necessary. I think\nit was necessary in a previous iteration. Thanks for noticing, will fix!\n\n> > +       err = ferror(dest);\n> > +       err |= fclose(dest);\n> > +       if (err)\n> > +               die(_(\"could not write '%s' promisor file\"), promisor_name);\n> > +\n> > +       strbuf_release(&read_line);\n> > +       strbuf_release(&promisor_source_name);\n> > +       strbuf_release(&write_dest);\n> > +       strset_clear(&dest_content);\n> > +}\n> \n> Everything appears to be released. Good.\n\nThank you so much for your help Eric,\n\nLorenzo\n\n"},{"id":"540957","messageId":"cover.1775431990.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1774205661.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v3 0/5] preserve promisor files content after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-06T00:23:48Z","receivedAt":"2026-04-06T00:23:53Z","isPatch":true,"body":"The goal of this patch is to solve the NEEDSWORK comment added by\n5374a290 (fetch-pack: write fetched refs to .promisor, 14/10/2019). This\nis done by adding a helper function that takes the content of all\n.promisor files in the `repository`, and copies it inside the first\n.promisor file created by the repack.\n\nAlso, I added a comment explaining what is the purpose of the content of\nthe .promisor files, since this wasn't explained anywhere (I found\ninformation regarding this only in the message of the previously cited\ncommit).\n\nFinally, I added some tests to \"t7700-repack.sh\" and\n\"t7703-repack-geometric.sh\" that check if the content of .promisor files\nare correctly copied into the .promisor files created by a repack.\n\nThis version is significantly different from the previous one. Maybe I\nshould have created a completely different patch series. Let me know for\nthe future.\n\n\nIMPORTANT:\nThe \"CodingGuidelines\" explicitly state that:\n \"A C file must directly include the header files that declare the\n  functions and the types it uses, except for the functions and types\n  that are made available to it by including one of the header files\n  it must include by the previous rule\"\nwhere \"the previous rule\" is (if I understand correctly), the one related\nto \"<git-compat-util.h>\". From what I understand then, I should have\nadded an include for \"strmap.h\" (which is needed for `strset`), correct?\nAnd if I am correct, shouldn't \"strbuf.h\", \"hash.h\", \"odb.h\",\n\"string-list.h\" and \"strvec.h\" also be included?\n\n\nV3 DIFF:\n * Made the helper function \"copy_promisor_content()\" a static function,\n   because, in my opinion, it is too specific to be used anywhere else\n   (at least in the near future).\n * Modified the helper function to add a <time> piece of information when\n   coping the content of repacked \".promisor\" files, as suggested by\n   Junio Hamano. This is done to give an additional piece of information\n   so that, after a repack, it is still possible to know when the ref\n   <ref> was observed to be pointing at object <oid>.\n * Modified the helper function so that it copies each line only if the\n   <oid> appears in the pack associated with the newly created\n   \".promisor\" file. This is done so that it is possible correctly copy\n   each line in the correct newly created \".promisor\" file, instead of\n   simply appending everything inside the first created \".promisor\" file.\n   I have done some tests with a git repo that contains around 1M objects\n   and with \".promisor\" files that contain in total around 100k lines,\n   and this operation doesn't seem to meaningfully increase the execution\n   time of the repack (it seems to add around 0.5% of execution time on \n   average).\n * Modified the helper function to implement a \"promisor ignorelist\", so\n   that we can explicitly tell to ignore certain packs that we know that\n   we excluded (for example during a geometric repack).\n * Implemented better tests.\n\n\nLorenzoPegorari (5):\n  pack-write: add explanation to promisor file content\n  pack-write: add helper to fill promisor file after repack\n  repack-promisor: preserve content of promisor files after repack\n  t7700: test for promisor file content after repack\n  t7703: test for promisor file content after geometric repack\n\n Documentation/git-repack.adoc |   4 +-\n pack-write.c                  |   9 ++\n repack-promisor.c             | 149 +++++++++++++++++++++++++++++++---\n t/t7700-repack.sh             |  63 ++++++++++++++\n t/t7703-repack-geometric.sh   |  42 ++++++++++\n 5 files changed, 252 insertions(+), 15 deletions(-)\n\n-- \n2.53.0.585.g1533fa96a8\n\n"},{"id":"540958","messageId":"eb1964dca8d8f7e17efccda0b21e2300dab55d65.1775431990.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775431990.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v3 1/5] pack-write: add explanation to promisor file content","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-06T00:24:40Z","receivedAt":"2026-04-06T00:24:46Z","isPatch":true,"body":"In the entire codebase there is no explanation as to why the \".promisor\"\nfiles may contain the ref names (and their associated hashes) that were\nfetched at the time the corresponding packfile was downloaded.\n\nAs explained in the log message of commit 5374a290 (fetch-pack: write\nfetched refs to .promisor, 2019-10-14), where this loop originally came\nfrom, these ref names (and associated hashes) are not used for anything\nin the production, but are solely there to help debugging.\n\nExplain this in a new comment.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n pack-write.c | 9 +++++++++\n 1 file changed, 9 insertions(+)\n\ndiff --git a/pack-write.c b/pack-write.c\nindex 83eaf88541..b8ab9510ff 100644\n--- a/pack-write.c\n+++ b/pack-write.c\n@@ -603,6 +603,15 @@ void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_\n \tint i, err;\n \tFILE *output = xfopen(promisor_name, \"w\");\n \n+\t/*\n+\t * Write in the .promisor file the ref names and associated hashes,\n+\t * obtained by fetch-pack, at the point of generation of the\n+\t * corresponding packfile. These pieces of info are only used to make\n+\t * it easier to debug issues with partial clones, as we can identify\n+\t * what refs (and their associated hashes) were fetched at the time\n+\t * the packfile was downloaded, and if necessary, compare those hashes\n+\t * against what the promisor remote reports now.\n+\t */\n \tfor (i = 0; i < nr_sought; i++)\n \t\tfprintf(output, \"%s %s\\n\", oid_to_hex(&sought[i]->old_oid),\n \t\t\tsought[i]->name);\n-- \n2.53.0.585.g1533fa96a8\n\n"},{"id":"540959","messageId":"3cd15429194c763727fbfd5981ba38c1dc2cc907.1775431990.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775431990.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v3 2/5] pack-write: add helper to fill promisor file after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-06T00:24:53Z","receivedAt":"2026-04-06T00:24:57Z","isPatch":true,"body":"A \".promisor\" file may contain ref names (and their associated hashes)\nthat were fetched at the time the corresponding packfile was downloaded.\nThis information is used for debugging reasons. This information is\nstored as lines structured like this: \"<oid> <ref>\".\n\nCreate a `copy_promisor_content()` helper function that allows this\ndebugging info to not be lost after a `repack`, by coping it inside a new\n\".promisor\" file.\n\nThe function logic is the following:\n * Take all \".promisor\" files contained inside the given `repo`.\n * Ignore those whose name is contained inside the given `strset\n   not_repacked_names`, which basically acts as a \"promisor ignorelist\"\n   (intended to be used for packfiles that have not been repacked).\n * Read each line of the remaining \".promisor\" files, which can be:\n    * \"<oid> <ref>\" if the \".promisor\" file was never repacked. If so,\n      add the time at which the \".promisor\" file was last modified <time>\n      to the line to create the string: \"<oid> <ref> <time>\".\n    * \"<oid> <ref> <time>\" if the \".promisor\" file was repacked. If so,\n      don't modify it.\n * Ignore the line if its <oid> is not present inside the\n   \"<packtmp>-<dest_hex>.idx\" file.\n * If the destination file \"<packtmp>-<dest_hex>.promisor\" does not\n   already contain the line, append it to the file.\n\nThe function assumes that the contents of all \".promisor\" files are\ncorrectly formed.\n\nThe time of last data modification is used in place of the time of file\ncreation, because the former is much easier to obtain than the latter\none.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n repack-promisor.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 119 insertions(+)\n\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 90318ce150..6da452e8ff 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -34,6 +34,125 @@ static int write_oid(const struct object_id *oid,\n \treturn 0;\n }\n \n+/*\n+ * Go through all .promisor files contained in repo (excluding those whose name\n+ * appears in not_repacked_basenames, which acts as a ignorelist), and copies\n+ * their content inside the destination file \"<packtmp>-<dest_hex>.promisor\".\n+ * Each line of a never repacked .promisor file is: \"<oid> <ref>\" (as described\n+ * in the write_promisor_file() function).\n+ * After a repack, the copied lines will be: \"<oid> <ref> <time>\", where <time>\n+ * is the time at which the .promisor file was last modified.\n+ * Only the lines whose <oid> is present inside \"<packtmp>-<dest_hex>.idx\" will\n+ * be copied.\n+ * The contents of all .promisor files are assumed to be correctly formed.\n+ */\n+static void copy_promisor_content(struct repository *repo,\n+\t\t\t\t\t      const char *dest_hex,\n+\t\t\t\t\t      const char *packtmp,\n+\t\t\t\t\t      struct strset *not_repacked_basenames)\n+{\n+\tchar *dest_idx_name;\n+\tchar *dest_promisor_name;\n+\tFILE *dest;\n+\tstruct strset dest_content = STRSET_INIT;\n+\tstruct strbuf dest_to_write = STRBUF_INIT;\n+\tstruct strbuf source_promisor_name = STRBUF_INIT;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tstruct object_id dest_oid;\n+\tstruct packed_git *dest_pack, *p;\n+\tint err;\n+\n+\tdest_idx_name = mkpathdup(\"%s-%s.idx\", packtmp, dest_hex);\n+\tget_oid_hex_algop(dest_hex, &dest_oid, repo->hash_algo);\n+\tdest_pack = parse_pack_index(repo, dest_oid.hash, dest_idx_name);\n+\n+\t/* Open the .promisor dest file, and fill dest_content with its content */\n+\tdest_promisor_name = mkpathdup(\"%s-%s.promisor\", packtmp, dest_hex);\n+\tdest = xfopen(dest_promisor_name, \"r+\");\n+\twhile (strbuf_getline(&line, dest) != EOF)\n+\t\tstrset_add(&dest_content, line.buf);\n+\n+\trepo_for_each_pack(repo, p) {\n+\t\tFILE *source;\n+\t\tstruct stat source_stat;\n+\n+\t\tif (!p->pack_promisor)\n+\t\t\tcontinue;\n+\n+\t\tif (not_repacked_basenames &&\n+\t\t\tstrset_contains(not_repacked_basenames, pack_basename(p)))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&source_promisor_name);\n+\t\tstrbuf_addstr(&source_promisor_name, p->pack_name);\n+\t\tstrbuf_strip_suffix(&source_promisor_name, \".pack\");\n+\t\tstrbuf_addstr(&source_promisor_name, \".promisor\");\n+\n+\t\tif (stat(source_promisor_name.buf, &source_stat))\n+\t\t\tdie(_(\"File not found: %s\"), source_promisor_name.buf);\n+\n+\t\tsource = xfopen(source_promisor_name.buf, \"r\");\n+\n+\t\twhile (strbuf_getline(&line, source) != EOF) {\n+\t\t\tstruct strbuf **parts;\n+\t\t\tstruct object_id oid;\n+\n+\t\t\t/* Split line into <oid>, <ref> and <time> (if <time> exists) */\n+\t\t\tparts = strbuf_split_max(&line, ' ', 3);\n+\n+\t\t\t/* Ignore the lines where <oid> doesn't appear in the dest_pack */\n+\t\t\tstrbuf_rtrim(parts[0]);\n+\t\t\tget_oid_hex_algop(parts[0]->buf, &oid, repo->hash_algo);\n+\t\t\tif (!find_pack_entry_one(&oid, dest_pack))\n+\t\t\t\tcontinue;\n+\n+\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n+\t\t\tif (!parts[2]) {\n+\t\t\t\tstruct tm tm;\n+\t\t\t\tlocaltime_r(&source_stat.st_mtim.tv_sec, &tm),\n+\t\t\t\tstrbuf_addch(&line, ' ');\n+\t\t\t\tstrbuf_addftime(&line, \"%Y/%m/%d-%H:%M:%S\", &tm, 0, 0);\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * Add the finalized line to dest_to_write and dest_content if it\n+\t\t\t * wasn't already present inside dest_content\n+\t\t\t */\n+\t\t\tif (strset_add(&dest_content, line.buf)) {\n+\t\t\t\tstrbuf_addbuf(&dest_to_write, &line);\n+\t\t\t\tstrbuf_addch(&dest_to_write, '\\n');\n+\t\t\t}\n+\n+\t\t\tstrbuf_list_free(parts);\n+\t\t}\n+\n+\t\terr = ferror(source);\n+\t\terr |= fclose(source);\n+\t\tif (err)\n+\t\t\tdie(_(\"Could not read '%s' promisor file\"), source_promisor_name.buf);\n+\t}\n+\n+\t/* If dest_to_write is not empty, then there are new lines to append */\n+\tif (dest_to_write.len) {\n+\t\tif (fseek(dest, 0L, SEEK_END))\n+\t\t\tdie_errno(_(\"fseek failed\"));\n+\t\tfprintf(dest, \"%s\", dest_to_write.buf);\n+\t}\n+\n+\terr = ferror(dest);\n+\terr |= fclose(dest);\n+\tif (err)\n+\t\tdie(_(\"Could not write '%s' promisor file\"), dest_promisor_name);\n+\n+\tclose_pack_index(dest_pack);\n+\tfree(dest_idx_name);\n+\tfree(dest_promisor_name);\n+\tstrset_clear(&dest_content);\n+\tstrbuf_release(&dest_to_write);\n+\tstrbuf_release(&source_promisor_name);\n+\tstrbuf_release(&line);\n+}\n+\n static void finish_repacking_promisor_objects(struct repository *repo,\n \t\t\t\t\t      struct child_process *cmd,\n \t\t\t\t\t      struct string_list *names,\n-- \n2.53.0.585.g1533fa96a8\n\n"},{"id":"540960","messageId":"c16b1198fdcf3ad9aaaf6f9436fe7558980edc73.1775431990.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775431990.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v3 3/5] repack-promisor: preserve content of promisor files after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-06T00:25:06Z","receivedAt":"2026-04-06T00:25:10Z","isPatch":true,"body":"When a repack involving promisor packfiles happens, the new \".promisor\"\nfile is created empty, losing all the debug info that might be present\ninside the \".promisor\" files before the repack.\n\nUse the \"copy_promisor_content()\" function created previously to preserve\nthe contents of all \".promisor\" files inside the first \".promisor\" file\ncreated by the repack.\n\nFor geometric repacking, we have to create a `strset` that contains the\nbasenames of all excluded packs. For \"normal\" repacking this is not\nnecessary, since there should be no excluded packs.\n\nAlso, update the documentation accordingly.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n Documentation/git-repack.adoc |  4 ++--\n repack-promisor.c             | 30 +++++++++++++++++-------------\n 2 files changed, 19 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/git-repack.adoc b/Documentation/git-repack.adoc\nindex 673ce91083..33d3c8afbd 100644\n--- a/Documentation/git-repack.adoc\n+++ b/Documentation/git-repack.adoc\n@@ -45,8 +45,8 @@ other objects in that pack they already have locally.\n +\n Promisor packfiles are repacked separately: if there are packfiles that\n have an associated \".promisor\" file, these packfiles will be repacked\n-into another separate pack, and an empty \".promisor\" file corresponding\n-to the new separate pack will be written.\n+into another separate pack, and a \".promisor\" file corresponding to the\n+new separate pack will be written (with arbitrary contents).\n \n -A::\n \tSame as `-a`, unless `-d` is used.  Then any unreachable\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 6da452e8ff..37502e0023 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -156,7 +156,8 @@ static void copy_promisor_content(struct repository *repo,\n static void finish_repacking_promisor_objects(struct repository *repo,\n \t\t\t\t\t      struct child_process *cmd,\n \t\t\t\t\t      struct string_list *names,\n-\t\t\t\t\t      const char *packtmp)\n+\t\t\t\t\t      const char *packtmp,\n+\t\t\t\t\t      struct strset *not_repacked_basenames)\n {\n \tstruct strbuf line = STRBUF_INIT;\n \tFILE *out;\n@@ -174,19 +175,15 @@ static void finish_repacking_promisor_objects(struct repository *repo,\n \n \t\t/*\n \t\t * pack-objects creates the .pack and .idx files, but not the\n-\t\t * .promisor file. Create the .promisor file, which is empty.\n-\t\t *\n-\t\t * NEEDSWORK: fetch-pack sometimes generates non-empty\n-\t\t * .promisor files containing the ref names and associated\n-\t\t * hashes at the point of generation of the corresponding\n-\t\t * packfile, but this would not preserve their contents. Maybe\n-\t\t * concatenate the contents of all .promisor files instead of\n-\t\t * just creating a new empty file.\n+\t\t * .promisor file. Create the .promisor file.\n \t\t */\n \t\tpromisor_name = mkpathdup(\"%s-%s.promisor\", packtmp,\n \t\t\t\t\t  line.buf);\n \t\twrite_promisor_file(promisor_name, NULL, 0);\n \n+\t\t/* Now let's fill the content of the newly created .promisor file */\n+\t\tcopy_promisor_content(repo, line.buf, packtmp, not_repacked_basenames);\n+\n \t\titem->util = generated_pack_populate(item->string, packtmp);\n \n \t\tfree(promisor_name);\n@@ -226,7 +223,7 @@ void repack_promisor_objects(struct repository *repo,\n \t\treturn;\n \t}\n \n-\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp);\n+\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp, NULL);\n }\n \n void pack_geometry_repack_promisors(struct repository *repo,\n@@ -237,6 +234,7 @@ void pack_geometry_repack_promisors(struct repository *repo,\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tFILE *in;\n+\tstruct strset not_repacked_basenames = STRSET_INIT;\n \n \tif (!geometry->promisor_split)\n \t\treturn;\n@@ -250,9 +248,15 @@ void pack_geometry_repack_promisors(struct repository *repo,\n \tin = xfdopen(cmd.in, \"w\");\n \tfor (size_t i = 0; i < geometry->promisor_split; i++)\n \t\tfprintf(in, \"%s\\n\", pack_basename(geometry->promisor_pack[i]));\n-\tfor (size_t i = geometry->promisor_split; i < geometry->promisor_pack_nr; i++)\n-\t\tfprintf(in, \"^%s\\n\", pack_basename(geometry->promisor_pack[i]));\n+\tfor (size_t i = geometry->promisor_split; i < geometry->promisor_pack_nr; i++) {\n+\t\tconst char *name = pack_basename(geometry->promisor_pack[i]);\n+\t\tfprintf(in, \"^%s\\n\", name);\n+\t\tstrset_add(&not_repacked_basenames, name);\n+\t}\n \tfclose(in);\n \n-\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp);\n+\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp,\n+\t\t\tstrset_get_size(&not_repacked_basenames) ? &not_repacked_basenames : NULL);\n+\n+\tstrset_clear(&not_repacked_basenames);\n }\n-- \n2.53.0.585.g1533fa96a8\n\n"},{"id":"540961","messageId":"8e58c1263d15fb8dba8ce1d2866d369e938bf2b6.1775431990.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775431990.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v3 4/5] t7700: test for promisor file content after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-06T00:25:19Z","receivedAt":"2026-04-06T00:25:24Z","isPatch":true,"body":"Add tests that checks if the content of \".promisor\" files are correctly\ncopied inside the \".promisor\" files created by a repack.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n t/t7700-repack.sh | 63 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 63 insertions(+)\n\ndiff --git a/t/t7700-repack.sh b/t/t7700-repack.sh\nindex 63ef63fc50..89a2116641 100755\n--- a/t/t7700-repack.sh\n+++ b/t/t7700-repack.sh\n@@ -904,4 +904,67 @@ test_expect_success 'pending objects are repacked appropriately' '\n \t)\n '\n \n+test_expect_success 'check one .promisor file content after repack' '\n+\ttest_when_finished rm -rf prom_test &&\n+\tgit init prom_test &&\n+\tpath=prom_test/.git/objects/pack &&\n+\n+\t(\n+\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n+\t\t\n+\t\t# Simulate .promisor file by creating it manually\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid ref\" >$prom &&\n+\n+\t\t# Save the current .promisor content, repack, and check if correct\n+\t\tprom_before_repack=$(cat $prom) &&\n+\t\tgit -C prom_test repack -a -d &&\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n+\t\t# $prom should contain \"$prom_before_repack <date>\"\n+\t\ttest_grep \"$prom_before_repack \" $prom &&\n+\n+\t\t# Save the current .promisor content, repack, and check if correct\n+\t\tcat $prom >prom_before_repack &&\n+\t\tgit -C prom_test repack -a -d &&\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n+\t\t# $prom should be exactly the same as prom_before_repack\n+\t\ttest_cmp prom_before_repack $prom\n+\t)\n+'\n+\n+test_expect_success 'check multiple .promisor file content after repack' '\n+\ttest_when_finished rm -rf prom_test &&\n+\tgit init prom_test &&\n+\tpath=prom_test/.git/objects/pack &&\n+\n+\t(\n+\t\t# Create 2 packs and simulate .promisor files by creating them manually\n+\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid ref\" >$prom &&\n+\t\tprom_before_repack1=$(cat $prom) &&\n+\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n+\t\tprom=$(ls -t $path/*.pack | head -n 1 | sed \"s/\\.pack/.promisor/\") &&\n+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid ref\" >$prom &&\n+\t\tprom_before_repack2=$(cat $prom) &&\n+\n+\t\t# Repack, and check if correct compared to previous saved .promisor content\n+\t\tgit -C prom_test repack -a -d &&\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n+\t\t# $prom should contain \"$prom_before_repack1 <date>\" & \"$prom_before_repack2 <date>\"\n+\t\ttest_grep \"$prom_before_repack1 \" $prom &&\n+\t\ttest_grep \"$prom_before_repack2 \" $prom &&\n+\n+\t\t# Save the current .promisor content, repack, and check if correct\n+\t\tcat $prom >prom_before_repack &&\n+\t\tgit -C prom_test repack -a -d &&\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n+\t\t# $prom should be exactly the same as prom_before_repack\n+\t\ttest_cmp prom_before_repack $prom\n+\t)\n+'\n+\n test_done\n-- \n2.53.0.585.g1533fa96a8\n\n"},{"id":"540962","messageId":"1533fa96a8e2ed135504dee54535b2ed38fe5dab.1775431990.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775431990.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v3 5/5] t7703: test for promisor file content after geometric repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-06T00:25:28Z","receivedAt":"2026-04-06T00:25:32Z","isPatch":true,"body":"Add test that checks if the content of \".promisor\" files are correctly\ncopied inside the \".promisor\" files created by a geometric repack.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n t/t7703-repack-geometric.sh | 42 +++++++++++++++++++++++++++++++++++++\n 1 file changed, 42 insertions(+)\n\ndiff --git a/t/t7703-repack-geometric.sh b/t/t7703-repack-geometric.sh\nindex 04d5d8fc33..231db98743 100755\n--- a/t/t7703-repack-geometric.sh\n+++ b/t/t7703-repack-geometric.sh\n@@ -541,4 +541,46 @@ test_expect_success 'geometric repack works with promisor packs' '\n \t)\n '\n \n+test_expect_success 'check .promisor file content after geometric repack' '\n+\ttest_when_finished rm -rf prom_test &&\n+\tgit init prom_test &&\n+\tpath=prom_test/.git/objects/pack &&\n+\n+\t(\n+\t\t# Create 2 packs with 3 objs each, and manually create .promisor files\n+\t\ttest_commit_bulk -C prom_test --start=1 1 &&  # 3 objects\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid ref\" >$prom &&\n+\t\tprom_before_repack1=$(cat $prom) &&\n+\t\ttest_commit_bulk -C prom_test --start=2 1 &&  # 3 objects\n+\t\tprom=$(ls -t $path/*.pack | head -n 1 | sed \"s/\\.pack/.promisor/\") &&\n+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid ref\" >$prom &&\n+\t\tprom_before_repack2=$(cat $prom) &&\n+\n+\t\t# Create 2 packs with 12 and 24 objs, and manually create .promisor files\n+\t\ttest_commit_bulk -C prom_test --start=3 4 &&  # 12 objects\n+\t\tprom=$(ls -t $path/*.pack | head -n 1 | sed \"s/\\.pack/.promisor/\") &&\n+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid ref\" >$prom &&\n+\t\tprom_before_repack3=$(cat $prom) &&\n+\t\ttest_commit_bulk -C prom_test --start=7 8 &&  # 24 objects\n+\t\tprom=$(ls -t $path/*.pack | head -n 1 | sed \"s/\\.pack/.promisor/\") &&\n+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid ref\" >$prom &&\n+\t\tprom_before_repack4=$(cat $prom) &&\n+\n+\t\t# Geometric repack, and check if correct compared to previous saved .promisor content\n+\t\tgit -C prom_test repack --geometric 2 -d &&\n+\t\tprom=$(ls -t $path/*.pack | head -n 1 | sed \"s/\\.pack/.promisor/\") &&\n+\t\t# $prom should have repacked only the first 2 small packs, so it should only contain\n+\t\t# the following: \"$prom_before_repack1 <date>\" & \"$prom_before_repack2 <date>\"\n+\t\ttest_grep \"$prom_before_repack1 \" $prom &&\n+\t\ttest_grep \"$prom_before_repack2 \" $prom &&\n+\t\ttest_grep ! $prom_before_repack3 $prom &&\n+\t\ttest_grep ! $prom_before_repack4 $prom\n+\t)\n+'\n+\n test_done\n-- \n2.53.0.585.g1533fa96a8\n\n"},{"id":"540987","messageId":"f2540dc1-fe76-48ec-91a7-82e32ced75fc@malon.dev","threadId":"65326","inReplyTo":"3cd15429194c763727fbfd5981ba38c1dc2cc907.1775431990.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v3 2/5] pack-write: add helper to fill promisor file after repack","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-04-06T17:22:16Z","receivedAt":"2026-04-06T17:22:23Z","isPatch":true,"body":"Hi,\n\nOn 4/6/26 08:24, LorenzoPegorari wrote:\n\n> +\t\twhile (strbuf_getline(&line, source) != EOF) {\n> +\t\t\tstruct strbuf **parts;\n> +\t\t\tstruct object_id oid;\n> +\n> +\t\t\t/* Split line into <oid>, <ref> and <time> (if <time> exists) */\n> +\t\t\tparts = strbuf_split_max(&line, ' ', 3);\n> +\n> +\t\t\t/* Ignore the lines where <oid> doesn't appear in the dest_pack */\n> +\t\t\tstrbuf_rtrim(parts[0]);\n> +\t\t\tget_oid_hex_algop(parts[0]->buf, &oid, repo->hash_algo);\n> +\t\t\tif (!find_pack_entry_one(&oid, dest_pack))\n> +\t\t\t\tcontinue;\n\nMemory leak here;\n\n> +\n> +\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n> +\t\t\tif (!parts[2]) {\n> +\t\t\t\tstruct tm tm;\n> +\t\t\t\tlocaltime_r(&source_stat.st_mtim.tv_sec, &tm),\n\nTypo.\n\n> +\t\t\t\tstrbuf_addch(&line, ' ');\n> +\t\t\t\tstrbuf_addftime(&line, \"%Y/%m/%d-%H:%M:%S\", &tm, 0, 0);\n> +\t\t\t}\n> +\n> +\t\t\t/*\n> +\t\t\t * Add the finalized line to dest_to_write and dest_content if it\n> +\t\t\t * wasn't already present inside dest_content\n> +\t\t\t */\n> +\t\t\tif (strset_add(&dest_content, line.buf)) {\n> +\t\t\t\tstrbuf_addbuf(&dest_to_write, &line);\n> +\t\t\t\tstrbuf_addch(&dest_to_write, '\\n');\n> +\t\t\t}\n\nIt looks good elsewhere, at least in this patch 2/5. (ゝ∀･)\n\nRegards, Yuchen\n\n\n"},{"id":"541011","messageId":"adP-MYYSmElK9wL3@lorenzo-VM","threadId":"65326","inReplyTo":"f2540dc1-fe76-48ec-91a7-82e32ced75fc@malon.dev","subject":"Re: [GSoC PATCH v3 2/5] pack-write: add helper to fill promisor file after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-06T18:40:49Z","receivedAt":"2026-04-06T18:40:54Z","isPatch":true,"body":"On Tue, Apr 07, 2026 at 01:22:16AM +0800, Tian Yuchen wrote:\n> Hi,\n> \n> On 4/6/26 08:24, LorenzoPegorari wrote:\n> \n> > +\t\twhile (strbuf_getline(&line, source) != EOF) {\n> > +\t\t\tstruct strbuf **parts;\n> > +\t\t\tstruct object_id oid;\n> > +\n> > +\t\t\t/* Split line into <oid>, <ref> and <time> (if <time> exists) */\n> > +\t\t\tparts = strbuf_split_max(&line, ' ', 3);\n> > +\n> > +\t\t\t/* Ignore the lines where <oid> doesn't appear in the dest_pack */\n> > +\t\t\tstrbuf_rtrim(parts[0]);\n> > +\t\t\tget_oid_hex_algop(parts[0]->buf, &oid, repo->hash_algo);\n> > +\t\t\tif (!find_pack_entry_one(&oid, dest_pack))\n> > +\t\t\t\tcontinue;\n> \n> Memory leak here;\n\nYep, `strbuf_list_free(parts)` is missing here. Ack.\n\n> > +\n> > +\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n> > +\t\t\tif (!parts[2]) {\n> > +\t\t\t\tstruct tm tm;\n> > +\t\t\t\tlocaltime_r(&source_stat.st_mtim.tv_sec, &tm),\n> \n> Typo.\n\nAck.\n\n> \n> > +\t\t\t\tstrbuf_addch(&line, ' ');\n> > +\t\t\t\tstrbuf_addftime(&line, \"%Y/%m/%d-%H:%M:%S\", &tm, 0, 0);\n> > +\t\t\t}\n> > +\n> > +\t\t\t/*\n> > +\t\t\t * Add the finalized line to dest_to_write and dest_content if it\n> > +\t\t\t * wasn't already present inside dest_content\n> > +\t\t\t */\n> > +\t\t\tif (strset_add(&dest_content, line.buf)) {\n> > +\t\t\t\tstrbuf_addbuf(&dest_to_write, &line);\n> > +\t\t\t\tstrbuf_addch(&dest_to_write, '\\n');\n> > +\t\t\t}\n> \n> It looks good elsewhere, at least in this patch 2/5. (ゝ∀･)\n> \n> Regards, Yuchen\n\nThank you Yuchen!\n\nLorenzo\n"},{"id":"541022","messageId":"xmqqeckraiwh.fsf@gitster.g","threadId":"65326","inReplyTo":"adP-MYYSmElK9wL3@lorenzo-VM","subject":"Re: [GSoC PATCH v3 2/5] pack-write: add helper to fill promisor file after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-06T21:17:02Z","receivedAt":"2026-04-06T21:17:04Z","isPatch":true,"body":"Lorenzo Pegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> On Tue, Apr 07, 2026 at 01:22:16AM +0800, Tian Yuchen wrote:\n>> Hi,\n>> \n>> On 4/6/26 08:24, LorenzoPegorari wrote:\n>> \n>> > +\t\twhile (strbuf_getline(&line, source) != EOF) {\n>> > +\t\t\tstruct strbuf **parts;\n>> > +\t\t\tstruct object_id oid;\n>> > +\n>> > +\t\t\t/* Split line into <oid>, <ref> and <time> (if <time> exists) */\n>> > +\t\t\tparts = strbuf_split_max(&line, ' ', 3);\n>> > +\n>> > +\t\t\t/* Ignore the lines where <oid> doesn't appear in the dest_pack */\n>> > +\t\t\tstrbuf_rtrim(parts[0]);\n>> > +\t\t\tget_oid_hex_algop(parts[0]->buf, &oid, repo->hash_algo);\n>> > +\t\t\tif (!find_pack_entry_one(&oid, dest_pack))\n>> > +\t\t\t\tcontinue;\n>> \n>> Memory leak here;\n>\n> Yep, `strbuf_list_free(parts)` is missing here. Ack.\n\nAlso strbuf_split*() is a bad API.  Unless you need all the parts[]\nstrbuf instances all editable at the same time, an array of strbuf\nis a data structure that is way overkill.  Splitting into string-list\nmay make it more palatable, I think.\n\nWe even went through a series of patches (and follow-up effort by\nother contributors) [*] to rewrite callers that unnecessarily call\nstrbuf_split*().\n\n[References]\n  https://lore.kernel.org/git/20250731225433.4028872-1-gitster@pobox.com/\n  https://lore.kernel.org/git/cover.1761217100.git.belkid98@gmail.com/\n\n"},{"id":"541024","messageId":"xmqq8qazai3b.fsf@gitster.g","threadId":"65326","inReplyTo":"3cd15429194c763727fbfd5981ba38c1dc2cc907.1775431990.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v3 2/5] pack-write: add helper to fill promisor file after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-06T21:34:32Z","receivedAt":"2026-04-06T21:34:34Z","isPatch":true,"body":"LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> A \".promisor\" file may contain ref names (and their associated hashes)\n> that were fetched at the time the corresponding packfile was downloaded.\n> This information is used for debugging reasons. This information is\n> stored as lines structured like this: \"<oid> <ref>\".\n>\n> Create a `copy_promisor_content()` helper function that allows this\n> debugging info to not be lost after a `repack`, by coping it inside a new\n> \".promisor\" file.\n\n\"coping\" -> \"copying\"\n\n> The function logic is the following:\n>  * Take all \".promisor\" files contained inside the given `repo`.\n>  * Ignore those whose name is contained inside the given `strset\n>    not_repacked_names`, which basically acts as a \"promisor ignorelist\"\n>    (intended to be used for packfiles that have not been repacked).\n>  * Read each line of the remaining \".promisor\" files, which can be:\n>     * \"<oid> <ref>\" if the \".promisor\" file was never repacked. If so,\n>       add the time at which the \".promisor\" file was last modified <time>\n>       to the line to create the string: \"<oid> <ref> <time>\".\n>     * \"<oid> <ref> <time>\" if the \".promisor\" file was repacked. If so,\n>       don't modify it.\n>  * Ignore the line if its <oid> is not present inside the\n>    \"<packtmp>-<dest_hex>.idx\" file.\n>  * If the destination file \"<packtmp>-<dest_hex>.promisor\" does not\n>    already contain the line, append it to the file.\n>\n> The function assumes that the contents of all \".promisor\" files are\n> correctly formed.\n>\n> The time of last data modification is used in place of the time of file\n> creation, because the former is much easier to obtain than the latter\n> one.\n\nThe time of file creation is not recorded anywhere if you are\ndealing with the usual UNIX filesystems (ctime is not creation\ntime), so it is not the issue of \"easier to obtain\".\n\nThe reason why this design chooses to add time is because in a\nnever-repacked .promisor file, the modification time of the file\nitself can be used when you compare the entries in it with entries\nin another .promisor file that did get repacked.  By having\ntimestamp, the debugger can tell at which time the refs at the\nremote repository pointed at what object---the same ref may appear\ntwice in the same .promisor file and having timestamps would help\nunderstanding what happened over time.\n\n"},{"id":"541026","messageId":"xmqqldez9232.fsf@gitster.g","threadId":"65326","inReplyTo":"8e58c1263d15fb8dba8ce1d2866d369e938bf2b6.1775431990.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v3 4/5] t7700: test for promisor file content after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-06T22:05:37Z","receivedAt":"2026-04-06T22:05:40Z","isPatch":true,"body":"LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> +test_expect_success 'check one .promisor file content after repack' '\n> +\ttest_when_finished rm -rf prom_test &&\n> +\tgit init prom_test &&\n> +\tpath=prom_test/.git/objects/pack &&\n> +\n> +\t(\n> +\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n> +\t\t\n> +\t\t# Simulate .promisor file by creating it manually\n> +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n\nSo \"prom\" is a list of filenames; since $path does not have any\nfunny letters that interferes, later use of unquotd $prom will\nlist these files.  OK.\n\n> +\t\toid=$(git -C prom_test rev-parse HEAD) &&\n> +\t\techo \"$oid ref\" >$prom &&\n\nOh, not quite.  How are we guaranteeing that there is only one file\nin the list of files in $prom?\n\nIn any case, quoting from Documentation/CodingGuidelines:\n\n - Redirection operators should be written with space before, but no\n   space after them.  In other words, write 'echo test >\"$file\"'\n   instead of 'echo test> $file' or 'echo test > $file'.  Note that\n   even though it is not required by POSIX to double-quote the\n   redirection target in a variable (as shown above), our code does so\n   because some versions of bash issue a warning without the quotes.\n\n\t(incorrect)\n\tcat hello > world < universe\n\techo hello >$world\n\n\t(correct)\n\tcat hello >world <universe\n\techo hello >\"$world\"\n\n\n> +\t\t# Save the current .promisor content, repack, and check if correct\n> +\t\tprom_before_repack=$(cat $prom) &&\n\nThis is misleading, unless you plan to update the early part of this\ntest to store a more realistic data in the $prom file.  Wouldn't it\nbe equivanent to\n\n\t\tprom_before_repack=\"$oid ref\" &&\n\nat this point?\n\n> +\t\tgit -C prom_test repack -a -d &&\n> +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n\nWe expect that there is only one .pack and .promisor file.  Why\nare we listing .pack and turning them to .promisor, instead of doing\n\n\t\tprom=$(ls $path/*.promisor) &&\n\nhere?  Don't we expect that this \"repack\" to recreate .promisor file\nas well (and if we do not see the file then we detected another bug,\nwhich is a good thing)?\n\n> +\t\t# $prom should contain \"$prom_before_repack <date>\"\n> +\t\ttest_grep \"$prom_before_repack \" $prom &&\n\nI do not quite understand this test.  Ahh, OK.  We expect that there\nwas only a single entry in the original, because that is what we\nplaced in the original .promisor file.\n\nEnclose $prom inside a pair of double quotes, as it is misleading\nwithout.  I wasted a few minutes wondering where you are expecting\nthese possibly multiple promisor files from.\n\n> +\t\t# Save the current .promisor content, repack, and check if correct\n> +\t\tcat $prom >prom_before_repack &&\n\n\t\tcp \"$prom\" prom_before_repack &&\n\nwould be more standard.\n\n> +\t\tgit -C prom_test repack -a -d &&\n> +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n\nThe same comment about \"don't we know .promisor file should exist,\nand shouldn't we check it directly?\" applies here.\n\n> +\t\t# $prom should be exactly the same as prom_before_repack\n> +\t\ttest_cmp prom_before_repack $prom\n> +\t)\n> +'\n\n\nSame comment applies from earlier to the next test piece, I suspect.\nLet's take a look.\n\n> +\n> +test_expect_success 'check multiple .promisor file content after repack' '\n> +\ttest_when_finished rm -rf prom_test &&\n> +\tgit init prom_test &&\n> +\tpath=prom_test/.git/objects/pack &&\n> +\n> +\t(\n> +\t\t# Create 2 packs and simulate .promisor files by creating them manually\n> +\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n> +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> +\t\toid=$(git -C prom_test rev-parse HEAD) &&\n> +\t\techo \"$oid ref\" >$prom &&\n> +\t\tprom_before_repack1=$(cat $prom) &&\n\n> +\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n> +\t\tprom=$(ls -t $path/*.pack | head -n 1 | sed \"s/\\.pack/.promisor/\") &&\n\nDo not pipe head into sed, as sed is more capable.\n\n\t\tls -t $path/*.pack | sed \"s/.../;q\"\n\n> +\t\toid=$(git -C prom_test rev-parse HEAD) &&\n> +\t\techo \"$oid ref\" >$prom &&\n> +\t\tprom_before_repack2=$(cat $prom) &&\n\nBut more importantly, this may become a source of flakiness.  These\ntwo packfiles are likely to have very close timestamps and depending\non the timing, how heavily loaded the machine is, and the phase of\nthe moon, it is not guaranteed that you'd grab the name of the new\npack.  Instead of sorting by type or getting the first one, which\nwould not work reliably, grab both and filter out what you already\nhave seen.\n\n> +\t\t# Repack, and check if correct compared to previous saved .promisor content\n> +\t\tgit -C prom_test repack -a -d &&\n> +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> +\t\t# $prom should contain \"$prom_before_repack1 <date>\" & \"$prom_before_repack2 <date>\"\n> +\t\ttest_grep \"$prom_before_repack1 \" $prom &&\n> +\t\ttest_grep \"$prom_before_repack2 \" $prom &&\n> +\n> +\t\t# Save the current .promisor content, repack, and check if correct\n> +\t\tcat $prom >prom_before_repack &&\n> +\t\tgit -C prom_test repack -a -d &&\n> +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> +\t\t# $prom should be exactly the same as prom_before_repack\n> +\t\ttest_cmp prom_before_repack $prom\n> +\t)\n> +'\n> +\n>  test_done\n"},{"id":"541030","messageId":"xmqqy0iz7clt.fsf@gitster.g","threadId":"65326","inReplyTo":"adP-MYYSmElK9wL3@lorenzo-VM","subject":"Re: [GSoC PATCH v3 2/5] pack-write: add helper to fill promisor file after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-07T02:01:18Z","receivedAt":"2026-04-07T02:01:22Z","isPatch":true,"body":"Lorenzo Pegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> On Tue, Apr 07, 2026 at 01:22:16AM +0800, Tian Yuchen wrote:\n>> Hi,\n>> \n>> On 4/6/26 08:24, LorenzoPegorari wrote:\n>> \n>> > +\t\twhile (strbuf_getline(&line, source) != EOF) {\n>> > +\t\t\tstruct strbuf **parts;\n>> > +\t\t\tstruct object_id oid;\n>> > +\n>> > +\t\t\t/* Split line into <oid>, <ref> and <time> (if <time> exists) */\n>> > +\t\t\tparts = strbuf_split_max(&line, ' ', 3);\n>> > +\n>> > +\t\t\t/* Ignore the lines where <oid> doesn't appear in the dest_pack */\n>> > +\t\t\tstrbuf_rtrim(parts[0]);\n>> > +\t\t\tget_oid_hex_algop(parts[0]->buf, &oid, repo->hash_algo);\n>> > +\t\t\tif (!find_pack_entry_one(&oid, dest_pack))\n>> > +\t\t\t\tcontinue;\n>> \n>> Memory leak here;\n>\n> Yep, `strbuf_list_free(parts)` is missing here. Ack.\n>\n>> > +\n>> > +\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n>> > +\t\t\tif (!parts[2]) {\n>> > +\t\t\t\tstruct tm tm;\n>> > +\t\t\t\tlocaltime_r(&source_stat.st_mtim.tv_sec, &tm),\n>> \n>> Typo.\n>\n> Ack.\n\nNot just an unintended use of comma operator, this is not portable\nand breaks OSX build\n\n  https://github.com/git/git/actions/runs/24058681172/job/70170218891#step:4:213\n\n>> > +\t\t\t\tstrbuf_addch(&line, ' ');\n>> > +\t\t\t\tstrbuf_addftime(&line, \"%Y/%m/%d-%H:%M:%S\", &tm, 0, 0);\n\nI suspect that storing seconds since epoch as a large integer would\nbe simpler and much less error prone than storing localtime in\ntextual form without even recording the timezone.\n\n"},{"id":"541083","messageId":"xmqqwlyi4p6t.fsf@gitster.g","threadId":"65326","inReplyTo":"8e58c1263d15fb8dba8ce1d2866d369e938bf2b6.1775431990.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v3 4/5] t7700: test for promisor file content after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-07T18:10:02Z","receivedAt":"2026-04-07T18:10:08Z","isPatch":true,"body":"LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> Add tests that checks if the content of \".promisor\" files are correctly\n> copied inside the \".promisor\" files created by a repack.\n>\n> Signed-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n> ---\n>  t/t7700-repack.sh | 63 +++++++++++++++++++++++++++++++++++++++++++++++\n>  1 file changed, 63 insertions(+)\n>\n> diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh\n> index 63ef63fc50..89a2116641 100755\n> --- a/t/t7700-repack.sh\n> +++ b/t/t7700-repack.sh\n> @@ -904,4 +904,67 @@ test_expect_success 'pending objects are repacked appropriately' '\n>  \t)\n>  '\n>  \n> +test_expect_success 'check one .promisor file content after repack' '\n> +\ttest_when_finished rm -rf prom_test &&\n> +\tgit init prom_test &&\n> +\tpath=prom_test/.git/objects/pack &&\n> +\n> +\t(\n> +\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n> +\t\t\n> +\t\t# Simulate .promisor file by creating it manually\n> +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> +\t\toid=$(git -C prom_test rev-parse HEAD) &&\n> +\t\techo \"$oid ref\" >$prom &&\n> +\n> +\t\t# Save the current .promisor content, repack, and check if correct\n> +\t\tprom_before_repack=$(cat $prom) &&\n> +\t\tgit -C prom_test repack -a -d &&\n> +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> +\t\t# $prom should contain \"$prom_before_repack <date>\"\n> +\t\ttest_grep \"$prom_before_repack \" $prom &&\n> +\n> +\t\t# Save the current .promisor content, repack, and check if correct\n> +\t\tcat $prom >prom_before_repack &&\n> +\t\tgit -C prom_test repack -a -d &&\n> +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> +\t\t# $prom should be exactly the same as prom_before_repack\n> +\t\ttest_cmp prom_before_repack $prom\n> +\t)\n> +'\n> +\n> +test_expect_success 'check multiple .promisor file content after repack' '\n> +...\n> +\n> +\t\t# Repack, and check if correct compared to previous saved .promisor content\n> +\t\tgit -C prom_test repack -a -d &&\n> +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> +\t\t# $prom should contain \"$prom_before_repack1 <date>\" & \"$prom_before_repack2 <date>\"\n> +\t\ttest_grep \"$prom_before_repack1 \" $prom &&\n> +\t\ttest_grep \"$prom_before_repack2 \" $prom &&\n\nThis test seems to be flakey.\n\n  https://github.com/git/git/actions/runs/24095497271/job/70292906676#step:10:5274\n\nshows that $prom gets two file names, and because test_grep is\nexpecting a single source to grep inside, the first test_grep\nfails.\n\n"},{"id":"541098","messageId":"adV7Nq2fqA-Rz3JK@lorenzo-VM","threadId":"65326","inReplyTo":"xmqqeckraiwh.fsf@gitster.g","subject":"Re: [GSoC PATCH v3 2/5] pack-write: add helper to fill promisor file after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-07T21:46:30Z","receivedAt":"2026-04-07T21:46:34Z","isPatch":true,"body":"On Mon, Apr 06, 2026 at 02:17:02PM -0700, Junio C Hamano wrote:\n> Also strbuf_split*() is a bad API.  Unless you need all the parts[]\n> strbuf instances all editable at the same time, an array of strbuf\n> is a data structure that is way overkill.  Splitting into string-list\n> may make it more palatable, I think.\n> \n> We even went through a series of patches (and follow-up effort by\n> other contributors) [*] to rewrite callers that unnecessarily call\n> strbuf_split*().\n> \n> [References]\n>   https://lore.kernel.org/git/20250731225433.4028872-1-gitster@pobox.com/\n>   https://lore.kernel.org/git/cover.1761217100.git.belkid98@gmail.com/\n\nMhm makes perfect sense. I will rewrite it using `string_list`. Thanks!\n\nLorenzo\n"},{"id":"541099","messageId":"adV8iP668rgxK-9k@lorenzo-VM","threadId":"65326","inReplyTo":"xmqqy0iz7clt.fsf@gitster.g","subject":"Re: [GSoC PATCH v3 2/5] pack-write: add helper to fill promisor file after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-07T21:52:08Z","receivedAt":"2026-04-07T21:52:11Z","isPatch":true,"body":"On Mon, Apr 06, 2026 at 07:01:18PM -0700, Junio C Hamano wrote:\n> Lorenzo Pegorari <lorenzo.pegorari2002@gmail.com> writes:\n> > On Tue, Apr 07, 2026 at 01:22:16AM +0800, Tian Yuchen wrote:\n> >> On 4/6/26 08:24, LorenzoPegorari wrote:\n> >> > +\n> >> > +\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n> >> > +\t\t\tif (!parts[2]) {\n> >> > +\t\t\t\tstruct tm tm;\n> >> > +\t\t\t\tlocaltime_r(&source_stat.st_mtim.tv_sec, &tm),\n> >> \n> >> Typo.\n> >\n> > Ack.\n> \n> Not just an unintended use of comma operator, this is not portable\n> and breaks OSX build\n> \n>   https://github.com/git/git/actions/runs/24058681172/job/70170218891#step:4:213\n\nYeah, I was shocked that it compiled at all on my system with no issue\nwhatsoever.\n\n> >> > +\t\t\t\tstrbuf_addch(&line, ' ');\n> >> > +\t\t\t\tstrbuf_addftime(&line, \"%Y/%m/%d-%H:%M:%S\", &tm, 0, 0);\n> \n> I suspect that storing seconds since epoch as a large integer would\n> be simpler and much less error prone than storing localtime in\n> textual form without even recording the timezone.\n\nYeah, maybe for this kinda specific debugging info being less error\nprone is more important then \"looking good\". Will do that.\n\nThanks,\nLorenzo\n"},{"id":"541101","messageId":"xmqqbjfu2zsp.fsf@gitster.g","threadId":"65326","inReplyTo":"adV8iP668rgxK-9k@lorenzo-VM","subject":"Re: [GSoC PATCH v3 2/5] pack-write: add helper to fill promisor file after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-07T22:03:50Z","receivedAt":"2026-04-07T22:03:52Z","isPatch":true,"body":"Lorenzo Pegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> On Mon, Apr 06, 2026 at 07:01:18PM -0700, Junio C Hamano wrote:\n>> Lorenzo Pegorari <lorenzo.pegorari2002@gmail.com> writes:\n>> > On Tue, Apr 07, 2026 at 01:22:16AM +0800, Tian Yuchen wrote:\n>> >> On 4/6/26 08:24, LorenzoPegorari wrote:\n>> >> > +\n>> >> > +\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n>> >> > +\t\t\tif (!parts[2]) {\n>> >> > +\t\t\t\tstruct tm tm;\n>> >> > +\t\t\t\tlocaltime_r(&source_stat.st_mtim.tv_sec, &tm),\n>> >> \n>> >> Typo.\n>> >\n>> > Ack.\n>> \n>> Not just an unintended use of comma operator, this is not portable\n>> and breaks OSX build\n>> \n>>   https://github.com/git/git/actions/runs/24058681172/job/70170218891#step:4:213\n>\n> Yeah, I was shocked that it compiled at all on my system with no issue\n> whatsoever.\n>\n>> >> > +\t\t\t\tstrbuf_addch(&line, ' ');\n>> >> > +\t\t\t\tstrbuf_addftime(&line, \"%Y/%m/%d-%H:%M:%S\", &tm, 0, 0);\n>> \n>> I suspect that storing seconds since epoch as a large integer would\n>> be simpler and much less error prone than storing localtime in\n>> textual form without even recording the timezone.\n>\n> Yeah, maybe for this kinda specific debugging info being less error\n> prone is more important then \"looking good\". Will do that.\n>\n> Thanks,\n> Lorenzo\n\nBefore moving on, please fetch what I pushed out and see if\nSQUASH??? commit I made on top (you should be able to find it out of\n'seen') is any useful for your update.\n\nThanks.\n"},{"id":"541102","messageId":"adWABoOqHNkTTTyR@lorenzo-VM","threadId":"65326","inReplyTo":"xmqq8qazai3b.fsf@gitster.g","subject":"Re: [GSoC PATCH v3 2/5] pack-write: add helper to fill promisor file after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-07T22:07:02Z","receivedAt":"2026-04-07T22:07:06Z","isPatch":true,"body":"On Mon, Apr 06, 2026 at 02:34:32PM -0700, Junio C Hamano wrote:\n> LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n> \n> > A \".promisor\" file may contain ref names (and their associated hashes)\n> > that were fetched at the time the corresponding packfile was downloaded.\n> > This information is used for debugging reasons. This information is\n> > stored as lines structured like this: \"<oid> <ref>\".\n> >\n> > Create a `copy_promisor_content()` helper function that allows this\n> > debugging info to not be lost after a `repack`, by coping it inside a new\n> > \".promisor\" file.\n> \n> \"coping\" -> \"copying\"\n\nAck.\n\n> > The function logic is the following:\n> >  * Take all \".promisor\" files contained inside the given `repo`.\n> >  * Ignore those whose name is contained inside the given `strset\n> >    not_repacked_names`, which basically acts as a \"promisor ignorelist\"\n> >    (intended to be used for packfiles that have not been repacked).\n> >  * Read each line of the remaining \".promisor\" files, which can be:\n> >     * \"<oid> <ref>\" if the \".promisor\" file was never repacked. If so,\n> >       add the time at which the \".promisor\" file was last modified <time>\n> >       to the line to create the string: \"<oid> <ref> <time>\".\n> >     * \"<oid> <ref> <time>\" if the \".promisor\" file was repacked. If so,\n> >       don't modify it.\n> >  * Ignore the line if its <oid> is not present inside the\n> >    \"<packtmp>-<dest_hex>.idx\" file.\n> >  * If the destination file \"<packtmp>-<dest_hex>.promisor\" does not\n> >    already contain the line, append it to the file.\n> >\n> > The function assumes that the contents of all \".promisor\" files are\n> > correctly formed.\n> >\n> > The time of last data modification is used in place of the time of file\n> > creation, because the former is much easier to obtain than the latter\n> > one.\n> \n> The time of file creation is not recorded anywhere if you are\n> dealing with the usual UNIX filesystems (ctime is not creation\n> time), so it is not the issue of \"easier to obtain\".\n\nThat's what I found out during my researches, but I wasn't sure. Thanks\nfor confirming it.\n\n> The reason why this design chooses to add time is because in a\n> never-repacked .promisor file, the modification time of the file\n> itself can be used when you compare the entries in it with entries\n> in another .promisor file that did get repacked.  By having\n> timestamp, the debugger can tell at which time the refs at the\n> remote repository pointed at what object---the same ref may appear\n> twice in the same .promisor file and having timestamps would help\n> understanding what happened over time.\n\nExactly. I'll improve the commit message to better explain the utility\nof these timestamps.\n\nThanks,\nLorenzo\n\n"},{"id":"541107","messageId":"adWPLhGeo-4Mqfbh@lorenzo-VM","threadId":"65326","inReplyTo":"xmqqwlyi4p6t.fsf@gitster.g","subject":"Re: [GSoC PATCH v3 4/5] t7700: test for promisor file content after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-07T23:11:42Z","receivedAt":"2026-04-07T23:11:46Z","isPatch":true,"body":"On Tue, Apr 07, 2026 at 11:10:02AM -0700, Junio C Hamano wrote:\n> LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n> \n> > Add tests that checks if the content of \".promisor\" files are correctly\n> > copied inside the \".promisor\" files created by a repack.\n> >\n> > Signed-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n> > ---\n> >  t/t7700-repack.sh | 63 +++++++++++++++++++++++++++++++++++++++++++++++\n> >  1 file changed, 63 insertions(+)\n> >\n> > diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh\n> > index 63ef63fc50..89a2116641 100755\n> > --- a/t/t7700-repack.sh\n> > +++ b/t/t7700-repack.sh\n> > @@ -904,4 +904,67 @@ test_expect_success 'pending objects are repacked appropriately' '\n> >  \t)\n> >  '\n> >  \n> > +test_expect_success 'check one .promisor file content after repack' '\n> > +\ttest_when_finished rm -rf prom_test &&\n> > +\tgit init prom_test &&\n> > +\tpath=prom_test/.git/objects/pack &&\n> > +\n> > +\t(\n> > +\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n> > +\t\t\n> > +\t\t# Simulate .promisor file by creating it manually\n> > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> > +\t\toid=$(git -C prom_test rev-parse HEAD) &&\n> > +\t\techo \"$oid ref\" >$prom &&\n> > +\n> > +\t\t# Save the current .promisor content, repack, and check if correct\n> > +\t\tprom_before_repack=$(cat $prom) &&\n> > +\t\tgit -C prom_test repack -a -d &&\n> > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> > +\t\t# $prom should contain \"$prom_before_repack <date>\"\n> > +\t\ttest_grep \"$prom_before_repack \" $prom &&\n> > +\n> > +\t\t# Save the current .promisor content, repack, and check if correct\n> > +\t\tcat $prom >prom_before_repack &&\n> > +\t\tgit -C prom_test repack -a -d &&\n> > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> > +\t\t# $prom should be exactly the same as prom_before_repack\n> > +\t\ttest_cmp prom_before_repack $prom\n> > +\t)\n> > +'\n> > +\n> > +test_expect_success 'check multiple .promisor file content after repack' '\n> > +...\n> > +\n> > +\t\t# Repack, and check if correct compared to previous saved .promisor content\n> > +\t\tgit -C prom_test repack -a -d &&\n> > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> > +\t\t# $prom should contain \"$prom_before_repack1 <date>\" & \"$prom_before_repack2 <date>\"\n> > +\t\ttest_grep \"$prom_before_repack1 \" $prom &&\n> > +\t\ttest_grep \"$prom_before_repack2 \" $prom &&\n> \n> This test seems to be flakey.\n> \n>   https://github.com/git/git/actions/runs/24095497271/job/70292906676#step:10:5274\n> \n> shows that $prom gets two file names, and because test_grep is\n> expecting a single source to grep inside, the first test_grep\n> fails.\n\nUff yeah, I see.\n\nI also saw your other mail regarding the \"SQUASH???\" commit you made\n(inside the `seen` branch). I'm not so sure if it is useful to solve\nthis issue tho.\n\nIt looks like, for some reason, `repack -a` fails to repack everything\ninto a single pack, but I believe that `repack -a -f` should force it to\nrepack everything no matter what (I think??).\n"},{"id":"541108","messageId":"adWSMUZtlh7ct9bX@lorenzo-VM","threadId":"65326","inReplyTo":"xmqqldez9232.fsf@gitster.g","subject":"Re: [GSoC PATCH v3 4/5] t7700: test for promisor file content after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-07T23:28:39Z","receivedAt":"2026-04-07T23:28:45Z","isPatch":true,"body":"On Mon, Apr 06, 2026 at 03:05:37PM -0700, Junio C Hamano wrote:\n> LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n> \n> > +test_expect_success 'check one .promisor file content after repack' '\n> > +\ttest_when_finished rm -rf prom_test &&\n> > +\tgit init prom_test &&\n> > +\tpath=prom_test/.git/objects/pack &&\n> > +\n> > +\t(\n> > +\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n> > +\t\t\n> > +\t\t# Simulate .promisor file by creating it manually\n> > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> \n> So \"prom\" is a list of filenames; since $path does not have any\n> funny letters that interferes, later use of unquotd $prom will\n> list these files.  OK.\n\n`test_commit_bulk` creates a single \".pack\" file. We use `$prom` to\ncreate the associated \".promisor\" file.\n\n> > +\t\toid=$(git -C prom_test rev-parse HEAD) &&\n> > +\t\techo \"$oid ref\" >$prom &&\n> \n> Oh, not quite.  How are we guaranteeing that there is only one file\n> in the list of files in $prom?\n\nYes, because `test_commit_bulk` specifically creates a single pack.\n\n> In any case, quoting from Documentation/CodingGuidelines:\n> \n>  - Redirection operators should be written with space before, but no\n>    space after them.  In other words, write 'echo test >\"$file\"'\n>    instead of 'echo test> $file' or 'echo test > $file'.  Note that\n>    even though it is not required by POSIX to double-quote the\n>    redirection target in a variable (as shown above), our code does so\n>    because some versions of bash issue a warning without the quotes.\n> \n> \t(incorrect)\n> \tcat hello > world < universe\n> \techo hello >$world\n> \n> \t(correct)\n> \tcat hello >world <universe\n> \techo hello >\"$world\"\n\nAck.\n\n> > +\t\t# Save the current .promisor content, repack, and check if correct\n> > +\t\tprom_before_repack=$(cat $prom) &&\n> \n> This is misleading, unless you plan to update the early part of this\n> test to store a more realistic data in the $prom file.  Wouldn't it\n> be equivanent to\n> \n> \t\tprom_before_repack=\"$oid ref\" &&\n> \n> at this point?\n\nYeah, simply using \"$oid ref\" would be better at this point. One less\nconfusing variable.\n\n> > +\t\tgit -C prom_test repack -a -d &&\n> > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> \n> We expect that there is only one .pack and .promisor file.  Why\n> are we listing .pack and turning them to .promisor, instead of doing\n> \n> \t\tprom=$(ls $path/*.promisor) &&\n> \n> here?  Don't we expect that this \"repack\" to recreate .promisor file\n> as well (and if we do not see the file then we detected another bug,\n> which is a good thing)?\n\n100% correct. I just copied it from above without thinking enough. Will\ndo this. Thanks!\n\n> > +\t\t# $prom should contain \"$prom_before_repack <date>\"\n> > +\t\ttest_grep \"$prom_before_repack \" $prom &&\n> \n> I do not quite understand this test.  Ahh, OK.  We expect that there\n> was only a single entry in the original, because that is what we\n> placed in the original .promisor file.\n\nExactly.\n\n> Enclose $prom inside a pair of double quotes, as it is misleading\n> without.  I wasted a few minutes wondering where you are expecting\n> these possibly multiple promisor files from.\n\nWill do that.\n\n> > +\t\t# Save the current .promisor content, repack, and check if correct\n> > +\t\tcat $prom >prom_before_repack &&\n> \n> \t\tcp \"$prom\" prom_before_repack &&\n> \n> would be more standard.\n\nAck.\n\n> > +\t\tgit -C prom_test repack -a -d &&\n> > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> \n> The same comment about \"don't we know .promisor file should exist,\n> and shouldn't we check it directly?\" applies here.\n\nTrue. Ack.\n\n> > +\t\t# $prom should be exactly the same as prom_before_repack\n> > +\t\ttest_cmp prom_before_repack $prom\n> > +\t)\n> > +'\n> \n> \n> Same comment applies from earlier to the next test piece, I suspect.\n> Let's take a look.\n> \n> > +\n> > +test_expect_success 'check multiple .promisor file content after repack' '\n> > +\ttest_when_finished rm -rf prom_test &&\n> > +\tgit init prom_test &&\n> > +\tpath=prom_test/.git/objects/pack &&\n> > +\n> > +\t(\n> > +\t\t# Create 2 packs and simulate .promisor files by creating them manually\n> > +\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n> > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> > +\t\toid=$(git -C prom_test rev-parse HEAD) &&\n> > +\t\techo \"$oid ref\" >$prom &&\n> > +\t\tprom_before_repack1=$(cat $prom) &&\n> \n> > +\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n> > +\t\tprom=$(ls -t $path/*.pack | head -n 1 | sed \"s/\\.pack/.promisor/\") &&\n> \n> Do not pipe head into sed, as sed is more capable.\n> \n> \t\tls -t $path/*.pack | sed \"s/.../;q\"\n\nAck.\n\n> > +\t\toid=$(git -C prom_test rev-parse HEAD) &&\n> > +\t\techo \"$oid ref\" >$prom &&\n> > +\t\tprom_before_repack2=$(cat $prom) &&\n> \n> But more importantly, this may become a source of flakiness.  These\n> two packfiles are likely to have very close timestamps and depending\n> on the timing, how heavily loaded the machine is, and the phase of\n> the moon, it is not guaranteed that you'd grab the name of the new\n> pack.  Instead of sorting by type or getting the first one, which\n> would not work reliably, grab both and filter out what you already\n> have seen.\n\nMakes sense. Ack\n\n> > +\t\t# Repack, and check if correct compared to previous saved .promisor content\n> > +\t\tgit -C prom_test repack -a -d &&\n> > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> > +\t\t# $prom should contain \"$prom_before_repack1 <date>\" & \"$prom_before_repack2 <date>\"\n> > +\t\ttest_grep \"$prom_before_repack1 \" $prom &&\n> > +\t\ttest_grep \"$prom_before_repack2 \" $prom &&\n> > +\n> > +\t\t# Save the current .promisor content, repack, and check if correct\n> > +\t\tcat $prom >prom_before_repack &&\n> > +\t\tgit -C prom_test repack -a -d &&\n> > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> > +\t\t# $prom should be exactly the same as prom_before_repack\n> > +\t\ttest_cmp prom_before_repack $prom\n> > +\t)\n> > +'\n> > +\n> >  test_done\n\nThank you so much Junio,\nLorenzo\n"},{"id":"541112","messageId":"adWjoIENaTDDHerq@lorenzo-VM","threadId":"65326","inReplyTo":"adWPLhGeo-4Mqfbh@lorenzo-VM","subject":"Re: [GSoC PATCH v3 4/5] t7700: test for promisor file content after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-08T00:38:56Z","receivedAt":"2026-04-08T00:39:01Z","isPatch":true,"body":"On Wed, Apr 08, 2026 at 01:11:42AM +0200, Lorenzo Pegorari wrote:\n> On Tue, Apr 07, 2026 at 11:10:02AM -0700, Junio C Hamano wrote:\n> > LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n> > \n> > > Add tests that checks if the content of \".promisor\" files are correctly\n> > > copied inside the \".promisor\" files created by a repack.\n> > >\n> > > Signed-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n> > > ---\n> > >  t/t7700-repack.sh | 63 +++++++++++++++++++++++++++++++++++++++++++++++\n> > >  1 file changed, 63 insertions(+)\n> > >\n> > > diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh\n> > > index 63ef63fc50..89a2116641 100755\n> > > --- a/t/t7700-repack.sh\n> > > +++ b/t/t7700-repack.sh\n> > > @@ -904,4 +904,67 @@ test_expect_success 'pending objects are repacked appropriately' '\n> > >  \t)\n> > >  '\n> > >  \n> > > +test_expect_success 'check one .promisor file content after repack' '\n> > > +\ttest_when_finished rm -rf prom_test &&\n> > > +\tgit init prom_test &&\n> > > +\tpath=prom_test/.git/objects/pack &&\n> > > +\n> > > +\t(\n> > > +\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n> > > +\t\t\n> > > +\t\t# Simulate .promisor file by creating it manually\n> > > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> > > +\t\toid=$(git -C prom_test rev-parse HEAD) &&\n> > > +\t\techo \"$oid ref\" >$prom &&\n> > > +\n> > > +\t\t# Save the current .promisor content, repack, and check if correct\n> > > +\t\tprom_before_repack=$(cat $prom) &&\n> > > +\t\tgit -C prom_test repack -a -d &&\n> > > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> > > +\t\t# $prom should contain \"$prom_before_repack <date>\"\n> > > +\t\ttest_grep \"$prom_before_repack \" $prom &&\n> > > +\n> > > +\t\t# Save the current .promisor content, repack, and check if correct\n> > > +\t\tcat $prom >prom_before_repack &&\n> > > +\t\tgit -C prom_test repack -a -d &&\n> > > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> > > +\t\t# $prom should be exactly the same as prom_before_repack\n> > > +\t\ttest_cmp prom_before_repack $prom\n> > > +\t)\n> > > +'\n> > > +\n> > > +test_expect_success 'check multiple .promisor file content after repack' '\n> > > +...\n> > > +\n> > > +\t\t# Repack, and check if correct compared to previous saved .promisor content\n> > > +\t\tgit -C prom_test repack -a -d &&\n> > > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> > > +\t\t# $prom should contain \"$prom_before_repack1 <date>\" & \"$prom_before_repack2 <date>\"\n> > > +\t\ttest_grep \"$prom_before_repack1 \" $prom &&\n> > > +\t\ttest_grep \"$prom_before_repack2 \" $prom &&\n> > \n> > This test seems to be flakey.\n> > \n> >   https://github.com/git/git/actions/runs/24095497271/job/70292906676#step:10:5274\n> > \n> > shows that $prom gets two file names, and because test_grep is\n> > expecting a single source to grep inside, the first test_grep\n> > fails.\n> \n> Uff yeah, I see.\n> \n> I also saw your other mail regarding the \"SQUASH???\" commit you made\n> (inside the `seen` branch). I'm not so sure if it is useful to solve\n> this issue tho.\n> \n> It looks like, for some reason, `repack -a` fails to repack everything\n> into a single pack, but I believe that `repack -a -f` should force it to\n> repack everything no matter what (I think??).\n\nOk, I think that I have fixed it. Forked the repo and tested multiple\ntimes with the GitHub Actions-based CI. Issue doesn't appear anymore.\n\nThank you super much for the help Junio.\n\nMay I ask you for a tiny bit more of your time to answer the following\nquestion that I originally asked in the cover letter:\n\n>The \"CodingGuidelines\" explicitly state that:\n>  \"A C file must directly include the header files that declare the\n>   functions and the types it uses, except for the functions and types\n>   that are made available to it by including one of the header files\n>   it must include by the previous rule\"\n> where \"the previous rule\" is (if I understand correctly), the one related\n> to \"<git-compat-util.h>\". From what I understand then, I should have\n> added an include for \"strmap.h\" (which is needed for `strset`), correct?\n> And if I am correct, shouldn't \"strbuf.h\", \"hash.h\", \"odb.h\",\n> \"string-list.h\" and \"strvec.h\" also be included?\n\nThank you so much in advance. Pretty sure the next patch version will be\nthe last one.\n\nLorenzo\n"},{"id":"541368","messageId":"cover.1775832056.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775431990.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v4 0/5] preserve promisor files content after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T15:01:26Z","receivedAt":"2026-04-10T15:01:33Z","isPatch":true,"body":"The goal of this patch is to solve the NEEDSWORK comment added by\n5374a290 (fetch-pack: write fetched refs to .promisor, 14/10/2019). This\nis done by adding a helper function that takes the content of all\n.promisor files in the `repository`, and copies it inside the first\n.promisor file created by the repack.\n\nAlso, I added a comment explaining what is the purpose of the content of\nthe .promisor files, since this wasn't explained anywhere (I found\ninformation regarding this only in the message of the previously cited\ncommit).\n\nFinally, I added some tests to \"t7700-repack.sh\" and\n\"t7703-repack-geometric.sh\" that check if the content of .promisor files\nare correctly copied into the .promisor files created by a repack.\n\n\nIf Eric Sunshine, Tian Yuchen (for the patch 2/5 \"pack-write: add helper\nto fill promisor file after repack\") and Junio Hamano (for all patches)\nwant to be added with a `<Reviewed-by>` tag, please let me know (and, of\ncourse, thanks a lot for the help)!\n\n\nQUESTION:\nThe \"CodingGuidelines\" explicitly state that:\n \"A C file must directly include the header files that declare the\n  functions and the types it uses, except for the functions and types\n  that are made available to it by including one of the header files\n  it must include by the previous rule\"\nwhere \"the previous rule\" is (if I understand correctly), the one related\nto \"<git-compat-util.h>\". From what I understand then, I should have\nadded an include for \"strmap.h\" (which is needed for `strset`), correct?\nAnd if I am correct, shouldn't \"strbuf.h\", \"hash.h\", \"odb.h\",\n\"string-list.h\" and \"strvec.h\" also be included?\n\n\nV4 DIFF:\n * `copy_promisor_content()` now prints timestamps in Unix time format.\n * `copy_promisor_content()` now doesn't use a list of `strbuf`, but\n   instead uses the more lightweight `string_list`.\n * improved the tests.\n * fixed issue (that showed up in the GitHub Actions-based CI) where\n   sometimes the 2 packs created in the second new test inside \"t7700\"\n   were not both repacked into a single new pack. \n\n\nLorenzoPegorari (5):\n  pack-write: add explanation to promisor file content\n  pack-write: add helper to fill promisor file after repack\n  repack-promisor: preserve content of promisor files after repack\n  t7700: test for promisor file content after repack\n  t7703: test for promisor file content after geometric repack\n\n Documentation/git-repack.adoc |   4 +-\n pack-write.c                  |   9 +++\n repack-promisor.c             | 146 +++++++++++++++++++++++++++++++---\n t/t7700-repack.sh             |  60 ++++++++++++++\n t/t7703-repack-geometric.sh   |  33 ++++++++\n 5 files changed, 237 insertions(+), 15 deletions(-)\n\nRange-diff against v3:\n1:  eb1964dca8 = 1:  b4990fcdf0 pack-write: add explanation to promisor file content\n2:  3cd1542919 ! 2:  34c4e79311 pack-write: add helper to fill promisor file after repack\n    @@ Commit message\n         stored as lines structured like this: \"<oid> <ref>\".\n     \n         Create a `copy_promisor_content()` helper function that allows this\n    -    debugging info to not be lost after a `repack`, by coping it inside a new\n    -    \".promisor\" file.\n    +    debugging info to not be lost after a `repack`, by copying it inside a\n    +    new \".promisor\" file.\n     \n         The function logic is the following:\n          * Take all \".promisor\" files contained inside the given `repo`.\n    @@ Commit message\n            (intended to be used for packfiles that have not been repacked).\n          * Read each line of the remaining \".promisor\" files, which can be:\n             * \"<oid> <ref>\" if the \".promisor\" file was never repacked. If so,\n    -          add the time at which the \".promisor\" file was last modified <time>\n    -          to the line to create the string: \"<oid> <ref> <time>\".\n    +          add the time (in Unix time) at which the \".promisor\" file was last\n    +          modified <time> to the line, to obtain: \"<oid> <ref> <time>\".\n             * \"<oid> <ref> <time>\" if the \".promisor\" file was repacked. If so,\n               don't modify it.\n          * Ignore the line if its <oid> is not present inside the\n    @@ Commit message\n         The function assumes that the contents of all \".promisor\" files are\n         correctly formed.\n     \n    -    The time of last data modification is used in place of the time of file\n    -    creation, because the former is much easier to obtain than the latter\n    -    one.\n    +    The time of last data modification, for never-repacked \".promisor\" file,\n    +    can be used when comparing the entries in it with entries in another\n    +    \".promisor\" file that did get repacked. With these timestamps, the\n    +    debugger will be able to tell at which time the refs at the remote\n    +    repository pointed at what object. Also, when looking at already\n    +    repacked \".promisor\" files, the same ref may appear multiple times, and\n    +    having timestamps will help understanding what happened over time.\n     \n         Signed-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n     \n    @@ repack-promisor.c: static int write_oid(const struct object_id *oid,\n     + * Each line of a never repacked .promisor file is: \"<oid> <ref>\" (as described\n     + * in the write_promisor_file() function).\n     + * After a repack, the copied lines will be: \"<oid> <ref> <time>\", where <time>\n    -+ * is the time at which the .promisor file was last modified.\n    ++ * is the time (in Unix time) at which the .promisor file was last modified.\n     + * Only the lines whose <oid> is present inside \"<packtmp>-<dest_hex>.idx\" will\n     + * be copied.\n     + * The contents of all .promisor files are assumed to be correctly formed.\n    @@ repack-promisor.c: static int write_oid(const struct object_id *oid,\n     +\t\tsource = xfopen(source_promisor_name.buf, \"r\");\n     +\n     +\t\twhile (strbuf_getline(&line, source) != EOF) {\n    -+\t\t\tstruct strbuf **parts;\n    ++\t\t\tstruct string_list line_sections = STRING_LIST_INIT_DUP;\n     +\t\t\tstruct object_id oid;\n     +\n     +\t\t\t/* Split line into <oid>, <ref> and <time> (if <time> exists) */\n    -+\t\t\tparts = strbuf_split_max(&line, ' ', 3);\n    ++\t\t\tstring_list_split(&line_sections, line.buf, \" \", 3);\n     +\n     +\t\t\t/* Ignore the lines where <oid> doesn't appear in the dest_pack */\n    -+\t\t\tstrbuf_rtrim(parts[0]);\n    -+\t\t\tget_oid_hex_algop(parts[0]->buf, &oid, repo->hash_algo);\n    -+\t\t\tif (!find_pack_entry_one(&oid, dest_pack))\n    ++\t\t\tget_oid_hex_algop(line_sections.items[0].string, &oid, repo->hash_algo);\n    ++\t\t\tif (!find_pack_entry_one(&oid, dest_pack)) {\n    ++\t\t\t\tstring_list_clear(&line_sections, 0);\n     +\t\t\t\tcontinue;\n    ++\t\t\t}\n     +\n     +\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n    -+\t\t\tif (!parts[2]) {\n    -+\t\t\t\tstruct tm tm;\n    -+\t\t\t\tlocaltime_r(&source_stat.st_mtim.tv_sec, &tm),\n    -+\t\t\t\tstrbuf_addch(&line, ' ');\n    -+\t\t\t\tstrbuf_addftime(&line, \"%Y/%m/%d-%H:%M:%S\", &tm, 0, 0);\n    -+\t\t\t}\n    ++\t\t\tif (line_sections.nr < 3)\n    ++\t\t\t\tstrbuf_addf(&line, \" %lld\", (long long int)source_stat.st_mtim.tv_sec);\n     +\n     +\t\t\t/*\n     +\t\t\t * Add the finalized line to dest_to_write and dest_content if it\n    @@ repack-promisor.c: static int write_oid(const struct object_id *oid,\n     +\t\t\t\tstrbuf_addch(&dest_to_write, '\\n');\n     +\t\t\t}\n     +\n    -+\t\t\tstrbuf_list_free(parts);\n    ++\t\t\tstring_list_clear(&line_sections, 0);\n     +\t\t}\n     +\n     +\t\terr = ferror(source);\n3:  c16b1198fd = 3:  72ef2378b9 repack-promisor: preserve content of promisor files after repack\n4:  8e58c1263d ! 4:  0aceaed480 t7700: test for promisor file content after repack\n    @@ Metadata\n      ## Commit message ##\n         t7700: test for promisor file content after repack\n     \n    -    Add tests that checks if the content of \".promisor\" files are correctly\n    +    Add tests that check if the content of \".promisor\" files are correctly\n         copied inside the \".promisor\" files created by a repack.\n     \n    +    The `-f` flag is used when repacking to ensure that all the packs\n    +    (created with `test_commit_bulk`) are repacked into a single new pack.\n    +\n         Signed-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n     \n      ## t/t7700-repack.sh ##\n    @@ t/t7700-repack.sh: test_expect_success 'pending objects are repacked appropriate\n      '\n      \n     +test_expect_success 'check one .promisor file content after repack' '\n    -+\ttest_when_finished rm -rf prom_test &&\n    ++\ttest_when_finished rm -rf prom_test prom_before_repack &&\n     +\tgit init prom_test &&\n     +\tpath=prom_test/.git/objects/pack &&\n     +\n     +\t(\n    -+\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n    -+\t\t\n    ++\t\ttest_commit_bulk -C prom_test 1 &&\n    ++\n     +\t\t# Simulate .promisor file by creating it manually\n     +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n     +\t\toid=$(git -C prom_test rev-parse HEAD) &&\n    -+\t\techo \"$oid ref\" >$prom &&\n    ++\t\techo \"$oid ref\" >\"$prom\" &&\n     +\n    -+\t\t# Save the current .promisor content, repack, and check if correct\n    -+\t\tprom_before_repack=$(cat $prom) &&\n    -+\t\tgit -C prom_test repack -a -d &&\n    -+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n    -+\t\t# $prom should contain \"$prom_before_repack <date>\"\n    -+\t\ttest_grep \"$prom_before_repack \" $prom &&\n    ++\t\t# Repack, and check if correct\n    ++\t\tgit -C prom_test repack -a -d -f &&\n    ++\t\tprom=$(ls $path/*.promisor) &&\n    ++\t\t# $prom should contain \"$oid ref <time>\"\n    ++\t\ttest_grep \"$prom_before_repack \" \"$prom\" &&\n     +\n     +\t\t# Save the current .promisor content, repack, and check if correct\n    -+\t\tcat $prom >prom_before_repack &&\n    -+\t\tgit -C prom_test repack -a -d &&\n    -+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n    ++\t\tcp \"$prom\" prom_before_repack &&\n    ++\t\tgit -C prom_test repack -a -d -f &&\n    ++\t\tprom=$(ls $path/*.promisor) &&\n     +\t\t# $prom should be exactly the same as prom_before_repack\n    -+\t\ttest_cmp prom_before_repack $prom\n    ++\t\ttest_cmp prom_before_repack \"$prom\"\n     +\t)\n     +'\n     +\n     +test_expect_success 'check multiple .promisor file content after repack' '\n    -+\ttest_when_finished rm -rf prom_test &&\n    ++\ttest_when_finished rm -rf prom_test prom_before_repack &&\n     +\tgit init prom_test &&\n     +\tpath=prom_test/.git/objects/pack &&\n     +\n     +\t(\n     +\t\t# Create 2 packs and simulate .promisor files by creating them manually\n    -+\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n    ++\t\ttest_commit_bulk -C prom_test 1 &&\n     +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n    -+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n    -+\t\techo \"$oid ref\" >$prom &&\n    -+\t\tprom_before_repack1=$(cat $prom) &&\n    -+\t\ttest_commit_bulk -C prom_test --start=1 1 &&\n    -+\t\tprom=$(ls -t $path/*.pack | head -n 1 | sed \"s/\\.pack/.promisor/\") &&\n    -+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n    -+\t\techo \"$oid ref\" >$prom &&\n    -+\t\tprom_before_repack2=$(cat $prom) &&\n    ++\t\toid1=$(git -C prom_test rev-parse HEAD) &&\n    ++\t\techo \"$oid1 ref1\" >\"$prom\" &&\n    ++\t\ttest_commit_bulk -C prom_test 1 &&\n    ++\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom|d\") &&\n    ++\t\toid2=$(git -C prom_test rev-parse HEAD) &&\n    ++\t\techo \"$oid2 ref2\" >\"$prom\" &&\n     +\n    -+\t\t# Repack, and check if correct compared to previous saved .promisor content\n    -+\t\tgit -C prom_test repack -a -d &&\n    -+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n    -+\t\t# $prom should contain \"$prom_before_repack1 <date>\" & \"$prom_before_repack2 <date>\"\n    -+\t\ttest_grep \"$prom_before_repack1 \" $prom &&\n    -+\t\ttest_grep \"$prom_before_repack2 \" $prom &&\n    ++\t\t# Repack, and check if correct\n    ++\t\tgit -C prom_test repack -a -d -f &&\n    ++\t\tprom=$(ls $path/*.promisor) &&\n    ++\t\t# $prom should contain \"$oid1 ref1 <time>\" & \"$oid2 ref2 <time>\"\n    ++\t\ttest_grep \"$oid1 ref1 \" \"$prom\" &&\n    ++\t\ttest_grep \"$oid2 ref2 \" \"$prom\" &&\n     +\n     +\t\t# Save the current .promisor content, repack, and check if correct\n    -+\t\tcat $prom >prom_before_repack &&\n    -+\t\tgit -C prom_test repack -a -d &&\n    -+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n    ++\t\tcp \"$prom\" prom_before_repack &&\n    ++\t\tgit -C prom_test repack -a -d -f &&\n    ++\t\tprom=$(ls $path/*.promisor) &&\n     +\t\t# $prom should be exactly the same as prom_before_repack\n    -+\t\ttest_cmp prom_before_repack $prom\n    ++\t\ttest_cmp prom_before_repack \"$prom\"\n     +\t)\n     +'\n     +\n5:  1533fa96a8 ! 5:  d9f6341481 t7703: test for promisor file content after geometric repack\n    @@ t/t7703-repack-geometric.sh: test_expect_success 'geometric repack works with pr\n     +\t(\n     +\t\t# Create 2 packs with 3 objs each, and manually create .promisor files\n     +\t\ttest_commit_bulk -C prom_test --start=1 1 &&  # 3 objects\n    -+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n    -+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n    -+\t\techo \"$oid ref\" >$prom &&\n    -+\t\tprom_before_repack1=$(cat $prom) &&\n    ++\t\tprom1=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n    ++\t\toid1=$(git -C prom_test rev-parse HEAD) &&\n    ++\t\techo \"$oid1 ref1\" >\"$prom1\" &&\n     +\t\ttest_commit_bulk -C prom_test --start=2 1 &&  # 3 objects\n    -+\t\tprom=$(ls -t $path/*.pack | head -n 1 | sed \"s/\\.pack/.promisor/\") &&\n    -+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n    -+\t\techo \"$oid ref\" >$prom &&\n    -+\t\tprom_before_repack2=$(cat $prom) &&\n    ++\t\tprom2=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom1|d\") &&\n    ++\t\toid2=$(git -C prom_test rev-parse HEAD) &&\n    ++\t\techo \"$oid2 ref2\" >\"$prom2\" &&\n     +\n    -+\t\t# Create 2 packs with 12 and 24 objs, and manually create .promisor files\n    ++\t\t# Create 1 pack with 12 objs, and manually create .promisor file\n     +\t\ttest_commit_bulk -C prom_test --start=3 4 &&  # 12 objects\n    -+\t\tprom=$(ls -t $path/*.pack | head -n 1 | sed \"s/\\.pack/.promisor/\") &&\n    -+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n    -+\t\techo \"$oid ref\" >$prom &&\n    -+\t\tprom_before_repack3=$(cat $prom) &&\n    -+\t\ttest_commit_bulk -C prom_test --start=7 8 &&  # 24 objects\n    -+\t\tprom=$(ls -t $path/*.pack | head -n 1 | sed \"s/\\.pack/.promisor/\") &&\n    -+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n    -+\t\techo \"$oid ref\" >$prom &&\n    -+\t\tprom_before_repack4=$(cat $prom) &&\n    ++\t\tprom3=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom1|d; \\|$prom2|d\") &&\n    ++\t\toid3=$(git -C prom_test rev-parse HEAD) &&\n    ++\t\techo \"$oid3 ref3\" >\"$prom3\" &&\n     +\n    -+\t\t# Geometric repack, and check if correct compared to previous saved .promisor content\n    ++\t\t# Geometric repack, and check if correct\n     +\t\tgit -C prom_test repack --geometric 2 -d &&\n    -+\t\tprom=$(ls -t $path/*.pack | head -n 1 | sed \"s/\\.pack/.promisor/\") &&\n    -+\t\t# $prom should have repacked only the first 2 small packs, so it should only contain\n    -+\t\t# the following: \"$prom_before_repack1 <date>\" & \"$prom_before_repack2 <date>\"\n    -+\t\ttest_grep \"$prom_before_repack1 \" $prom &&\n    -+\t\ttest_grep \"$prom_before_repack2 \" $prom &&\n    -+\t\ttest_grep ! $prom_before_repack3 $prom &&\n    -+\t\ttest_grep ! $prom_before_repack4 $prom\n    ++\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom3|d\") &&\n    ++\t\t# $prom should have repacked only the first 2 small packs, so it should only\n    ++\t\t# contain the following: \"$oid1 ref1 <time>\" & \"$oid2 ref2 <time>\"\n    ++\t\ttest_grep \"$oid1 ref1 \" \"$prom\" &&\n    ++\t\ttest_grep \"$oid2 ref2 \" \"$prom\" &&\n    ++\t\ttest_grep ! \"$oid3 ref3\" \"$prom\"\n     +\t)\n     +'\n     +\n-- \n2.53.0.585.ge25071d955\n\n"},{"id":"541369","messageId":"b4990fcdf0759dc8f0e95b497daee54e08b09e0d.1775832056.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775832056.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v4 1/5] pack-write: add explanation to promisor file content","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T15:02:35Z","receivedAt":"2026-04-10T15:02:40Z","isPatch":true,"body":"In the entire codebase there is no explanation as to why the \".promisor\"\nfiles may contain the ref names (and their associated hashes) that were\nfetched at the time the corresponding packfile was downloaded.\n\nAs explained in the log message of commit 5374a290 (fetch-pack: write\nfetched refs to .promisor, 2019-10-14), where this loop originally came\nfrom, these ref names (and associated hashes) are not used for anything\nin the production, but are solely there to help debugging.\n\nExplain this in a new comment.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n pack-write.c | 9 +++++++++\n 1 file changed, 9 insertions(+)\n\ndiff --git a/pack-write.c b/pack-write.c\nindex 83eaf88541..b8ab9510ff 100644\n--- a/pack-write.c\n+++ b/pack-write.c\n@@ -603,6 +603,15 @@ void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_\n \tint i, err;\n \tFILE *output = xfopen(promisor_name, \"w\");\n \n+\t/*\n+\t * Write in the .promisor file the ref names and associated hashes,\n+\t * obtained by fetch-pack, at the point of generation of the\n+\t * corresponding packfile. These pieces of info are only used to make\n+\t * it easier to debug issues with partial clones, as we can identify\n+\t * what refs (and their associated hashes) were fetched at the time\n+\t * the packfile was downloaded, and if necessary, compare those hashes\n+\t * against what the promisor remote reports now.\n+\t */\n \tfor (i = 0; i < nr_sought; i++)\n \t\tfprintf(output, \"%s %s\\n\", oid_to_hex(&sought[i]->old_oid),\n \t\t\tsought[i]->name);\n-- \n2.53.0.585.ge25071d955\n\n"},{"id":"541371","messageId":"34c4e793113f22c393a6196d6e99a96d78cc3ab9.1775832056.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775832056.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v4 2/5] pack-write: add helper to fill promisor file after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T15:02:51Z","receivedAt":"2026-04-10T15:02:57Z","isPatch":true,"body":"A \".promisor\" file may contain ref names (and their associated hashes)\nthat were fetched at the time the corresponding packfile was downloaded.\nThis information is used for debugging reasons. This information is\nstored as lines structured like this: \"<oid> <ref>\".\n\nCreate a `copy_promisor_content()` helper function that allows this\ndebugging info to not be lost after a `repack`, by copying it inside a\nnew \".promisor\" file.\n\nThe function logic is the following:\n * Take all \".promisor\" files contained inside the given `repo`.\n * Ignore those whose name is contained inside the given `strset\n   not_repacked_names`, which basically acts as a \"promisor ignorelist\"\n   (intended to be used for packfiles that have not been repacked).\n * Read each line of the remaining \".promisor\" files, which can be:\n    * \"<oid> <ref>\" if the \".promisor\" file was never repacked. If so,\n      add the time (in Unix time) at which the \".promisor\" file was last\n      modified <time> to the line, to obtain: \"<oid> <ref> <time>\".\n    * \"<oid> <ref> <time>\" if the \".promisor\" file was repacked. If so,\n      don't modify it.\n * Ignore the line if its <oid> is not present inside the\n   \"<packtmp>-<dest_hex>.idx\" file.\n * If the destination file \"<packtmp>-<dest_hex>.promisor\" does not\n   already contain the line, append it to the file.\n\nThe function assumes that the contents of all \".promisor\" files are\ncorrectly formed.\n\nThe time of last data modification, for never-repacked \".promisor\" file,\ncan be used when comparing the entries in it with entries in another\n\".promisor\" file that did get repacked. With these timestamps, the\ndebugger will be able to tell at which time the refs at the remote\nrepository pointed at what object. Also, when looking at already\nrepacked \".promisor\" files, the same ref may appear multiple times, and\nhaving timestamps will help understanding what happened over time.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n repack-promisor.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 116 insertions(+)\n\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 90318ce150..797314d7b9 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -34,6 +34,122 @@ static int write_oid(const struct object_id *oid,\n \treturn 0;\n }\n \n+/*\n+ * Go through all .promisor files contained in repo (excluding those whose name\n+ * appears in not_repacked_basenames, which acts as a ignorelist), and copies\n+ * their content inside the destination file \"<packtmp>-<dest_hex>.promisor\".\n+ * Each line of a never repacked .promisor file is: \"<oid> <ref>\" (as described\n+ * in the write_promisor_file() function).\n+ * After a repack, the copied lines will be: \"<oid> <ref> <time>\", where <time>\n+ * is the time (in Unix time) at which the .promisor file was last modified.\n+ * Only the lines whose <oid> is present inside \"<packtmp>-<dest_hex>.idx\" will\n+ * be copied.\n+ * The contents of all .promisor files are assumed to be correctly formed.\n+ */\n+static void copy_promisor_content(struct repository *repo,\n+\t\t\t\t\t      const char *dest_hex,\n+\t\t\t\t\t      const char *packtmp,\n+\t\t\t\t\t      struct strset *not_repacked_basenames)\n+{\n+\tchar *dest_idx_name;\n+\tchar *dest_promisor_name;\n+\tFILE *dest;\n+\tstruct strset dest_content = STRSET_INIT;\n+\tstruct strbuf dest_to_write = STRBUF_INIT;\n+\tstruct strbuf source_promisor_name = STRBUF_INIT;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tstruct object_id dest_oid;\n+\tstruct packed_git *dest_pack, *p;\n+\tint err;\n+\n+\tdest_idx_name = mkpathdup(\"%s-%s.idx\", packtmp, dest_hex);\n+\tget_oid_hex_algop(dest_hex, &dest_oid, repo->hash_algo);\n+\tdest_pack = parse_pack_index(repo, dest_oid.hash, dest_idx_name);\n+\n+\t/* Open the .promisor dest file, and fill dest_content with its content */\n+\tdest_promisor_name = mkpathdup(\"%s-%s.promisor\", packtmp, dest_hex);\n+\tdest = xfopen(dest_promisor_name, \"r+\");\n+\twhile (strbuf_getline(&line, dest) != EOF)\n+\t\tstrset_add(&dest_content, line.buf);\n+\n+\trepo_for_each_pack(repo, p) {\n+\t\tFILE *source;\n+\t\tstruct stat source_stat;\n+\n+\t\tif (!p->pack_promisor)\n+\t\t\tcontinue;\n+\n+\t\tif (not_repacked_basenames &&\n+\t\t\tstrset_contains(not_repacked_basenames, pack_basename(p)))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&source_promisor_name);\n+\t\tstrbuf_addstr(&source_promisor_name, p->pack_name);\n+\t\tstrbuf_strip_suffix(&source_promisor_name, \".pack\");\n+\t\tstrbuf_addstr(&source_promisor_name, \".promisor\");\n+\n+\t\tif (stat(source_promisor_name.buf, &source_stat))\n+\t\t\tdie(_(\"File not found: %s\"), source_promisor_name.buf);\n+\n+\t\tsource = xfopen(source_promisor_name.buf, \"r\");\n+\n+\t\twhile (strbuf_getline(&line, source) != EOF) {\n+\t\t\tstruct string_list line_sections = STRING_LIST_INIT_DUP;\n+\t\t\tstruct object_id oid;\n+\n+\t\t\t/* Split line into <oid>, <ref> and <time> (if <time> exists) */\n+\t\t\tstring_list_split(&line_sections, line.buf, \" \", 3);\n+\n+\t\t\t/* Ignore the lines where <oid> doesn't appear in the dest_pack */\n+\t\t\tget_oid_hex_algop(line_sections.items[0].string, &oid, repo->hash_algo);\n+\t\t\tif (!find_pack_entry_one(&oid, dest_pack)) {\n+\t\t\t\tstring_list_clear(&line_sections, 0);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n+\t\t\tif (line_sections.nr < 3)\n+\t\t\t\tstrbuf_addf(&line, \" %lld\", (long long int)source_stat.st_mtim.tv_sec);\n+\n+\t\t\t/*\n+\t\t\t * Add the finalized line to dest_to_write and dest_content if it\n+\t\t\t * wasn't already present inside dest_content\n+\t\t\t */\n+\t\t\tif (strset_add(&dest_content, line.buf)) {\n+\t\t\t\tstrbuf_addbuf(&dest_to_write, &line);\n+\t\t\t\tstrbuf_addch(&dest_to_write, '\\n');\n+\t\t\t}\n+\n+\t\t\tstring_list_clear(&line_sections, 0);\n+\t\t}\n+\n+\t\terr = ferror(source);\n+\t\terr |= fclose(source);\n+\t\tif (err)\n+\t\t\tdie(_(\"Could not read '%s' promisor file\"), source_promisor_name.buf);\n+\t}\n+\n+\t/* If dest_to_write is not empty, then there are new lines to append */\n+\tif (dest_to_write.len) {\n+\t\tif (fseek(dest, 0L, SEEK_END))\n+\t\t\tdie_errno(_(\"fseek failed\"));\n+\t\tfprintf(dest, \"%s\", dest_to_write.buf);\n+\t}\n+\n+\terr = ferror(dest);\n+\terr |= fclose(dest);\n+\tif (err)\n+\t\tdie(_(\"Could not write '%s' promisor file\"), dest_promisor_name);\n+\n+\tclose_pack_index(dest_pack);\n+\tfree(dest_idx_name);\n+\tfree(dest_promisor_name);\n+\tstrset_clear(&dest_content);\n+\tstrbuf_release(&dest_to_write);\n+\tstrbuf_release(&source_promisor_name);\n+\tstrbuf_release(&line);\n+}\n+\n static void finish_repacking_promisor_objects(struct repository *repo,\n \t\t\t\t\t      struct child_process *cmd,\n \t\t\t\t\t      struct string_list *names,\n-- \n2.53.0.585.ge25071d955\n\n"},{"id":"541372","messageId":"72ef2378b9cd50941b1c0c485bcbacb820e798c9.1775832056.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775832056.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v4 3/5] repack-promisor: preserve content of promisor files after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T15:03:56Z","receivedAt":"2026-04-10T15:04:01Z","isPatch":true,"body":"When a repack involving promisor packfiles happens, the new \".promisor\"\nfile is created empty, losing all the debug info that might be present\ninside the \".promisor\" files before the repack.\n\nUse the \"copy_promisor_content()\" function created previously to preserve\nthe contents of all \".promisor\" files inside the first \".promisor\" file\ncreated by the repack.\n\nFor geometric repacking, we have to create a `strset` that contains the\nbasenames of all excluded packs. For \"normal\" repacking this is not\nnecessary, since there should be no excluded packs.\n\nAlso, update the documentation accordingly.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n Documentation/git-repack.adoc |  4 ++--\n repack-promisor.c             | 30 +++++++++++++++++-------------\n 2 files changed, 19 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/git-repack.adoc b/Documentation/git-repack.adoc\nindex 673ce91083..33d3c8afbd 100644\n--- a/Documentation/git-repack.adoc\n+++ b/Documentation/git-repack.adoc\n@@ -45,8 +45,8 @@ other objects in that pack they already have locally.\n +\n Promisor packfiles are repacked separately: if there are packfiles that\n have an associated \".promisor\" file, these packfiles will be repacked\n-into another separate pack, and an empty \".promisor\" file corresponding\n-to the new separate pack will be written.\n+into another separate pack, and a \".promisor\" file corresponding to the\n+new separate pack will be written (with arbitrary contents).\n \n -A::\n \tSame as `-a`, unless `-d` is used.  Then any unreachable\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 797314d7b9..0c373c8820 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -153,7 +153,8 @@ static void copy_promisor_content(struct repository *repo,\n static void finish_repacking_promisor_objects(struct repository *repo,\n \t\t\t\t\t      struct child_process *cmd,\n \t\t\t\t\t      struct string_list *names,\n-\t\t\t\t\t      const char *packtmp)\n+\t\t\t\t\t      const char *packtmp,\n+\t\t\t\t\t      struct strset *not_repacked_basenames)\n {\n \tstruct strbuf line = STRBUF_INIT;\n \tFILE *out;\n@@ -171,19 +172,15 @@ static void finish_repacking_promisor_objects(struct repository *repo,\n \n \t\t/*\n \t\t * pack-objects creates the .pack and .idx files, but not the\n-\t\t * .promisor file. Create the .promisor file, which is empty.\n-\t\t *\n-\t\t * NEEDSWORK: fetch-pack sometimes generates non-empty\n-\t\t * .promisor files containing the ref names and associated\n-\t\t * hashes at the point of generation of the corresponding\n-\t\t * packfile, but this would not preserve their contents. Maybe\n-\t\t * concatenate the contents of all .promisor files instead of\n-\t\t * just creating a new empty file.\n+\t\t * .promisor file. Create the .promisor file.\n \t\t */\n \t\tpromisor_name = mkpathdup(\"%s-%s.promisor\", packtmp,\n \t\t\t\t\t  line.buf);\n \t\twrite_promisor_file(promisor_name, NULL, 0);\n \n+\t\t/* Now let's fill the content of the newly created .promisor file */\n+\t\tcopy_promisor_content(repo, line.buf, packtmp, not_repacked_basenames);\n+\n \t\titem->util = generated_pack_populate(item->string, packtmp);\n \n \t\tfree(promisor_name);\n@@ -223,7 +220,7 @@ void repack_promisor_objects(struct repository *repo,\n \t\treturn;\n \t}\n \n-\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp);\n+\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp, NULL);\n }\n \n void pack_geometry_repack_promisors(struct repository *repo,\n@@ -234,6 +231,7 @@ void pack_geometry_repack_promisors(struct repository *repo,\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tFILE *in;\n+\tstruct strset not_repacked_basenames = STRSET_INIT;\n \n \tif (!geometry->promisor_split)\n \t\treturn;\n@@ -247,9 +245,15 @@ void pack_geometry_repack_promisors(struct repository *repo,\n \tin = xfdopen(cmd.in, \"w\");\n \tfor (size_t i = 0; i < geometry->promisor_split; i++)\n \t\tfprintf(in, \"%s\\n\", pack_basename(geometry->promisor_pack[i]));\n-\tfor (size_t i = geometry->promisor_split; i < geometry->promisor_pack_nr; i++)\n-\t\tfprintf(in, \"^%s\\n\", pack_basename(geometry->promisor_pack[i]));\n+\tfor (size_t i = geometry->promisor_split; i < geometry->promisor_pack_nr; i++) {\n+\t\tconst char *name = pack_basename(geometry->promisor_pack[i]);\n+\t\tfprintf(in, \"^%s\\n\", name);\n+\t\tstrset_add(&not_repacked_basenames, name);\n+\t}\n \tfclose(in);\n \n-\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp);\n+\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp,\n+\t\t\tstrset_get_size(&not_repacked_basenames) ? &not_repacked_basenames : NULL);\n+\n+\tstrset_clear(&not_repacked_basenames);\n }\n-- \n2.53.0.585.ge25071d955\n\n"},{"id":"541373","messageId":"0aceaed480b990abfd981dbdd696ea53a2238e1a.1775832056.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775832056.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v4 4/5] t7700: test for promisor file content after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T15:04:21Z","receivedAt":"2026-04-10T15:04:25Z","isPatch":true,"body":"Add tests that check if the content of \".promisor\" files are correctly\ncopied inside the \".promisor\" files created by a repack.\n\nThe `-f` flag is used when repacking to ensure that all the packs\n(created with `test_commit_bulk`) are repacked into a single new pack.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n t/t7700-repack.sh | 60 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 60 insertions(+)\n\ndiff --git a/t/t7700-repack.sh b/t/t7700-repack.sh\nindex 63ef63fc50..186a931ea7 100755\n--- a/t/t7700-repack.sh\n+++ b/t/t7700-repack.sh\n@@ -904,4 +904,64 @@ test_expect_success 'pending objects are repacked appropriately' '\n \t)\n '\n \n+test_expect_success 'check one .promisor file content after repack' '\n+\ttest_when_finished rm -rf prom_test prom_before_repack &&\n+\tgit init prom_test &&\n+\tpath=prom_test/.git/objects/pack &&\n+\n+\t(\n+\t\ttest_commit_bulk -C prom_test 1 &&\n+\n+\t\t# Simulate .promisor file by creating it manually\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid ref\" >\"$prom\" &&\n+\n+\t\t# Repack, and check if correct\n+\t\tgit -C prom_test repack -a -d -f &&\n+\t\tprom=$(ls $path/*.promisor) &&\n+\t\t# $prom should contain \"$oid ref <time>\"\n+\t\ttest_grep \"$prom_before_repack \" \"$prom\" &&\n+\n+\t\t# Save the current .promisor content, repack, and check if correct\n+\t\tcp \"$prom\" prom_before_repack &&\n+\t\tgit -C prom_test repack -a -d -f &&\n+\t\tprom=$(ls $path/*.promisor) &&\n+\t\t# $prom should be exactly the same as prom_before_repack\n+\t\ttest_cmp prom_before_repack \"$prom\"\n+\t)\n+'\n+\n+test_expect_success 'check multiple .promisor file content after repack' '\n+\ttest_when_finished rm -rf prom_test prom_before_repack &&\n+\tgit init prom_test &&\n+\tpath=prom_test/.git/objects/pack &&\n+\n+\t(\n+\t\t# Create 2 packs and simulate .promisor files by creating them manually\n+\t\ttest_commit_bulk -C prom_test 1 &&\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n+\t\toid1=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid1 ref1\" >\"$prom\" &&\n+\t\ttest_commit_bulk -C prom_test 1 &&\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom|d\") &&\n+\t\toid2=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid2 ref2\" >\"$prom\" &&\n+\n+\t\t# Repack, and check if correct\n+\t\tgit -C prom_test repack -a -d -f &&\n+\t\tprom=$(ls $path/*.promisor) &&\n+\t\t# $prom should contain \"$oid1 ref1 <time>\" & \"$oid2 ref2 <time>\"\n+\t\ttest_grep \"$oid1 ref1 \" \"$prom\" &&\n+\t\ttest_grep \"$oid2 ref2 \" \"$prom\" &&\n+\n+\t\t# Save the current .promisor content, repack, and check if correct\n+\t\tcp \"$prom\" prom_before_repack &&\n+\t\tgit -C prom_test repack -a -d -f &&\n+\t\tprom=$(ls $path/*.promisor) &&\n+\t\t# $prom should be exactly the same as prom_before_repack\n+\t\ttest_cmp prom_before_repack \"$prom\"\n+\t)\n+'\n+\n test_done\n-- \n2.53.0.585.ge25071d955\n\n"},{"id":"541374","messageId":"d9f63414818419031c61a54cc3550506405478d5.1775832056.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775832056.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v4 5/5] t7703: test for promisor file content after geometric repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T15:04:37Z","receivedAt":"2026-04-10T15:04:42Z","isPatch":true,"body":"Add test that checks if the content of \".promisor\" files are correctly\ncopied inside the \".promisor\" files created by a geometric repack.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n t/t7703-repack-geometric.sh | 33 +++++++++++++++++++++++++++++++++\n 1 file changed, 33 insertions(+)\n\ndiff --git a/t/t7703-repack-geometric.sh b/t/t7703-repack-geometric.sh\nindex 04d5d8fc33..a8e3e6ae3f 100755\n--- a/t/t7703-repack-geometric.sh\n+++ b/t/t7703-repack-geometric.sh\n@@ -541,4 +541,37 @@ test_expect_success 'geometric repack works with promisor packs' '\n \t)\n '\n \n+test_expect_success 'check .promisor file content after geometric repack' '\n+\ttest_when_finished rm -rf prom_test &&\n+\tgit init prom_test &&\n+\tpath=prom_test/.git/objects/pack &&\n+\n+\t(\n+\t\t# Create 2 packs with 3 objs each, and manually create .promisor files\n+\t\ttest_commit_bulk -C prom_test --start=1 1 &&  # 3 objects\n+\t\tprom1=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n+\t\toid1=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid1 ref1\" >\"$prom1\" &&\n+\t\ttest_commit_bulk -C prom_test --start=2 1 &&  # 3 objects\n+\t\tprom2=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom1|d\") &&\n+\t\toid2=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid2 ref2\" >\"$prom2\" &&\n+\n+\t\t# Create 1 pack with 12 objs, and manually create .promisor file\n+\t\ttest_commit_bulk -C prom_test --start=3 4 &&  # 12 objects\n+\t\tprom3=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom1|d; \\|$prom2|d\") &&\n+\t\toid3=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid3 ref3\" >\"$prom3\" &&\n+\n+\t\t# Geometric repack, and check if correct\n+\t\tgit -C prom_test repack --geometric 2 -d &&\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom3|d\") &&\n+\t\t# $prom should have repacked only the first 2 small packs, so it should only\n+\t\t# contain the following: \"$oid1 ref1 <time>\" & \"$oid2 ref2 <time>\"\n+\t\ttest_grep \"$oid1 ref1 \" \"$prom\" &&\n+\t\ttest_grep \"$oid2 ref2 \" \"$prom\" &&\n+\t\ttest_grep ! \"$oid3 ref3\" \"$prom\"\n+\t)\n+'\n+\n test_done\n-- \n2.53.0.585.ge25071d955\n\n"},{"id":"541383","messageId":"xmqq1pgmrf4w.fsf@gitster.g","threadId":"65326","inReplyTo":"cover.1775832056.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v4 0/5] preserve promisor files content after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-10T15:47:43Z","receivedAt":"2026-04-10T15:47:46Z","isPatch":true,"body":"LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> QUESTION:\n> The \"CodingGuidelines\" explicitly state that:\n>  \"A C file must directly include the header files that declare the\n>   functions and the types it uses, except for the functions and types\n>   that are made available to it by including one of the header files\n>   it must include by the previous rule\"\n> where \"the previous rule\" is (if I understand correctly), the one related\n> to \"<git-compat-util.h>\". From what I understand then, I should have\n> added an include for \"strmap.h\" (which is needed for `strset`), correct?\n> And if I am correct, shouldn't \"strbuf.h\", \"hash.h\", \"odb.h\",\n> \"string-list.h\" and \"strvec.h\" also be included?\n\nIf you are using any of the facilities declared in these header\nfiles in your program, yes.\n\nIn practice many header files pull in other header files for\ndefinitions they themselves use.  For example, <X.h> that defines\n\"struct X\" may include <Y.h> for the definition of \"struct Y\" because\nthe former embeds an instance of the latter, instead of having a pointer\nto an on-heap instance of the latter.\n\nIf you use both \"struct X\" and \"struct Y\", your program may compile\nwith only <X.h> included without <Y.h> included in such a case, but\nthe guideline suggests against doing so, because it should not be\nrelied on.  The implementation of \"struct X\" may change in the\nfuture and stop depending on \"struct Y\", at which time <X.h> stops\nincluding <Y.h> itself, and your program would start failing to\nbuild, because you use \"struct Y\" but without including <Y.h>.\n\nBut in practice, use of strbuf is so widespread and the header is\nincluded in some other headers that do not need to, so your build\nmay happen to work without including <strbuf.h>, for example.\n"},{"id":"541384","messageId":"xmqqo6jqpzxv.fsf@gitster.g","threadId":"65326","inReplyTo":"34c4e793113f22c393a6196d6e99a96d78cc3ab9.1775832056.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v4 2/5] pack-write: add helper to fill promisor file after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-10T16:01:16Z","receivedAt":"2026-04-10T16:01:19Z","isPatch":true,"body":"LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> +\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n> +\t\t\tif (line_sections.nr < 3)\n> +\t\t\t\tstrbuf_addf(&line, \" %lld\", (long long int)source_stat.st_mtim.tv_sec);\n\nIt should be easy to see in the output of\n\n\t$ git grep -e '%lld' -e 'st_mtim\\.tv_sec'\n\nthat we do not use these constructs.\n\nWrite it like this instead\n\n\t\t\t\tstrbuf_addf(&line, \" %\" PRItime,\n\t\t\t\t\t    (timestamp_t)source_stat.st_mtime);\n\nexamples to mimick the uses of timestamp_t and PRItime are found in\nmany places; worktree.c, date.c, builtin/blame.c would give plenty.\n\n"},{"id":"541390","messageId":"adkmqcOYrHaBqok_@lorenzo-VM","threadId":"65326","inReplyTo":"xmqqo6jqpzxv.fsf@gitster.g","subject":"Re: [GSoC PATCH v4 2/5] pack-write: add helper to fill promisor file after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T16:34:49Z","receivedAt":"2026-04-10T16:34:53Z","isPatch":true,"body":"On Fri, Apr 10, 2026 at 09:01:16AM -0700, Junio C Hamano wrote:\n> LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n> \n> > +\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n> > +\t\t\tif (line_sections.nr < 3)\n> > +\t\t\t\tstrbuf_addf(&line, \" %lld\", (long long int)source_stat.st_mtim.tv_sec);\n> \n> It should be easy to see in the output of\n> \n> \t$ git grep -e '%lld' -e 'st_mtim\\.tv_sec'\n> \n> that we do not use these constructs.\n> \n> Write it like this instead\n> \n> \t\t\t\tstrbuf_addf(&line, \" %\" PRItime,\n> \t\t\t\t\t    (timestamp_t)source_stat.st_mtime);\n> \n> examples to mimick the uses of timestamp_t and PRItime are found in\n> many places; worktree.c, date.c, builtin/blame.c would give plenty.\n\nOh I see. I really need to get into this mechanism of constantly using\n`git grep` to look for examples for pretty much everything. Still\nlearning. Thanks Junio!\n\nLorenzo\n"},{"id":"541391","messageId":"adko0kvU5WX69GYQ@lorenzo-VM","threadId":"65326","inReplyTo":"xmqq1pgmrf4w.fsf@gitster.g","subject":"Re: [GSoC PATCH v4 0/5] preserve promisor files content after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T16:44:02Z","receivedAt":"2026-04-10T16:44:06Z","isPatch":true,"body":"On Fri, Apr 10, 2026 at 08:47:43AM -0700, Junio C Hamano wrote:\n> LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n> \n> > QUESTION:\n> > The \"CodingGuidelines\" explicitly state that:\n> >  \"A C file must directly include the header files that declare the\n> >   functions and the types it uses, except for the functions and types\n> >   that are made available to it by including one of the header files\n> >   it must include by the previous rule\"\n> > where \"the previous rule\" is (if I understand correctly), the one related\n> > to \"<git-compat-util.h>\". From what I understand then, I should have\n> > added an include for \"strmap.h\" (which is needed for `strset`), correct?\n> > And if I am correct, shouldn't \"strbuf.h\", \"hash.h\", \"odb.h\",\n> > \"string-list.h\" and \"strvec.h\" also be included?\n> \n> If you are using any of the facilities declared in these header\n> files in your program, yes.\n\nGot it.\n\n> In practice many header files pull in other header files for\n> definitions they themselves use.  For example, <X.h> that defines\n> \"struct X\" may include <Y.h> for the definition of \"struct Y\" because\n> the former embeds an instance of the latter, instead of having a pointer\n> to an on-heap instance of the latter.\n> \n> If you use both \"struct X\" and \"struct Y\", your program may compile\n> with only <X.h> included without <Y.h> included in such a case, but\n> the guideline suggests against doing so, because it should not be\n> relied on.  The implementation of \"struct X\" may change in the\n> future and stop depending on \"struct Y\", at which time <X.h> stops\n> including <Y.h> itself, and your program would start failing to\n> build, because you use \"struct Y\" but without including <Y.h>.\n> \n> But in practice, use of strbuf is so widespread and the header is\n> included in some other headers that do not need to, so your build\n> may happen to work without including <strbuf.h>, for example.\n\nYeah, I 100% understand this. I simply found it weird that there were\nmany missing headers, so I was scared that I was not understanding the\nguidelines.\n\nI will add a 6th patch that adds these missing headers, in order to\ncomply with the guidelines.\n\nThanks,\nLorenzo\n"},{"id":"541393","messageId":"xmqqzf3aofdj.fsf_-_@gitster.g","threadId":"65326","inReplyTo":"xmqqo6jqpzxv.fsf@gitster.g","subject":"[PATCH] CodingGuidelines: st_mtimespec vs st_mtim vs st_mtime","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-10T18:10:48Z","receivedAt":"2026-04-10T18:10:52Z","isPatch":true,"body":"Most unfortunately macOS does not support st_[amc]tim for timestamps\ndown to nanosecond resolution as POSIX systems.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/CodingGuidelines | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git c/Documentation/CodingGuidelines w/Documentation/CodingGuidelines\nindex 4992e52093..4e54139fd7 100644\n--- c/Documentation/CodingGuidelines\n+++ w/Documentation/CodingGuidelines\n@@ -693,6 +693,12 @@ For C programs:\n          char *dogs[] = ...;\n          walk_all_dogs(dogs);\n \n+ - For file timestamps, do not use \"st_mtim\" (and other timestamp\n+   members in \"struct stat\") unconditionally; not everybody is POSIX\n+   (grep for USE_ST_TIMESPEC).  If you only need timestamp in whole\n+   second resolution, \"st_mtime\" should work fine everywhere.\n+\n+\n For Perl programs:\n \n  - Most of the C guidelines above apply.\n"},{"id":"541409","messageId":"cover.1775861047.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775832056.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v5 0/6] preserve promisor files content after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T22:54:44Z","receivedAt":"2026-04-10T22:54:50Z","isPatch":true,"body":"The goal of this patch is to solve the NEEDSWORK comment added by\n5374a290 (fetch-pack: write fetched refs to .promisor, 14/10/2019). This\nis done by adding a helper function that takes the content of all\n.promisor files in the `repository`, and copies it inside the first\n.promisor file created by the repack.\n\nAlso, I added a comment explaining what is the purpose of the content of\nthe .promisor files, since this wasn't explained anywhere (I found\ninformation regarding this only in the message of the previously cited\ncommit).\n\nFinally, I added some tests to \"t7700-repack.sh\" and\n\"t7703-repack-geometric.sh\" that check if the content of .promisor files\nare correctly copied into the .promisor files created by a repack.\n\n\nIf Eric Sunshine, Tian Yuchen (for the patch 2/5 \"pack-write: add helper\nto fill promisor file after repack\") and Junio Hamano (for all patches)\nwant to be added with a `<Reviewed-by>` tag, please let me know (and, of\ncourse, thanks a lot for the help)!\n\n\nV5 DIFF:\n * fixed commit message (from `pack-write:` to `repack-promisor:`).\n * fixed timestamp `fprintf()` format.\n\n\nLorenzoPegorari (6):\n  pack-write: add explanation to promisor file content\n  repack-promisor add helper to fill promisor file after repack\n  repack-promisor: preserve content of promisor files after repack\n  t7700: test for promisor file content after repack\n  t7703: test for promisor file content after geometric repack\n  repack-promisor: add missing headers\n\n Documentation/git-repack.adoc |   4 +-\n pack-write.c                  |   9 ++\n repack-promisor.c             | 152 +++++++++++++++++++++++++++++++---\n t/t7700-repack.sh             |  60 ++++++++++++++\n t/t7703-repack-geometric.sh   |  33 ++++++++\n 5 files changed, 243 insertions(+), 15 deletions(-)\n\nRange-diff against v4:\n1:  b4990fcdf0 = 1:  b4990fcdf0 pack-write: add explanation to promisor file content\n2:  34c4e79311 ! 2:  3558bb3895 pack-write: add helper to fill promisor file after repack\n    @@ Metadata\n     Author: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n     \n      ## Commit message ##\n    -    pack-write: add helper to fill promisor file after repack\n    +    repack-promisor add helper to fill promisor file after repack\n     \n         A \".promisor\" file may contain ref names (and their associated hashes)\n         that were fetched at the time the corresponding packfile was downloaded.\n    @@ repack-promisor.c: static int write_oid(const struct object_id *oid,\n     +\n     +\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n     +\t\t\tif (line_sections.nr < 3)\n    -+\t\t\t\tstrbuf_addf(&line, \" %lld\", (long long int)source_stat.st_mtim.tv_sec);\n    ++\t\t\t\tstrbuf_addf(&line, \" %\" PRItime, (timestamp_t)source_stat.st_mtime);\n     +\n     +\t\t\t/*\n     +\t\t\t * Add the finalized line to dest_to_write and dest_content if it\n3:  72ef2378b9 = 3:  b483be7558 repack-promisor: preserve content of promisor files after repack\n4:  0aceaed480 = 4:  f631993c89 t7700: test for promisor file content after repack\n5:  d9f6341481 = 5:  ab307e68fe t7703: test for promisor file content after geometric repack\n-:  ---------- > 6:  e8720aaf12 repack-promisor: add missing headers\n-- \n2.53.0.584.ge8720aaf12\n\n"},{"id":"541410","messageId":"b4990fcdf0759dc8f0e95b497daee54e08b09e0d.1775861047.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775861047.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v5 1/6] pack-write: add explanation to promisor file content","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T22:54:59Z","receivedAt":"2026-04-10T22:55:04Z","isPatch":true,"body":"In the entire codebase there is no explanation as to why the \".promisor\"\nfiles may contain the ref names (and their associated hashes) that were\nfetched at the time the corresponding packfile was downloaded.\n\nAs explained in the log message of commit 5374a290 (fetch-pack: write\nfetched refs to .promisor, 2019-10-14), where this loop originally came\nfrom, these ref names (and associated hashes) are not used for anything\nin the production, but are solely there to help debugging.\n\nExplain this in a new comment.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n pack-write.c | 9 +++++++++\n 1 file changed, 9 insertions(+)\n\ndiff --git a/pack-write.c b/pack-write.c\nindex 83eaf88541..b8ab9510ff 100644\n--- a/pack-write.c\n+++ b/pack-write.c\n@@ -603,6 +603,15 @@ void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_\n \tint i, err;\n \tFILE *output = xfopen(promisor_name, \"w\");\n \n+\t/*\n+\t * Write in the .promisor file the ref names and associated hashes,\n+\t * obtained by fetch-pack, at the point of generation of the\n+\t * corresponding packfile. These pieces of info are only used to make\n+\t * it easier to debug issues with partial clones, as we can identify\n+\t * what refs (and their associated hashes) were fetched at the time\n+\t * the packfile was downloaded, and if necessary, compare those hashes\n+\t * against what the promisor remote reports now.\n+\t */\n \tfor (i = 0; i < nr_sought; i++)\n \t\tfprintf(output, \"%s %s\\n\", oid_to_hex(&sought[i]->old_oid),\n \t\t\tsought[i]->name);\n-- \n2.53.0.584.ge8720aaf12\n\n"},{"id":"541411","messageId":"3558bb38956b522c91057598db645eb42ffb48b2.1775861047.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775861047.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v5 2/6] repack-promisor add helper to fill promisor file after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T22:55:11Z","receivedAt":"2026-04-10T22:55:16Z","isPatch":true,"body":"A \".promisor\" file may contain ref names (and their associated hashes)\nthat were fetched at the time the corresponding packfile was downloaded.\nThis information is used for debugging reasons. This information is\nstored as lines structured like this: \"<oid> <ref>\".\n\nCreate a `copy_promisor_content()` helper function that allows this\ndebugging info to not be lost after a `repack`, by copying it inside a\nnew \".promisor\" file.\n\nThe function logic is the following:\n * Take all \".promisor\" files contained inside the given `repo`.\n * Ignore those whose name is contained inside the given `strset\n   not_repacked_names`, which basically acts as a \"promisor ignorelist\"\n   (intended to be used for packfiles that have not been repacked).\n * Read each line of the remaining \".promisor\" files, which can be:\n    * \"<oid> <ref>\" if the \".promisor\" file was never repacked. If so,\n      add the time (in Unix time) at which the \".promisor\" file was last\n      modified <time> to the line, to obtain: \"<oid> <ref> <time>\".\n    * \"<oid> <ref> <time>\" if the \".promisor\" file was repacked. If so,\n      don't modify it.\n * Ignore the line if its <oid> is not present inside the\n   \"<packtmp>-<dest_hex>.idx\" file.\n * If the destination file \"<packtmp>-<dest_hex>.promisor\" does not\n   already contain the line, append it to the file.\n\nThe function assumes that the contents of all \".promisor\" files are\ncorrectly formed.\n\nThe time of last data modification, for never-repacked \".promisor\" file,\ncan be used when comparing the entries in it with entries in another\n\".promisor\" file that did get repacked. With these timestamps, the\ndebugger will be able to tell at which time the refs at the remote\nrepository pointed at what object. Also, when looking at already\nrepacked \".promisor\" files, the same ref may appear multiple times, and\nhaving timestamps will help understanding what happened over time.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n repack-promisor.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 116 insertions(+)\n\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 90318ce150..72677f8c9f 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -34,6 +34,122 @@ static int write_oid(const struct object_id *oid,\n \treturn 0;\n }\n \n+/*\n+ * Go through all .promisor files contained in repo (excluding those whose name\n+ * appears in not_repacked_basenames, which acts as a ignorelist), and copies\n+ * their content inside the destination file \"<packtmp>-<dest_hex>.promisor\".\n+ * Each line of a never repacked .promisor file is: \"<oid> <ref>\" (as described\n+ * in the write_promisor_file() function).\n+ * After a repack, the copied lines will be: \"<oid> <ref> <time>\", where <time>\n+ * is the time (in Unix time) at which the .promisor file was last modified.\n+ * Only the lines whose <oid> is present inside \"<packtmp>-<dest_hex>.idx\" will\n+ * be copied.\n+ * The contents of all .promisor files are assumed to be correctly formed.\n+ */\n+static void copy_promisor_content(struct repository *repo,\n+\t\t\t\t\t      const char *dest_hex,\n+\t\t\t\t\t      const char *packtmp,\n+\t\t\t\t\t      struct strset *not_repacked_basenames)\n+{\n+\tchar *dest_idx_name;\n+\tchar *dest_promisor_name;\n+\tFILE *dest;\n+\tstruct strset dest_content = STRSET_INIT;\n+\tstruct strbuf dest_to_write = STRBUF_INIT;\n+\tstruct strbuf source_promisor_name = STRBUF_INIT;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tstruct object_id dest_oid;\n+\tstruct packed_git *dest_pack, *p;\n+\tint err;\n+\n+\tdest_idx_name = mkpathdup(\"%s-%s.idx\", packtmp, dest_hex);\n+\tget_oid_hex_algop(dest_hex, &dest_oid, repo->hash_algo);\n+\tdest_pack = parse_pack_index(repo, dest_oid.hash, dest_idx_name);\n+\n+\t/* Open the .promisor dest file, and fill dest_content with its content */\n+\tdest_promisor_name = mkpathdup(\"%s-%s.promisor\", packtmp, dest_hex);\n+\tdest = xfopen(dest_promisor_name, \"r+\");\n+\twhile (strbuf_getline(&line, dest) != EOF)\n+\t\tstrset_add(&dest_content, line.buf);\n+\n+\trepo_for_each_pack(repo, p) {\n+\t\tFILE *source;\n+\t\tstruct stat source_stat;\n+\n+\t\tif (!p->pack_promisor)\n+\t\t\tcontinue;\n+\n+\t\tif (not_repacked_basenames &&\n+\t\t\tstrset_contains(not_repacked_basenames, pack_basename(p)))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&source_promisor_name);\n+\t\tstrbuf_addstr(&source_promisor_name, p->pack_name);\n+\t\tstrbuf_strip_suffix(&source_promisor_name, \".pack\");\n+\t\tstrbuf_addstr(&source_promisor_name, \".promisor\");\n+\n+\t\tif (stat(source_promisor_name.buf, &source_stat))\n+\t\t\tdie(_(\"File not found: %s\"), source_promisor_name.buf);\n+\n+\t\tsource = xfopen(source_promisor_name.buf, \"r\");\n+\n+\t\twhile (strbuf_getline(&line, source) != EOF) {\n+\t\t\tstruct string_list line_sections = STRING_LIST_INIT_DUP;\n+\t\t\tstruct object_id oid;\n+\n+\t\t\t/* Split line into <oid>, <ref> and <time> (if <time> exists) */\n+\t\t\tstring_list_split(&line_sections, line.buf, \" \", 3);\n+\n+\t\t\t/* Ignore the lines where <oid> doesn't appear in the dest_pack */\n+\t\t\tget_oid_hex_algop(line_sections.items[0].string, &oid, repo->hash_algo);\n+\t\t\tif (!find_pack_entry_one(&oid, dest_pack)) {\n+\t\t\t\tstring_list_clear(&line_sections, 0);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n+\t\t\tif (line_sections.nr < 3)\n+\t\t\t\tstrbuf_addf(&line, \" %\" PRItime, (timestamp_t)source_stat.st_mtime);\n+\n+\t\t\t/*\n+\t\t\t * Add the finalized line to dest_to_write and dest_content if it\n+\t\t\t * wasn't already present inside dest_content\n+\t\t\t */\n+\t\t\tif (strset_add(&dest_content, line.buf)) {\n+\t\t\t\tstrbuf_addbuf(&dest_to_write, &line);\n+\t\t\t\tstrbuf_addch(&dest_to_write, '\\n');\n+\t\t\t}\n+\n+\t\t\tstring_list_clear(&line_sections, 0);\n+\t\t}\n+\n+\t\terr = ferror(source);\n+\t\terr |= fclose(source);\n+\t\tif (err)\n+\t\t\tdie(_(\"Could not read '%s' promisor file\"), source_promisor_name.buf);\n+\t}\n+\n+\t/* If dest_to_write is not empty, then there are new lines to append */\n+\tif (dest_to_write.len) {\n+\t\tif (fseek(dest, 0L, SEEK_END))\n+\t\t\tdie_errno(_(\"fseek failed\"));\n+\t\tfprintf(dest, \"%s\", dest_to_write.buf);\n+\t}\n+\n+\terr = ferror(dest);\n+\terr |= fclose(dest);\n+\tif (err)\n+\t\tdie(_(\"Could not write '%s' promisor file\"), dest_promisor_name);\n+\n+\tclose_pack_index(dest_pack);\n+\tfree(dest_idx_name);\n+\tfree(dest_promisor_name);\n+\tstrset_clear(&dest_content);\n+\tstrbuf_release(&dest_to_write);\n+\tstrbuf_release(&source_promisor_name);\n+\tstrbuf_release(&line);\n+}\n+\n static void finish_repacking_promisor_objects(struct repository *repo,\n \t\t\t\t\t      struct child_process *cmd,\n \t\t\t\t\t      struct string_list *names,\n-- \n2.53.0.584.ge8720aaf12\n\n"},{"id":"541412","messageId":"b483be7558f0efc1a6780b5cff13f4ccc3afd069.1775861047.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775861047.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v5 3/6] repack-promisor: preserve content of promisor files after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T22:55:48Z","receivedAt":"2026-04-10T22:55:52Z","isPatch":true,"body":"When a repack involving promisor packfiles happens, the new \".promisor\"\nfile is created empty, losing all the debug info that might be present\ninside the \".promisor\" files before the repack.\n\nUse the \"copy_promisor_content()\" function created previously to preserve\nthe contents of all \".promisor\" files inside the first \".promisor\" file\ncreated by the repack.\n\nFor geometric repacking, we have to create a `strset` that contains the\nbasenames of all excluded packs. For \"normal\" repacking this is not\nnecessary, since there should be no excluded packs.\n\nAlso, update the documentation accordingly.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n Documentation/git-repack.adoc |  4 ++--\n repack-promisor.c             | 30 +++++++++++++++++-------------\n 2 files changed, 19 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/git-repack.adoc b/Documentation/git-repack.adoc\nindex 673ce91083..33d3c8afbd 100644\n--- a/Documentation/git-repack.adoc\n+++ b/Documentation/git-repack.adoc\n@@ -45,8 +45,8 @@ other objects in that pack they already have locally.\n +\n Promisor packfiles are repacked separately: if there are packfiles that\n have an associated \".promisor\" file, these packfiles will be repacked\n-into another separate pack, and an empty \".promisor\" file corresponding\n-to the new separate pack will be written.\n+into another separate pack, and a \".promisor\" file corresponding to the\n+new separate pack will be written (with arbitrary contents).\n \n -A::\n \tSame as `-a`, unless `-d` is used.  Then any unreachable\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 72677f8c9f..6d9590cd4e 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -153,7 +153,8 @@ static void copy_promisor_content(struct repository *repo,\n static void finish_repacking_promisor_objects(struct repository *repo,\n \t\t\t\t\t      struct child_process *cmd,\n \t\t\t\t\t      struct string_list *names,\n-\t\t\t\t\t      const char *packtmp)\n+\t\t\t\t\t      const char *packtmp,\n+\t\t\t\t\t      struct strset *not_repacked_basenames)\n {\n \tstruct strbuf line = STRBUF_INIT;\n \tFILE *out;\n@@ -171,19 +172,15 @@ static void finish_repacking_promisor_objects(struct repository *repo,\n \n \t\t/*\n \t\t * pack-objects creates the .pack and .idx files, but not the\n-\t\t * .promisor file. Create the .promisor file, which is empty.\n-\t\t *\n-\t\t * NEEDSWORK: fetch-pack sometimes generates non-empty\n-\t\t * .promisor files containing the ref names and associated\n-\t\t * hashes at the point of generation of the corresponding\n-\t\t * packfile, but this would not preserve their contents. Maybe\n-\t\t * concatenate the contents of all .promisor files instead of\n-\t\t * just creating a new empty file.\n+\t\t * .promisor file. Create the .promisor file.\n \t\t */\n \t\tpromisor_name = mkpathdup(\"%s-%s.promisor\", packtmp,\n \t\t\t\t\t  line.buf);\n \t\twrite_promisor_file(promisor_name, NULL, 0);\n \n+\t\t/* Now let's fill the content of the newly created .promisor file */\n+\t\tcopy_promisor_content(repo, line.buf, packtmp, not_repacked_basenames);\n+\n \t\titem->util = generated_pack_populate(item->string, packtmp);\n \n \t\tfree(promisor_name);\n@@ -223,7 +220,7 @@ void repack_promisor_objects(struct repository *repo,\n \t\treturn;\n \t}\n \n-\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp);\n+\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp, NULL);\n }\n \n void pack_geometry_repack_promisors(struct repository *repo,\n@@ -234,6 +231,7 @@ void pack_geometry_repack_promisors(struct repository *repo,\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tFILE *in;\n+\tstruct strset not_repacked_basenames = STRSET_INIT;\n \n \tif (!geometry->promisor_split)\n \t\treturn;\n@@ -247,9 +245,15 @@ void pack_geometry_repack_promisors(struct repository *repo,\n \tin = xfdopen(cmd.in, \"w\");\n \tfor (size_t i = 0; i < geometry->promisor_split; i++)\n \t\tfprintf(in, \"%s\\n\", pack_basename(geometry->promisor_pack[i]));\n-\tfor (size_t i = geometry->promisor_split; i < geometry->promisor_pack_nr; i++)\n-\t\tfprintf(in, \"^%s\\n\", pack_basename(geometry->promisor_pack[i]));\n+\tfor (size_t i = geometry->promisor_split; i < geometry->promisor_pack_nr; i++) {\n+\t\tconst char *name = pack_basename(geometry->promisor_pack[i]);\n+\t\tfprintf(in, \"^%s\\n\", name);\n+\t\tstrset_add(&not_repacked_basenames, name);\n+\t}\n \tfclose(in);\n \n-\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp);\n+\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp,\n+\t\t\tstrset_get_size(&not_repacked_basenames) ? &not_repacked_basenames : NULL);\n+\n+\tstrset_clear(&not_repacked_basenames);\n }\n-- \n2.53.0.584.ge8720aaf12\n\n"},{"id":"541413","messageId":"f631993c89586a87d7e5f08ca4df1502d5228464.1775861047.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775861047.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v5 4/6] t7700: test for promisor file content after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T22:55:58Z","receivedAt":"2026-04-10T22:56:03Z","isPatch":true,"body":"Add tests that check if the content of \".promisor\" files are correctly\ncopied inside the \".promisor\" files created by a repack.\n\nThe `-f` flag is used when repacking to ensure that all the packs\n(created with `test_commit_bulk`) are repacked into a single new pack.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n t/t7700-repack.sh | 60 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 60 insertions(+)\n\ndiff --git a/t/t7700-repack.sh b/t/t7700-repack.sh\nindex 63ef63fc50..186a931ea7 100755\n--- a/t/t7700-repack.sh\n+++ b/t/t7700-repack.sh\n@@ -904,4 +904,64 @@ test_expect_success 'pending objects are repacked appropriately' '\n \t)\n '\n \n+test_expect_success 'check one .promisor file content after repack' '\n+\ttest_when_finished rm -rf prom_test prom_before_repack &&\n+\tgit init prom_test &&\n+\tpath=prom_test/.git/objects/pack &&\n+\n+\t(\n+\t\ttest_commit_bulk -C prom_test 1 &&\n+\n+\t\t# Simulate .promisor file by creating it manually\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid ref\" >\"$prom\" &&\n+\n+\t\t# Repack, and check if correct\n+\t\tgit -C prom_test repack -a -d -f &&\n+\t\tprom=$(ls $path/*.promisor) &&\n+\t\t# $prom should contain \"$oid ref <time>\"\n+\t\ttest_grep \"$prom_before_repack \" \"$prom\" &&\n+\n+\t\t# Save the current .promisor content, repack, and check if correct\n+\t\tcp \"$prom\" prom_before_repack &&\n+\t\tgit -C prom_test repack -a -d -f &&\n+\t\tprom=$(ls $path/*.promisor) &&\n+\t\t# $prom should be exactly the same as prom_before_repack\n+\t\ttest_cmp prom_before_repack \"$prom\"\n+\t)\n+'\n+\n+test_expect_success 'check multiple .promisor file content after repack' '\n+\ttest_when_finished rm -rf prom_test prom_before_repack &&\n+\tgit init prom_test &&\n+\tpath=prom_test/.git/objects/pack &&\n+\n+\t(\n+\t\t# Create 2 packs and simulate .promisor files by creating them manually\n+\t\ttest_commit_bulk -C prom_test 1 &&\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n+\t\toid1=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid1 ref1\" >\"$prom\" &&\n+\t\ttest_commit_bulk -C prom_test 1 &&\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom|d\") &&\n+\t\toid2=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid2 ref2\" >\"$prom\" &&\n+\n+\t\t# Repack, and check if correct\n+\t\tgit -C prom_test repack -a -d -f &&\n+\t\tprom=$(ls $path/*.promisor) &&\n+\t\t# $prom should contain \"$oid1 ref1 <time>\" & \"$oid2 ref2 <time>\"\n+\t\ttest_grep \"$oid1 ref1 \" \"$prom\" &&\n+\t\ttest_grep \"$oid2 ref2 \" \"$prom\" &&\n+\n+\t\t# Save the current .promisor content, repack, and check if correct\n+\t\tcp \"$prom\" prom_before_repack &&\n+\t\tgit -C prom_test repack -a -d -f &&\n+\t\tprom=$(ls $path/*.promisor) &&\n+\t\t# $prom should be exactly the same as prom_before_repack\n+\t\ttest_cmp prom_before_repack \"$prom\"\n+\t)\n+'\n+\n test_done\n-- \n2.53.0.584.ge8720aaf12\n\n"},{"id":"541414","messageId":"ab307e68feb9f84898dba748ef1f4598b07fc0e2.1775861047.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775861047.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v5 5/6] t7703: test for promisor file content after geometric repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T22:56:05Z","receivedAt":"2026-04-10T22:56:10Z","isPatch":true,"body":"Add test that checks if the content of \".promisor\" files are correctly\ncopied inside the \".promisor\" files created by a geometric repack.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n t/t7703-repack-geometric.sh | 33 +++++++++++++++++++++++++++++++++\n 1 file changed, 33 insertions(+)\n\ndiff --git a/t/t7703-repack-geometric.sh b/t/t7703-repack-geometric.sh\nindex 04d5d8fc33..a8e3e6ae3f 100755\n--- a/t/t7703-repack-geometric.sh\n+++ b/t/t7703-repack-geometric.sh\n@@ -541,4 +541,37 @@ test_expect_success 'geometric repack works with promisor packs' '\n \t)\n '\n \n+test_expect_success 'check .promisor file content after geometric repack' '\n+\ttest_when_finished rm -rf prom_test &&\n+\tgit init prom_test &&\n+\tpath=prom_test/.git/objects/pack &&\n+\n+\t(\n+\t\t# Create 2 packs with 3 objs each, and manually create .promisor files\n+\t\ttest_commit_bulk -C prom_test --start=1 1 &&  # 3 objects\n+\t\tprom1=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n+\t\toid1=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid1 ref1\" >\"$prom1\" &&\n+\t\ttest_commit_bulk -C prom_test --start=2 1 &&  # 3 objects\n+\t\tprom2=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom1|d\") &&\n+\t\toid2=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid2 ref2\" >\"$prom2\" &&\n+\n+\t\t# Create 1 pack with 12 objs, and manually create .promisor file\n+\t\ttest_commit_bulk -C prom_test --start=3 4 &&  # 12 objects\n+\t\tprom3=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom1|d; \\|$prom2|d\") &&\n+\t\toid3=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid3 ref3\" >\"$prom3\" &&\n+\n+\t\t# Geometric repack, and check if correct\n+\t\tgit -C prom_test repack --geometric 2 -d &&\n+\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom3|d\") &&\n+\t\t# $prom should have repacked only the first 2 small packs, so it should only\n+\t\t# contain the following: \"$oid1 ref1 <time>\" & \"$oid2 ref2 <time>\"\n+\t\ttest_grep \"$oid1 ref1 \" \"$prom\" &&\n+\t\ttest_grep \"$oid2 ref2 \" \"$prom\" &&\n+\t\ttest_grep ! \"$oid3 ref3\" \"$prom\"\n+\t)\n+'\n+\n test_done\n-- \n2.53.0.584.ge8720aaf12\n\n"},{"id":"541415","messageId":"e8720aaf120f9ed534c85c4b694e1441b12f261d.1775861047.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775861047.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v5 6/6] repack-promisor: add missing headers","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-10T22:56:42Z","receivedAt":"2026-04-10T22:56:47Z","isPatch":true,"body":"According to the coding guidelines, a C file must directly include the\nheader files that declare the facilities it uses.\n\nDirectly include these missing headers, in order to comply with the\ncoding guidelines.\n\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n repack-promisor.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 6d9590cd4e..26055212a3 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -1,11 +1,17 @@\n #include \"git-compat-util.h\"\n #include \"repack.h\"\n+#include \"hash.h\"\n #include \"hex.h\"\n+#include \"odb.h\"\n #include \"pack.h\"\n #include \"packfile.h\"\n #include \"path.h\"\n #include \"repository.h\"\n #include \"run-command.h\"\n+#include \"strbuf.h\"\n+#include \"string-list.h\"\n+#include \"strmap.h\"\n+#include \"strvec.h\"\n \n struct write_oid_context {\n \tstruct child_process *cmd;\n-- \n2.53.0.584.ge8720aaf12\n\n"},{"id":"541416","messageId":"xmqqse92mn5c.fsf@gitster.g","threadId":"65326","inReplyTo":"cover.1775832056.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v4 0/5] preserve promisor files content after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-10T23:05:51Z","receivedAt":"2026-04-10T23:05:54Z","isPatch":true,"body":"LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> The goal of this patch is to solve the NEEDSWORK comment added by\n> 5374a290 (fetch-pack: write fetched refs to .promisor, 14/10/2019). This\n> is done by adding a helper function that takes the content of all\n> .promisor files in the `repository`, and copies it inside the first\n> .promisor file created by the repack.\n> ...\n> V4 DIFF:\n>  * `copy_promisor_content()` now prints timestamps in Unix time format.\n>  * `copy_promisor_content()` now doesn't use a list of `strbuf`, but\n>    instead uses the more lightweight `string_list`.\n>  * improved the tests.\n>  * fixed issue (that showed up in the GitHub Actions-based CI) where\n>    sometimes the 2 packs created in the second new test inside \"t7700\"\n>    were not both repacked into a single new pack. \n\nWhen merged to the tip of 'seen' (with a fixup to use st_mtime where\nwe need only whole second precision, to avoid using st_mtim on\nplatforms that do not have it), this seems to break linux-leaks and\nlinux-reftable-leaks CI jobs (t0410, t5616, and t5710).\n\nThis topic standalone, without interaction with other things in\n'seen', breaks these three tests.\n\n  https://github.com/git/git/actions/runs/24267948258/job/70866907548\n\nThis is one commit directly on top of your topic that reduces CI\njobs down to just two \"leaks\" job, and removes many test scripts\nto leave only these three breaking ones.\n\n\nI managed to also locally reproduce this failure.  Here is how\n\n    $ cd t && t5710-*.sh -i -v\n\ndies:\n\nDirect leak of 285 byte(s) in 1 object(s) allocated from:\n    #0 0x55ce48ca1d4d in malloc (git+0x8cd4d) (BuildId: 1aa6efa30b2fc4772028a3dd31aba3ced49bf128)\n    #1 0x55ce48fed3f2 in do_xmalloc wrapper.c:55:8\n    #2 0x55ce48fed3b6 in xmalloc wrapper.c:76:9\n    #3 0x55ce48eed314 in alloc_packed_git packfile.c:306:25\n    #4 0x55ce48eed209 in parse_pack_index packfile.c:326:25\n    #5 0x55ce48f65f03 in copy_promisor_content repack-promisor.c:67:14\n    ...\n\n"},{"id":"541417","messageId":"xmqqo6jqmlzz.fsf@gitster.g","threadId":"65326","inReplyTo":"3558bb38956b522c91057598db645eb42ffb48b2.1775861047.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v5 2/6] repack-promisor add helper to fill promisor file after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-10T23:30:40Z","receivedAt":"2026-04-10T23:30:43Z","isPatch":true,"body":"LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> +\tdest_pack = parse_pack_index(repo, dest_oid.hash, dest_idx_name);\n\nparse_pack_index() has this comment:\n\n        /*\n         * Parse the pack idx file found at idx_path and create a packed_git struct\n         * which can be used with find_pack_entry_one().\n         *\n         * You probably don't want to use this function! It skips most of the normal\n         * sanity checks (including whether we even have the matching .pack file),\n         * and does not add the resulting packed_git struct to the internal list of\n         * packs. You probably want add_packed_git() instead.\n         */\n        struct packed_git *parse_pack_index(struct repository *r, unsigned char *sha1,\n                                            const char *idx_path);\n\nThe function can return NULL, but this caller does not seem to be\nprepared for it to return NULL (i.e., the loop introduced by the\nrepo_for_each_pack() macro we see below, nobody assumes dest_pack\ncould be NULL).\n\nBut what pack index file are we parsing here?  Isn't it already part\nof the running system that we should be able to find on the list of\npackfiles in the packfile store?  Is this because we lack \"find a\npackfile on this packfile store by name\" API, because what we want\nto find if each of <oid>s we have appear in the particular packfile\nor not, and packfile_list_find_oid() is not sufficiently precise (i.e.\n\"the object appears in one of the packfile on the list\" is not what\nwe want to know, \"the object appears in this particular packfile\" is)?\n\n\tPatrick CC'ed primarily because this part of the API and the\n\tdata structures have been reshuffled to add quite a lot of\n\tabstraction since I last looked at the area.\n\nAs close_pack_index(dest_pack) does not release resources held by\ndest_pack itself (even though the region of mmaped memory that is\npointed at by its index_data member is unmapped), I think that is\nwhere the memory leak is breaking the CI jobs (see my other message).\n\nBut I am not sure if the use of parse_pack_index() - close_pack_index()\nAPI is the right thing to use here.\n\n\n\n> +\t/* Open the .promisor dest file, and fill dest_content with its content */\n> +\tdest_promisor_name = mkpathdup(\"%s-%s.promisor\", packtmp, dest_hex);\n> +\tdest = xfopen(dest_promisor_name, \"r+\");\n> +\twhile (strbuf_getline(&line, dest) != EOF)\n> +\t\tstrset_add(&dest_content, line.buf);\n> +\n> +\trepo_for_each_pack(repo, p) {\n> +\t\tFILE *source;\n> +\t\tstruct stat source_stat;\n> +\n> +\t\tif (!p->pack_promisor)\n> +\t\t\tcontinue;\n> +\n> +\t\tif (not_repacked_basenames &&\n> +\t\t\tstrset_contains(not_repacked_basenames, pack_basename(p)))\n> +\t\t\tcontinue;\n> +\n> +\t\tstrbuf_reset(&source_promisor_name);\n> +\t\tstrbuf_addstr(&source_promisor_name, p->pack_name);\n> +\t\tstrbuf_strip_suffix(&source_promisor_name, \".pack\");\n> +\t\tstrbuf_addstr(&source_promisor_name, \".promisor\");\n> +\n> +\t\tif (stat(source_promisor_name.buf, &source_stat))\n> +\t\t\tdie(_(\"File not found: %s\"), source_promisor_name.buf);\n> +\n> +\t\tsource = xfopen(source_promisor_name.buf, \"r\");\n> +\n> +\t\twhile (strbuf_getline(&line, source) != EOF) {\n> +\t\t\tstruct string_list line_sections = STRING_LIST_INIT_DUP;\n> +\t\t\tstruct object_id oid;\n> +\n> +\t\t\t/* Split line into <oid>, <ref> and <time> (if <time> exists) */\n> +\t\t\tstring_list_split(&line_sections, line.buf, \" \", 3);\n> +\n> +\t\t\t/* Ignore the lines where <oid> doesn't appear in the dest_pack */\n> +\t\t\tget_oid_hex_algop(line_sections.items[0].string, &oid, repo->hash_algo);\n> +\t\t\tif (!find_pack_entry_one(&oid, dest_pack)) {\n> +\t\t\t\tstring_list_clear(&line_sections, 0);\n> +\t\t\t\tcontinue;\n> +\t\t\t}\n> +\n> +\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n> +\t\t\tif (line_sections.nr < 3)\n> +\t\t\t\tstrbuf_addf(&line, \" %\" PRItime, (timestamp_t)source_stat.st_mtime);\n> +\n> +\t\t\t/*\n> +\t\t\t * Add the finalized line to dest_to_write and dest_content if it\n> +\t\t\t * wasn't already present inside dest_content\n> +\t\t\t */\n> +\t\t\tif (strset_add(&dest_content, line.buf)) {\n> +\t\t\t\tstrbuf_addbuf(&dest_to_write, &line);\n> +\t\t\t\tstrbuf_addch(&dest_to_write, '\\n');\n> +\t\t\t}\n> +\n> +\t\t\tstring_list_clear(&line_sections, 0);\n> +\t\t}\n> +\n> +\t\terr = ferror(source);\n> +\t\terr |= fclose(source);\n> +\t\tif (err)\n> +\t\t\tdie(_(\"Could not read '%s' promisor file\"), source_promisor_name.buf);\n> +\t}\n> +\n> +\t/* If dest_to_write is not empty, then there are new lines to append */\n> +\tif (dest_to_write.len) {\n> +\t\tif (fseek(dest, 0L, SEEK_END))\n> +\t\t\tdie_errno(_(\"fseek failed\"));\n> +\t\tfprintf(dest, \"%s\", dest_to_write.buf);\n> +\t}\n> +\n> +\terr = ferror(dest);\n> +\terr |= fclose(dest);\n> +\tif (err)\n> +\t\tdie(_(\"Could not write '%s' promisor file\"), dest_promisor_name);\n> +\n> +\tclose_pack_index(dest_pack);\n> +\tfree(dest_idx_name);\n> +\tfree(dest_promisor_name);\n> +\tstrset_clear(&dest_content);\n> +\tstrbuf_release(&dest_to_write);\n> +\tstrbuf_release(&source_promisor_name);\n> +\tstrbuf_release(&line);\n> +}\n> +\n>  static void finish_repacking_promisor_objects(struct repository *repo,\n>  \t\t\t\t\t      struct child_process *cmd,\n>  \t\t\t\t\t      struct string_list *names,\n"},{"id":"541418","messageId":"admq7TA9sEPjskO5@lorenzo-VM","threadId":"65326","inReplyTo":"xmqqo6jqmlzz.fsf@gitster.g","subject":"Re: [GSoC PATCH v5 2/6] repack-promisor add helper to fill promisor file after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-11T01:59:09Z","receivedAt":"2026-04-11T01:59:14Z","isPatch":true,"body":"On Fri, Apr 10, 2026 at 04:30:40PM -0700, Junio C Hamano wrote:\n> LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n> \n> > +\tdest_pack = parse_pack_index(repo, dest_oid.hash, dest_idx_name);\n> \n> parse_pack_index() has this comment:\n> \n>         /*\n>          * Parse the pack idx file found at idx_path and create a packed_git struct\n>          * which can be used with find_pack_entry_one().\n>          *\n>          * You probably don't want to use this function! It skips most of the normal\n>          * sanity checks (including whether we even have the matching .pack file),\n>          * and does not add the resulting packed_git struct to the internal list of\n>          * packs. You probably want add_packed_git() instead.\n>          */\n>         struct packed_git *parse_pack_index(struct repository *r, unsigned char *sha1,\n>                                             const char *idx_path);\n> \n> The function can return NULL, but this caller does not seem to be\n> prepared for it to return NULL (i.e., the loop introduced by the\n> repo_for_each_pack() macro we see below, nobody assumes dest_pack\n> could be NULL).\n\nThat's true. The function is created assuming that the promisor files\nare correctly formed, but this is unrelated to the promisor files\ncontent, so it should be checked to avoid any issue. I will add a\n`die()` if `parse_pack_index()` returns `NULL`, since this means that\nsomething bad happened considering that we literally just created the\npack that we want to retrieve.\n\n> But what pack index file are we parsing here?\n\n`parse_pack_index()` is used to create a temporary `packed_git` for the\njust created pack, so that we can then check if <oid> (for each line of\neach \".promisor\" file) appears in it (and then append that line to the\n\".promisor\" file of the just created pack).\n\n> Isn't it already part\n> of the running system that we should be able to find on the list of\n> packfiles in the packfile store?\n\nNo, the new pack's `packed_git` doesn't appear in the\n`repo_for_each_pack` macro loop, if that is what you are asking.\n\n> Is this because we lack \"find a\n> packfile on this packfile store by name\" API, because what we want\n> to find if each of <oid>s we have appear in the particular packfile\n> or not, and packfile_list_find_oid() is not sufficiently precise (i.e.\n> \"the object appears in one of the packfile on the list\" is not what\n> we want to know, \"the object appears in this particular packfile\" is)?\n\nYes, it's pretty much this.\n\n> \tPatrick CC'ed primarily because this part of the API and the\n> \tdata structures have been reshuffled to add quite a lot of\n> \tabstraction since I last looked at the area.\n> \n> As close_pack_index(dest_pack) does not release resources held by\n> dest_pack itself (even though the region of mmaped memory that is\n> pointed at by its index_data member is unmapped), I think that is\n> where the memory leak is breaking the CI jobs (see my other message).\n>\n> But I am not sure if the use of parse_pack_index() - close_pack_index()\n> API is the right thing to use here.\n\nI struggled a lot to understand how to deal with a pack opened with\n`parse_pack_index()`, considering that this function is only used once\nin the codebase. In the end I decided to simply use `close_pack_index()`\nas it is done in this other instance where `parse_pack_index()` is used.\n\nHaving done more research now, I realized that the issue is that the\ncode is missing a `free(dest_pack)`. With this line, no leak happens,\nand all GitHub Actions-based CI tests are green.\n\nAlso, I think that `close_pack_index()` is indeed the correct way to\nclose a `packed_git` opened with `parse_pack_index()` (instead of using\nthe generic `close_pack()`, or not closing it at all).\n\n\nFinally, having looked more deeply at that other instance where\n`parse_pack_index()` is used, I believe there actually is a possible\nmemory leak that might rarely ever happen. Isn't there a `free()`\nmissing, like this:\n\n---\n http.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/http.c b/http.c\nindex 8ea1b9d1f6..e765852071 100644\n--- a/http.c\n+++ b/http.c\n@@ -2446,8 +2446,10 @@ static int fetch_and_setup_pack_index(struct packfile_list *packs,\n \tif (!ret)\n \t\tclose_pack_index(new_pack);\n \tfree(tmp_idx);\n-\tif (ret)\n+\tif (ret) {\n+\t\tfree(new_pack);\n \t\treturn -1;\n+\t}\n\n \tpackfile_list_prepend(packs, new_pack);\n \treturn 0;\n--\n \n> > +\t/* Open the .promisor dest file, and fill dest_content with its content */\n> > +\tdest_promisor_name = mkpathdup(\"%s-%s.promisor\", packtmp, dest_hex);\n> > +\tdest = xfopen(dest_promisor_name, \"r+\");\n> > +\twhile (strbuf_getline(&line, dest) != EOF)\n> > +\t\tstrset_add(&dest_content, line.buf);\n> > +\n> > +\trepo_for_each_pack(repo, p) {\n> > +\t\tFILE *source;\n> > +\t\tstruct stat source_stat;\n> > +\n> > +\t\tif (!p->pack_promisor)\n> > +\t\t\tcontinue;\n> > +\n> > +\t\tif (not_repacked_basenames &&\n> > +\t\t\tstrset_contains(not_repacked_basenames, pack_basename(p)))\n> > +\t\t\tcontinue;\n> > +\n\nThanks Junio,\n\nLorenzo\n"},{"id":"541419","messageId":"xmqqjyuemeza.fsf@gitster.g","threadId":"65326","inReplyTo":"xmqqse92mn5c.fsf@gitster.g","subject":"Re: [GSoC PATCH v4 0/5] preserve promisor files content after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-11T02:02:17Z","receivedAt":"2026-04-11T02:02:21Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> When merged to the tip of 'seen' (with a fixup to use st_mtime where\n> we need only whole second precision, to avoid using st_mtim on\n> platforms that do not have it), this seems to break linux-leaks and\n> linux-reftable-leaks CI jobs (t0410, t5616, and t5710).\n>\n> This topic standalone, without interaction with other things in\n> 'seen', breaks these three tests.\n>\n>   https://github.com/git/git/actions/runs/24267948258/job/70866907548\n>\n> This is one commit directly on top of your topic that reduces CI\n> jobs down to just two \"leaks\" job, and removes many test scripts\n> to leave only these three breaking ones.\n>\n>\n> I managed to also locally reproduce this failure.  Here is how\n>\n>     $ cd t && t5710-*.sh -i -v\n>\n> dies:\n>\n> Direct leak of 285 byte(s) in 1 object(s) allocated from:\n>     #0 0x55ce48ca1d4d in malloc (git+0x8cd4d) (BuildId: 1aa6efa30b2fc4772028a3dd31aba3ced49bf128)\n>     #1 0x55ce48fed3f2 in do_xmalloc wrapper.c:55:8\n>     #2 0x55ce48fed3b6 in xmalloc wrapper.c:76:9\n>     #3 0x55ce48eed314 in alloc_packed_git packfile.c:306:25\n>     #4 0x55ce48eed209 in parse_pack_index packfile.c:326:25\n>     #5 0x55ce48f65f03 in copy_promisor_content repack-promisor.c:67:14\n>     ...\n\n\nFWIW, the tip of 'seen' as of this writing has queued this topic\n(the latest round v5) plus the attached \"SQUASH???\" commit at its\ntip.\n\nThe first hunk (die when dest_pack is NULL) is absolutely positively\na wrong thing to do.  As I said, I do not know if we want to call\nparse_pack_index() here or if we have a more appropriate helper\nfunction to use, but assuming that parse_pack_index() is the right\nthing to call, we should be prepared to see NULL returned.  BUG(\"\")\nis reserved for detected programming errors, and it is absolutely a\nwrong thing to call.\n\nAs the content copied by this function is supposed to be for\ndebugging only, I think dying when we cannot copy is not what we\nwant.  Rather, it probably makes more sense to fall back to the\ntraditional behaviour (e.g., not copying and instead leaving an\nempty file, if that is what we did before this patch series).\n\nThe second hunk just line-wraps an overly long line.  There are\nother overly long lines in this deeply indented block (which is a\nsign that it might be worth to see if the block is better made into\na small static helper function) that should be line-wrapped in a\nsimilar way, but I didn't bother.\n\nTHe last hunk is a real bugfix for the leak (again, provided that\nparse_pack_index() is what we want to use).\n\n\n repack-promisor.c | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 26055212a3..c7025e97f2 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -71,6 +71,8 @@ static void copy_promisor_content(struct repository *repo,\n \tdest_idx_name = mkpathdup(\"%s-%s.idx\", packtmp, dest_hex);\n \tget_oid_hex_algop(dest_hex, &dest_oid, repo->hash_algo);\n \tdest_pack = parse_pack_index(repo, dest_oid.hash, dest_idx_name);\n+\tif (!dest_pack)\n+\t\tBUG(\"parse_pack_index() failed.\");\n \n \t/* Open the .promisor dest file, and fill dest_content with its content */\n \tdest_promisor_name = mkpathdup(\"%s-%s.promisor\", packtmp, dest_hex);\n@@ -115,7 +117,8 @@ static void copy_promisor_content(struct repository *repo,\n \n \t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n \t\t\tif (line_sections.nr < 3)\n-\t\t\t\tstrbuf_addf(&line, \" %\" PRItime, (timestamp_t)source_stat.st_mtime);\n+\t\t\t\tstrbuf_addf(&line, \" %\" PRItime,\n+\t\t\t\t\t    (timestamp_t)source_stat.st_mtime);\n \n \t\t\t/*\n \t\t\t * Add the finalized line to dest_to_write and dest_content if it\n@@ -148,6 +151,7 @@ static void copy_promisor_content(struct repository *repo,\n \t\tdie(_(\"Could not write '%s' promisor file\"), dest_promisor_name);\n \n \tclose_pack_index(dest_pack);\n+\tfree(dest_pack);\n \tfree(dest_idx_name);\n \tfree(dest_promisor_name);\n \tstrset_clear(&dest_content);\n-- \n2.54.0-rc1-178-ge3ec7964f3\n\n"},{"id":"541421","messageId":"adpVMfHowCBY75I1@lorenzo-VM","threadId":"65326","inReplyTo":"xmqqjyuemeza.fsf@gitster.g","subject":"Re: [GSoC PATCH v4 0/5] preserve promisor files content after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-11T14:05:37Z","receivedAt":"2026-04-11T14:05:41Z","isPatch":true,"body":"On Fri, Apr 10, 2026 at 07:02:17PM -0700, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > When merged to the tip of 'seen' (with a fixup to use st_mtime where\n> > we need only whole second precision, to avoid using st_mtim on\n> > platforms that do not have it), this seems to break linux-leaks and\n> > linux-reftable-leaks CI jobs (t0410, t5616, and t5710).\n> >\n> > This topic standalone, without interaction with other things in\n> > 'seen', breaks these three tests.\n> >\n> >   https://github.com/git/git/actions/runs/24267948258/job/70866907548\n> >\n> > This is one commit directly on top of your topic that reduces CI\n> > jobs down to just two \"leaks\" job, and removes many test scripts\n> > to leave only these three breaking ones.\n> >\n> >\n> > I managed to also locally reproduce this failure.  Here is how\n> >\n> >     $ cd t && t5710-*.sh -i -v\n> >\n> > dies:\n> >\n> > Direct leak of 285 byte(s) in 1 object(s) allocated from:\n> >     #0 0x55ce48ca1d4d in malloc (git+0x8cd4d) (BuildId: 1aa6efa30b2fc4772028a3dd31aba3ced49bf128)\n> >     #1 0x55ce48fed3f2 in do_xmalloc wrapper.c:55:8\n> >     #2 0x55ce48fed3b6 in xmalloc wrapper.c:76:9\n> >     #3 0x55ce48eed314 in alloc_packed_git packfile.c:306:25\n> >     #4 0x55ce48eed209 in parse_pack_index packfile.c:326:25\n> >     #5 0x55ce48f65f03 in copy_promisor_content repack-promisor.c:67:14\n> >     ...\n> \n> \n> FWIW, the tip of 'seen' as of this writing has queued this topic\n> (the latest round v5) plus the attached \"SQUASH???\" commit at its\n> tip.\n> \n> The first hunk (die when dest_pack is NULL) is absolutely positively\n> a wrong thing to do.  As I said, I do not know if we want to call\n> parse_pack_index() here or if we have a more appropriate helper\n> function to use, but assuming that parse_pack_index() is the right\n> thing to call, we should be prepared to see NULL returned.  BUG(\"\")\n> is reserved for detected programming errors, and it is absolutely a\n> wrong thing to call.\n>\n> As the content copied by this function is supposed to be for\n> debugging only, I think dying when we cannot copy is not what we\n> want.  Rather, it probably makes more sense to fall back to the\n> traditional behaviour (e.g., not copying and instead leaving an\n> empty file, if that is what we did before this patch series).\n\nGot it. I will add a `warning(_(\"...\"))` if this happens, and then\nimmediately `return -1` to indicate that something went wrong and so the\n\".promisor\" file will be left empty.\n\n> The second hunk just line-wraps an overly long line.  There are\n> other overly long lines in this deeply indented block (which is a\n> sign that it might be worth to see if the block is better made into\n> a small static helper function) that should be line-wrapped in a\n> similar way, but I didn't bother.\n\nThere are only I believe 3 lines of code that exceed the soft cap of 80\ncharacters per line. I will line-wrap these long lines.\n\n> THe last hunk is a real bugfix for the leak (again, provided that\n> parse_pack_index() is what we want to use).\n\nYeah, I also noticed the issue, and reported it in an email pretty much\nat the same time that you sent this one.\n\n> \n>  repack-promisor.c | 6 +++++-\n>  1 file changed, 5 insertions(+), 1 deletion(-)\n> \n> diff --git a/repack-promisor.c b/repack-promisor.c\n> index 26055212a3..c7025e97f2 100644\n> --- a/repack-promisor.c\n> +++ b/repack-promisor.c\n> @@ -71,6 +71,8 @@ static void copy_promisor_content(struct repository *repo,\n>  \tdest_idx_name = mkpathdup(\"%s-%s.idx\", packtmp, dest_hex);\n>  \tget_oid_hex_algop(dest_hex, &dest_oid, repo->hash_algo);\n>  \tdest_pack = parse_pack_index(repo, dest_oid.hash, dest_idx_name);\n> +\tif (!dest_pack)\n> +\t\tBUG(\"parse_pack_index() failed.\");\n>  \n>  \t/* Open the .promisor dest file, and fill dest_content with its content */\n>  \tdest_promisor_name = mkpathdup(\"%s-%s.promisor\", packtmp, dest_hex);\n> @@ -115,7 +117,8 @@ static void copy_promisor_content(struct repository *repo,\n>  \n>  \t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n>  \t\t\tif (line_sections.nr < 3)\n> -\t\t\t\tstrbuf_addf(&line, \" %\" PRItime, (timestamp_t)source_stat.st_mtime);\n> +\t\t\t\tstrbuf_addf(&line, \" %\" PRItime,\n> +\t\t\t\t\t    (timestamp_t)source_stat.st_mtime);\n>  \n>  \t\t\t/*\n>  \t\t\t * Add the finalized line to dest_to_write and dest_content if it\n> @@ -148,6 +151,7 @@ static void copy_promisor_content(struct repository *repo,\n>  \t\tdie(_(\"Could not write '%s' promisor file\"), dest_promisor_name);\n>  \n>  \tclose_pack_index(dest_pack);\n> +\tfree(dest_pack);\n>  \tfree(dest_idx_name);\n>  \tfree(dest_promisor_name);\n>  \tstrset_clear(&dest_content);\n> -- \n\nThanks Junio.\n\nRegarding the \"Should we use `parse_pack_index()` here?\" question, I\nhave also found success using some like this:\n\n```\n\t[...]\n\tstruct packed_git *dest_pack, *p;\n\tstruct odb_source_files *files;\n\tint err;\n\n\tdest_idx_name = mkpathdup(\"%s-%s.idx\", packtmp, dest_hex);\n\tfiles = odb_source_files_downcast(repo->objects->sources);\n\tdest_pack = packfile_store_load_pack(files->packed, dest_idx_name, 0);\n\t[...]\n```\n\nThis code passes all the tests without any leaks.\n\nNow, in all honesty, I don't know the implications of using\n`parse_pack_index()` compared to using `packfile_store_load_pack()` and\n`odb_source_files_downcast()`. Unfortunately these functions are rarely\nused in the code, so I don't have many examples to look at.\n\nLet me know what is your opinion on this. In the meantime I will explore\nthis possibility further.\n\nThanks a lot,\n\nLorenzo\n"},{"id":"541422","messageId":"641a7a4c-f836-4811-bbeb-ef534716c3a9@malon.dev","threadId":"65326","inReplyTo":"b483be7558f0efc1a6780b5cff13f4ccc3afd069.1775861047.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v5 3/6] repack-promisor: preserve content of promisor files after repack","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-04-11T18:25:50Z","receivedAt":"2026-04-11T18:25:57Z","isPatch":true,"body":"On 4/11/26 06:55, LorenzoPegorari wrote:\n\n> @@ -171,19 +172,15 @@ static void finish_repacking_promisor_objects(struct repository *repo,\n>   \n>   \t\t/*\n>   \t\t * pack-objects creates the .pack and .idx files, but not the\n> -\t\t * .promisor file. Create the .promisor file, which is empty.\n> -\t\t *\n> -\t\t * NEEDSWORK: fetch-pack sometimes generates non-empty\n> -\t\t * .promisor files containing the ref names and associated\n> -\t\t * hashes at the point of generation of the corresponding\n> -\t\t * packfile, but this would not preserve their contents. Maybe\n> -\t\t * concatenate the contents of all .promisor files instead of\n> -\t\t * just creating a new empty file.\n> +\t\t * .promisor file. Create the .promisor file.\n>   \t\t */\n>   \t\tpromisor_name = mkpathdup(\"%s-%s.promisor\", packtmp,\n>   \t\t\t\t\t  line.buf);\n>   \t\twrite_promisor_file(promisor_name, NULL, 0);\n>   \n> +\t\t/* Now let's fill the content of the newly created .promisor file */\n> +\t\tcopy_promisor_content(repo, line.buf, packtmp, not_repacked_basenames);\n\nHere, the file opened by copy_promisor_content() is an empty file. Is \nthis line necessary? ;)\n\n...hold on. I recall you mentioning in one of the versions that you had \ndowngraded this helper from a generic function to a static one. Since it \nnow only serves this particular business logic, I think the \nimplementation should be tweaked slightly as well.\n\n> +\t/* Open the .promisor dest file, and fill dest_content with its content */\n> +\tdest_promisor_name = mkpathdup(\"%s-%s.promisor\", packtmp, dest_hex);\n> +\tdest = xfopen(dest_promisor_name, \"r+\");\n> +\twhile (strbuf_getline(&line, dest) != EOF)\n> +\t\tstrset_add(&dest_content, line.buf);\n\nIf file contains a large number of unique lines, dest_to_write, which is \na strbuf, may keep realloc memory until the loop ends, at which point \nall the memory is released. I wonder if this might be wasting some heap.\n\nIf it were me, I might write it like this:\n\n\tstruct strset seen_lines = STRSET_INIT;\n\tdest = xfopen(dest_promisor_name, \"w\");\n\twhile (strbuf_getline(&line, source) != EOF) {\n     \t\tif (strset_add(&seen_lines, line.buf)) {\n         \t\tfprintf(dest, \"%s\\n\", line.buf);\n     \t\t}\t\n\t}\n\nIt also prevents file pointer misalignment.\n\n(I think we still need to discuss what should ultimately become of this \nhelper; at the moment, it seems a bit disjointed, doesn’t it?)\n\nThank you, Yuchen\n\n\n\n"},{"id":"541424","messageId":"a20d7433-2297-4ad9-a68e-302443f1f941@malon.dev","threadId":"65326","inReplyTo":"ab307e68feb9f84898dba748ef1f4598b07fc0e2.1775861047.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v5 5/6] t7703: test for promisor file content after geometric repack","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-04-11T18:49:05Z","receivedAt":"2026-04-11T18:49:11Z","isPatch":true,"body":"On 4/11/26 06:56, LorenzoPegorari wrote:\n> Add test that checks if the content of \".promisor\" files are correctly\n> copied inside the \".promisor\" files created by a geometric repack.\n> \n> Signed-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n> ---\n>   t/t7703-repack-geometric.sh | 33 +++++++++++++++++++++++++++++++++\n>   1 file changed, 33 insertions(+)\n> \n> diff --git a/t/t7703-repack-geometric.sh b/t/t7703-repack-geometric.sh\n> index 04d5d8fc33..a8e3e6ae3f 100755\n> --- a/t/t7703-repack-geometric.sh\n> +++ b/t/t7703-repack-geometric.sh\n> @@ -541,4 +541,37 @@ test_expect_success 'geometric repack works with promisor packs' '\n>   \t)\n>   '\n>   \n> +test_expect_success 'check .promisor file content after geometric repack' '\n> +\ttest_when_finished rm -rf prom_test &&\n> +\tgit init prom_test &&\n> +\tpath=prom_test/.git/objects/pack &&\n> +\n> +\t(\n> +\t\t# Create 2 packs with 3 objs each, and manually create .promisor files\n> +\t\ttest_commit_bulk -C prom_test --start=1 1 &&  # 3 objects\n\n---\n\n> +\t\tprom1=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n\nThis approach seems a bit fragile.\n\n- Perhaps you’ve heard the saying which goes like \"never parse the \noutput of ls\". In a nutshell, the output of this command is not \nstandardised;\n\n- *.pack? This may produce multiple lines of output, which I don’t think \nis what we want.\n\n- $path instead of \"$path\", which cannot correctly handle spacing in \ndirectory names;\n\n- sed s command matches the \"first\" string it meets. We can’t guarantee \nthat the '.pack' part won’t appear in users’ path names, can we?\n\n(Fun fact: There are approximately 8,000 people in the United States \nwith the surname 'Pack'. Source: 2010 Census ; - )\n\n\n> +\t\toid1=$(git -C prom_test rev-parse HEAD) &&\n> +\t\techo \"$oid1 ref1\" >\"$prom1\" &&\n> +\t\ttest_commit_bulk -C prom_test --start=2 1 &&  # 3 objects\n> +\t\tprom2=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom1|d\") &&\n> +\t\toid2=$(git -C prom_test rev-parse HEAD) &&\n> +\t\techo \"$oid2 ref2\" >\"$prom2\" &&\n> +\n> +\t\t# Create 1 pack with 12 objs, and manually create .promisor file\n> +\t\ttest_commit_bulk -C prom_test --start=3 4 &&  # 12 objects\n> +\t\tprom3=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom1|d; \\|$prom2|d\") &&\n> +\t\toid3=$(git -C prom_test rev-parse HEAD) &&\n> +\t\techo \"$oid3 ref3\" >\"$prom3\" &&\n> +\n> +\t\t# Geometric repack, and check if correct\n> +\t\tgit -C prom_test repack --geometric 2 -d &&\n> +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom3|d\") &&\n> +\t\t# $prom should have repacked only the first 2 small packs, so it should only\n> +\t\t# contain the following: \"$oid1 ref1 <time>\" & \"$oid2 ref2 <time>\"\n> +\t\ttest_grep \"$oid1 ref1 \" \"$prom\" &&\n> +\t\ttest_grep \"$oid2 ref2 \" \"$prom\" &&\n> +\t\ttest_grep ! \"$oid3 ref3\" \"$prom\"\n> +\t)\n> +'\n> +\n>   test_done\n\nThanks, yuchen\n"},{"id":"541435","messageId":"xmqqo6jolmla.fsf@gitster.g","threadId":"65326","inReplyTo":"3558bb38956b522c91057598db645eb42ffb48b2.1775861047.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v5 2/6] repack-promisor add helper to fill promisor file after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-12T06:27:45Z","receivedAt":"2026-04-12T06:27:48Z","isPatch":true,"body":"LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> +/*\n> + * Go through all .promisor files contained in repo (excluding those whose name\n> + * appears in not_repacked_basenames, which acts as a ignorelist), and copies\n> + * their content inside the destination file \"<packtmp>-<dest_hex>.promisor\".\n> + * Each line of a never repacked .promisor file is: \"<oid> <ref>\" (as described\n> + * in the write_promisor_file() function).\n> + * After a repack, the copied lines will be: \"<oid> <ref> <time>\", where <time>\n> + * is the time (in Unix time) at which the .promisor file was last modified.\n> + * Only the lines whose <oid> is present inside \"<packtmp>-<dest_hex>.idx\" will\n> + * be copied.\n> + * The contents of all .promisor files are assumed to be correctly formed.\n> + */\n> +static void copy_promisor_content(struct repository *repo,\n> +\t\t\t\t\t      const char *dest_hex,\n> +\t\t\t\t\t      const char *packtmp,\n> +\t\t\t\t\t      struct strset *not_repacked_basenames)\n> +{\n> +\tchar *dest_idx_name;\n> +\tchar *dest_promisor_name;\n> +\tFILE *dest;\n> +\tstruct strset dest_content = STRSET_INIT;\n> +\tstruct strbuf dest_to_write = STRBUF_INIT;\n> +\tstruct strbuf source_promisor_name = STRBUF_INIT;\n> +\tstruct strbuf line = STRBUF_INIT;\n> +\tstruct object_id dest_oid;\n> +\tstruct packed_git *dest_pack, *p;\n> +\tint err;\n> +\n> +\tdest_idx_name = mkpathdup(\"%s-%s.idx\", packtmp, dest_hex);\n> +\tget_oid_hex_algop(dest_hex, &dest_oid, repo->hash_algo);\n\nThis needs to prepare for a corrupt input in dest_hex, which would\nresult in garbage dest_oid.  The helper function should signal a\nfailure with its return value, right?\n\n> +\tdest_pack = parse_pack_index(repo, dest_oid.hash, dest_idx_name);\n\nAs you earlier mentioned, this use of parse_pack_index() is\nperfectly fine.  The call chains that reach here are both from\ncmd_repack() that calls either repack_promisor_objects() or\npack_geometry_repack_promisors(), and both ran \"pack-objects\" to\ncreate a new pack and called finish_repacking_promisor_objects(),\nwhich in turn calls us, so the dest_hex/packtmp we are dealing with\npoint newly created packfile that is about to become but not yet\ncompleted as a part of this repository.  We know we created it, and\nwe know \"pack-objects\" did not fail, so parse_pack_index() being\nloose in validation does not pose a practical problem.\n\nThis still needs to prepare for parse_pack_index() to return NULL,\nthough.  \n\nIn the above two cases, we should make sure that dest_idx_name gets\nfreed before we return control to the caller (possibly signaling an\nerror by returning -1, but the current caller is not expecting to\nhear a failure from us and that may be OK).\n\n> +\t/* Open the .promisor dest file, and fill dest_content with its content */\n> +\tdest_promisor_name = mkpathdup(\"%s-%s.promisor\", packtmp, dest_hex);\n> +\tdest = xfopen(dest_promisor_name, \"r+\");\n> +\twhile (strbuf_getline(&line, dest) != EOF)\n> +\t\tstrset_add(&dest_content, line.buf);\n> +\n> +\trepo_for_each_pack(repo, p) {\n> +\t\tFILE *source;\n> +\t\tstruct stat source_stat;\n> +\n> +\t\tif (!p->pack_promisor)\n> +\t\t\tcontinue;\n> +\n> +\t\tif (not_repacked_basenames &&\n> +\t\t\tstrset_contains(not_repacked_basenames, pack_basename(p)))\n> +\t\t\tcontinue;\n> +\n> +\t\tstrbuf_reset(&source_promisor_name);\n> +\t\tstrbuf_addstr(&source_promisor_name, p->pack_name);\n> +\t\tstrbuf_strip_suffix(&source_promisor_name, \".pack\");\n> +\t\tstrbuf_addstr(&source_promisor_name, \".promisor\");\n> +\n> +\t\tif (stat(source_promisor_name.buf, &source_stat))\n> +\t\t\tdie(_(\"File not found: %s\"), source_promisor_name.buf);\n> +\n> +\t\tsource = xfopen(source_promisor_name.buf, \"r\");\n> +\n> +\t\twhile (strbuf_getline(&line, source) != EOF) {\n> +\t\t\tstruct string_list line_sections = STRING_LIST_INIT_DUP;\n> +\t\t\tstruct object_id oid;\n> +\n> +\t\t\t/* Split line into <oid>, <ref> and <time> (if <time> exists) */\n> +\t\t\tstring_list_split(&line_sections, line.buf, \" \", 3);\n\nThe strbuf's contents line.buf[] is read/write, so we could use\nline_sections that is initialized with NODUP and call\nsplit_in_place() to avoid unnecessary small allocations and\ndeallocations, no?\n\nMore importantly, we say \"split into up to 3 pieces\".  What happens\nif this is totally malformed and there is only one word?  Should we\nstill trust this line and try to carry it forward?  I doubt it.\n\n> +\t\t\t/* Ignore the lines where <oid> doesn't appear in the dest_pack */\n> +\t\t\tget_oid_hex_algop(line_sections.items[0].string, &oid, repo->hash_algo);\n\nOr the first word split is not a sane hexadecimal string that\nget_oid_hex() fails?\n\nIt would be the simplest to ignore/skip the line, just like what you\ndo to a correctly formated line about an irrelevant <oid> (iow, the\nif() statement immediately below).\n\n> +\t\t\tif (!find_pack_entry_one(&oid, dest_pack)) {\n\nAssuming that the object name was read correctly, if the pack we\njust created does not have the <oid> we read from the existing\n.promisor file, this line we just read has nothing to do with the\nrepacked result, so we ignore it, which sounds fine.\n\n> +\t\t\t\tstring_list_clear(&line_sections, 0);\n> +\t\t\t\tcontinue;\n> +\t\t\t}\n> +\n> +\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n> +\t\t\tif (line_sections.nr < 3)\n> +\t\t\t\tstrbuf_addf(&line, \" %\" PRItime, (timestamp_t)source_stat.st_mtime);\n\nShould we also validate line_sections[1] in some way?  I am not sure\nif we want to call check_ref_format() on it.\n\nIf we insist that .nr is at least 2 immediately after we split the\nstring, and make sure the line begins with <oid> (i.e., parsable as\nhex object name) that might be sufficient.  I dunno.\n\n> +\t\t\t/*\n> +\t\t\t * Add the finalized line to dest_to_write and dest_content if it\n> +\t\t\t * wasn't already present inside dest_content\n> +\t\t\t */\n> +\t\t\tif (strset_add(&dest_content, line.buf)) {\n> +\t\t\t\tstrbuf_addbuf(&dest_to_write, &line);\n> +\t\t\t\tstrbuf_addch(&dest_to_write, '\\n');\n> +\t\t\t}\n> +\n> +\t\t\tstring_list_clear(&line_sections, 0);\n> +\t\t}\n> +\n> +\t\terr = ferror(source);\n> +\t\terr |= fclose(source);\n> +\t\tif (err)\n> +\t\t\tdie(_(\"Could not read '%s' promisor file\"), source_promisor_name.buf);\n> +\t}\n> +\n> +\t/* If dest_to_write is not empty, then there are new lines to append */\n> +\tif (dest_to_write.len) {\n> +\t\tif (fseek(dest, 0L, SEEK_END))\n> +\t\t\tdie_errno(_(\"fseek failed\"));\n> +\t\tfprintf(dest, \"%s\", dest_to_write.buf);\n> +\t}\n> +\n> +\terr = ferror(dest);\n> +\terr |= fclose(dest);\n> +\tif (err)\n> +\t\tdie(_(\"Could not write '%s' promisor file\"), dest_promisor_name);\n> +\n> +\tclose_pack_index(dest_pack);\n\nAs we discussed, \n\n\tfree(dest_pack);\n\nis missing.\n\n> +\tfree(dest_idx_name);\n> +\tfree(dest_promisor_name);\n> +\tstrset_clear(&dest_content);\n> +\tstrbuf_release(&dest_to_write);\n> +\tstrbuf_release(&source_promisor_name);\n> +\tstrbuf_release(&line);\n> +}\n> +\n>  static void finish_repacking_promisor_objects(struct repository *repo,\n>  \t\t\t\t\t      struct child_process *cmd,\n>  \t\t\t\t\t      struct string_list *names,\n"},{"id":"541800","messageId":"CABPp-BGaN_PtvUVyR8sskpLUev16FjaUbTj21j_Lna73hEYGmw@mail.gmail.com","threadId":"65326","inReplyTo":"xmqqzf3aofdj.fsf_-_@gitster.g","subject":"Re: [PATCH] CodingGuidelines: st_mtimespec vs st_mtim vs st_mtime","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2026-04-16T23:46:13Z","receivedAt":"2026-04-16T23:46:25Z","isPatch":true,"body":"On Fri, Apr 10, 2026 at 11:10 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Most unfortunately macOS does not support st_[amc]tim for timestamps\n> down to nanosecond resolution as POSIX systems.\n>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>  Documentation/CodingGuidelines | 6 ++++++\n>  1 file changed, 6 insertions(+)\n>\n> diff --git c/Documentation/CodingGuidelines w/Documentation/CodingGuidelines\n> index 4992e52093..4e54139fd7 100644\n> --- c/Documentation/CodingGuidelines\n> +++ w/Documentation/CodingGuidelines\n> @@ -693,6 +693,12 @@ For C programs:\n>           char *dogs[] = ...;\n>           walk_all_dogs(dogs);\n>\n> + - For file timestamps, do not use \"st_mtim\" (and other timestamp\n> +   members in \"struct stat\") unconditionally; not everybody is POSIX\n> +   (grep for USE_ST_TIMESPEC).  If you only need timestamp in whole\n> +   second resolution, \"st_mtime\" should work fine everywhere.\n> +\n> +\n>  For Perl programs:\n>\n>   - Most of the C guidelines above apply.\n\nLooks good to me.  As a minor nit, \"need timestamp\" -> \"need a timestamp\".\n"},{"id":"541801","messageId":"aeF_PL5qYS-7Ogvd@lorenzo-VM","threadId":"65326","inReplyTo":"xmqqo6jolmla.fsf@gitster.g","subject":"Re: [GSoC PATCH v5 2/6] repack-promisor add helper to fill promisor file after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-17T00:30:52Z","receivedAt":"2026-04-17T00:30:56Z","isPatch":true,"body":"On Sat, Apr 11, 2026 at 11:27:45PM -0700, Junio C Hamano wrote:\n> LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n> \n> > +/*\n> > + * Go through all .promisor files contained in repo (excluding those whose name\n> > + * appears in not_repacked_basenames, which acts as a ignorelist), and copies\n> > + * their content inside the destination file \"<packtmp>-<dest_hex>.promisor\".\n> > + * Each line of a never repacked .promisor file is: \"<oid> <ref>\" (as described\n> > + * in the write_promisor_file() function).\n> > + * After a repack, the copied lines will be: \"<oid> <ref> <time>\", where <time>\n> > + * is the time (in Unix time) at which the .promisor file was last modified.\n> > + * Only the lines whose <oid> is present inside \"<packtmp>-<dest_hex>.idx\" will\n> > + * be copied.\n> > + * The contents of all .promisor files are assumed to be correctly formed.\n> > + */\n> > +static void copy_promisor_content(struct repository *repo,\n> > +\t\t\t\t\t      const char *dest_hex,\n> > +\t\t\t\t\t      const char *packtmp,\n> > +\t\t\t\t\t      struct strset *not_repacked_basenames)\n> > +{\n> > +\tchar *dest_idx_name;\n> > +\tchar *dest_promisor_name;\n> > +\tFILE *dest;\n> > +\tstruct strset dest_content = STRSET_INIT;\n> > +\tstruct strbuf dest_to_write = STRBUF_INIT;\n> > +\tstruct strbuf source_promisor_name = STRBUF_INIT;\n> > +\tstruct strbuf line = STRBUF_INIT;\n> > +\tstruct object_id dest_oid;\n> > +\tstruct packed_git *dest_pack, *p;\n> > +\tint err;\n> > +\n> > +\tdest_idx_name = mkpathdup(\"%s-%s.idx\", packtmp, dest_hex);\n> > +\tget_oid_hex_algop(dest_hex, &dest_oid, repo->hash_algo);\n> \n> This needs to prepare for a corrupt input in dest_hex, which would\n> result in garbage dest_oid.  The helper function should signal a\n> failure with its return value, right?\n\nAck. I think the best way is to signal a `warning()`, and then simply\nexit the helper function leaving the \".promisor\" file empty.\n\n> > +\tdest_pack = parse_pack_index(repo, dest_oid.hash, dest_idx_name);\n> \n> As you earlier mentioned, this use of parse_pack_index() is\n> perfectly fine.  The call chains that reach here are both from\n> cmd_repack() that calls either repack_promisor_objects() or\n> pack_geometry_repack_promisors(), and both ran \"pack-objects\" to\n> create a new pack and called finish_repacking_promisor_objects(),\n> which in turn calls us, so the dest_hex/packtmp we are dealing with\n> point newly created packfile that is about to become but not yet\n> completed as a part of this repository.  We know we created it, and\n> we know \"pack-objects\" did not fail, so parse_pack_index() being\n> loose in validation does not pose a practical problem.\n\nExactly. I couldn't quite explain it as good as you right now. :)\n\n> This still needs to prepare for parse_pack_index() to return NULL,\n> though.\n\nAck.\n\n> In the above two cases, we should make sure that dest_idx_name gets\n> freed before we return control to the caller (possibly signaling an\n> error by returning -1, but the current caller is not expecting to\n> hear a failure from us and that may be OK).\n\nAgain, I think this should be treated the same as when `dest_hex` is\ngarbage.\n\n> > +\t/* Open the .promisor dest file, and fill dest_content with its content */\n> > +\tdest_promisor_name = mkpathdup(\"%s-%s.promisor\", packtmp, dest_hex);\n> > +\tdest = xfopen(dest_promisor_name, \"r+\");\n> > +\twhile (strbuf_getline(&line, dest) != EOF)\n> > +\t\tstrset_add(&dest_content, line.buf);\n> > +\n> > +\trepo_for_each_pack(repo, p) {\n> > +\t\tFILE *source;\n> > +\t\tstruct stat source_stat;\n> > +\n> > +\t\tif (!p->pack_promisor)\n> > +\t\t\tcontinue;\n> > +\n> > +\t\tif (not_repacked_basenames &&\n> > +\t\t\tstrset_contains(not_repacked_basenames, pack_basename(p)))\n> > +\t\t\tcontinue;\n> > +\n> > +\t\tstrbuf_reset(&source_promisor_name);\n> > +\t\tstrbuf_addstr(&source_promisor_name, p->pack_name);\n> > +\t\tstrbuf_strip_suffix(&source_promisor_name, \".pack\");\n> > +\t\tstrbuf_addstr(&source_promisor_name, \".promisor\");\n> > +\n> > +\t\tif (stat(source_promisor_name.buf, &source_stat))\n> > +\t\t\tdie(_(\"File not found: %s\"), source_promisor_name.buf);\n> > +\n> > +\t\tsource = xfopen(source_promisor_name.buf, \"r\");\n> > +\n> > +\t\twhile (strbuf_getline(&line, source) != EOF) {\n> > +\t\t\tstruct string_list line_sections = STRING_LIST_INIT_DUP;\n> > +\t\t\tstruct object_id oid;\n> > +\n> > +\t\t\t/* Split line into <oid>, <ref> and <time> (if <time> exists) */\n> > +\t\t\tstring_list_split(&line_sections, line.buf, \" \", 3);\n> \n> The strbuf's contents line.buf[] is read/write, so we could use\n> line_sections that is initialized with NODUP and call\n> split_in_place() to avoid unnecessary small allocations and\n> deallocations, no?\n\nI don't think so, because we still need the complete `line` when we\nappend the <time> to it (if we do so), and when we print it to the\n`dest` file. This means that we can't use `split_in_place()` and\ninitialize it with `NODUP`, because then we would have the complete\n`line`.\n\n> More importantly, we say \"split into up to 3 pieces\".  What happens\n> if this is totally malformed and there is only one word?  Should we\n> still trust this line and try to carry it forward?  I doubt it.\n\nI think we should discard the line if it can't be split up into 2 or 3\npieces.\n\n> > +\t\t\t/* Ignore the lines where <oid> doesn't appear in the dest_pack */\n> > +\t\t\tget_oid_hex_algop(line_sections.items[0].string, &oid, repo->hash_algo);\n> \n> Or the first word split is not a sane hexadecimal string that\n> get_oid_hex() fails?\n\nSame, we should just discard it.\n\n> It would be the simplest to ignore/skip the line, just like what you\n> do to a correctly formated line about an irrelevant <oid> (iow, the\n> if() statement immediately below).\n\nAgreed.\n\n> > +\t\t\tif (!find_pack_entry_one(&oid, dest_pack)) {\n> \n> Assuming that the object name was read correctly, if the pack we\n> just created does not have the <oid> we read from the existing\n> .promisor file, this line we just read has nothing to do with the\n> repacked result, so we ignore it, which sounds fine.\n> \n> > +\t\t\t\tstring_list_clear(&line_sections, 0);\n> > +\t\t\t\tcontinue;\n> > +\t\t\t}\n> > +\n> > +\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n> > +\t\t\tif (line_sections.nr < 3)\n> > +\t\t\t\tstrbuf_addf(&line, \" %\" PRItime, (timestamp_t)source_stat.st_mtime);\n> \n> Should we also validate line_sections[1] in some way?  I am not sure\n> if we want to call check_ref_format() on it.\n>\n> If we insist that .nr is at least 2 immediately after we split the\n> string, and make sure the line begins with <oid> (i.e., parsable as\n> hex object name) that might be sufficient.  I dunno.\n\nI think we should check <ref>. Found some success using:\n        `check_refname_format(<ref>, REFNAME_ALLOW_ONELEVEL)`\n\n> > +\t\t\t/*\n> > +\t\t\t * Add the finalized line to dest_to_write and dest_content if it\n> > +\t\t\t * wasn't already present inside dest_content\n> > +\t\t\t */\n> > +\t\t\tif (strset_add(&dest_content, line.buf)) {\n> > +\t\t\t\tstrbuf_addbuf(&dest_to_write, &line);\n> > +\t\t\t\tstrbuf_addch(&dest_to_write, '\\n');\n> > +\t\t\t}\n> > +\n> > +\t\t\tstring_list_clear(&line_sections, 0);\n> > +\t\t}\n> > +\n> > +\t\terr = ferror(source);\n> > +\t\terr |= fclose(source);\n> > +\t\tif (err)\n> > +\t\t\tdie(_(\"Could not read '%s' promisor file\"), source_promisor_name.buf);\n> > +\t}\n> > +\n> > +\t/* If dest_to_write is not empty, then there are new lines to append */\n> > +\tif (dest_to_write.len) {\n> > +\t\tif (fseek(dest, 0L, SEEK_END))\n> > +\t\t\tdie_errno(_(\"fseek failed\"));\n> > +\t\tfprintf(dest, \"%s\", dest_to_write.buf);\n> > +\t}\n> > +\n> > +\terr = ferror(dest);\n> > +\terr |= fclose(dest);\n> > +\tif (err)\n> > +\t\tdie(_(\"Could not write '%s' promisor file\"), dest_promisor_name);\n> > +\n> > +\tclose_pack_index(dest_pack);\n> \n> As we discussed, \n> \n> \tfree(dest_pack);\n> \n> is missing.\n\nAck.\n\n> > +\tfree(dest_idx_name);\n> > +\tfree(dest_promisor_name);\n> > +\tstrset_clear(&dest_content);\n> > +\tstrbuf_release(&dest_to_write);\n> > +\tstrbuf_release(&source_promisor_name);\n> > +\tstrbuf_release(&line);\n> > +}\n> > +\n> >  static void finish_repacking_promisor_objects(struct repository *repo,\n> >  \t\t\t\t\t      struct child_process *cmd,\n> >  \t\t\t\t\t      struct string_list *names,\n"},{"id":"541802","messageId":"aeGAAx6_o6f_BN3A@lorenzo-VM","threadId":"65326","inReplyTo":"641a7a4c-f836-4811-bbeb-ef534716c3a9@malon.dev","subject":"Re: [GSoC PATCH v5 3/6] repack-promisor: preserve content of promisor files after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-17T00:34:11Z","receivedAt":"2026-04-17T00:34:15Z","isPatch":true,"body":"On Sun, Apr 12, 2026 at 02:25:50AM +0800, Tian Yuchen wrote:\n> On 4/11/26 06:55, LorenzoPegorari wrote:\n> \n> > @@ -171,19 +172,15 @@ static void finish_repacking_promisor_objects(struct repository *repo,\n> >   \t\t/*\n> >   \t\t * pack-objects creates the .pack and .idx files, but not the\n> > -\t\t * .promisor file. Create the .promisor file, which is empty.\n> > -\t\t *\n> > -\t\t * NEEDSWORK: fetch-pack sometimes generates non-empty\n> > -\t\t * .promisor files containing the ref names and associated\n> > -\t\t * hashes at the point of generation of the corresponding\n> > -\t\t * packfile, but this would not preserve their contents. Maybe\n> > -\t\t * concatenate the contents of all .promisor files instead of\n> > -\t\t * just creating a new empty file.\n> > +\t\t * .promisor file. Create the .promisor file.\n> >   \t\t */\n> >   \t\tpromisor_name = mkpathdup(\"%s-%s.promisor\", packtmp,\n> >   \t\t\t\t\t  line.buf);\n> >   \t\twrite_promisor_file(promisor_name, NULL, 0);\n> > +\t\t/* Now let's fill the content of the newly created .promisor file */\n> > +\t\tcopy_promisor_content(repo, line.buf, packtmp, not_repacked_basenames);\n> \n> Here, the file opened by copy_promisor_content() is an empty file. Is this\n> line necessary? ;)\n> \n> ...hold on. I recall you mentioning in one of the versions that you had\n> downgraded this helper from a generic function to a static one. Since it now\n> only serves this particular business logic, I think the implementation\n> should be tweaked slightly as well.\n\nGood point. It makes sense to modify the helper function to create the\nempty \".promisor\" file, and then fill it, so that we won't use\n`write_promisor_file()` at all.\n\n> > +\t/* Open the .promisor dest file, and fill dest_content with its content */\n> > +\tdest_promisor_name = mkpathdup(\"%s-%s.promisor\", packtmp, dest_hex);\n> > +\tdest = xfopen(dest_promisor_name, \"r+\");\n> > +\twhile (strbuf_getline(&line, dest) != EOF)\n> > +\t\tstrset_add(&dest_content, line.buf);\n> \n> If file contains a large number of unique lines, dest_to_write, which is a\n> strbuf, may keep realloc memory until the loop ends, at which point all the\n> memory is released. I wonder if this might be wasting some heap.\n> \n> If it were me, I might write it like this:\n> \n> \tstruct strset seen_lines = STRSET_INIT;\n> \tdest = xfopen(dest_promisor_name, \"w\");\n> \twhile (strbuf_getline(&line, source) != EOF) {\n>     \t\tif (strset_add(&seen_lines, line.buf)) {\n>         \t\tfprintf(dest, \"%s\\n\", line.buf);\n>     \t\t}\t\n> \t}\n> \n> It also prevents file pointer misalignment.\n\nMakes sense. Will use this. Thanks!\n\n> (I think we still need to discuss what should ultimately become of this\n> helper; at the moment, it seems a bit disjointed, doesn’t it?)\n\nI feel like keeping it a static function only used to generate the\n\".promisor\" files after a repack makes the most sense.\n\n> Thank you, Yuchen\n\nThanks,\nLorenzo\n"},{"id":"541803","messageId":"aeGC6Mm3BdjAOGQe@lorenzo-VM","threadId":"65326","inReplyTo":"a20d7433-2297-4ad9-a68e-302443f1f941@malon.dev","subject":"Re: [GSoC PATCH v5 5/6] t7703: test for promisor file content after geometric repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-17T00:46:32Z","receivedAt":"2026-04-17T00:46:37Z","isPatch":true,"body":"On Sun, Apr 12, 2026 at 02:49:05AM +0800, Tian Yuchen wrote:\n> On 4/11/26 06:56, LorenzoPegorari wrote:\n> > Add test that checks if the content of \".promisor\" files are correctly\n> > copied inside the \".promisor\" files created by a geometric repack.\n> > \n> > Signed-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n> > ---\n> >   t/t7703-repack-geometric.sh | 33 +++++++++++++++++++++++++++++++++\n> >   1 file changed, 33 insertions(+)\n> > \n> > diff --git a/t/t7703-repack-geometric.sh b/t/t7703-repack-geometric.sh\n> > index 04d5d8fc33..a8e3e6ae3f 100755\n> > --- a/t/t7703-repack-geometric.sh\n> > +++ b/t/t7703-repack-geometric.sh\n> > @@ -541,4 +541,37 @@ test_expect_success 'geometric repack works with promisor packs' '\n> >   \t)\n> >   '\n> > +test_expect_success 'check .promisor file content after geometric repack' '\n> > +\ttest_when_finished rm -rf prom_test &&\n> > +\tgit init prom_test &&\n> > +\tpath=prom_test/.git/objects/pack &&\n> > +\n> > +\t(\n> > +\t\t# Create 2 packs with 3 objs each, and manually create .promisor files\n> > +\t\ttest_commit_bulk -C prom_test --start=1 1 &&  # 3 objects\n> \n> ---\n> \n> > +\t\tprom1=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/\") &&\n> \n> This approach seems a bit fragile.\n> \n> - Perhaps you’ve heard the saying which goes like \"never parse the output of\n> ls\". In a nutshell, the output of this command is not standardised;\n\nDidn't know that. I'm learning a lot! :)\n\nI will rewrite this using `find`.\n\n> - *.pack? This may produce multiple lines of output, which I don’t think is\n> what we want.\n\n`test_commit_bulk` specifically creates a single pack. With \"*.pack\" we\nmight get multiple lines of output, but only if we first created\nmultiple packs.\n\nI think doing something like this makes sense:\n\n```\n[...]\npath=prom_test/.git/objects/pack &&\n# Create first pack\ntest_commit_bulk -C prom_test 1 &&\n# Get first pack name, and then get its \".promisor\" filename\nprom1=$(find $path -name \"*.pack\" | sed \"s/.pack$/.promisor/\") &&\n[...]\n# Create second pack\ntest_commit_bulk -C prom_test 1 &&\n# Get all packs names, then get their \".promisor\" filenames, and finally\n# remove the filename that we got before, to obtain the new filename\nprom2=$(find $path -name \"*.pack\" | sed \"s/.pack$/.promisor/; \\|$prom1|d\") &&\n[...]\n```\n\n> - $path instead of \"$path\", which cannot correctly handle spacing in\n> directory names;\n\nAck.\n\n> - sed s command matches the \"first\" string it meets. We can’t guarantee that\n> the '.pack' part won’t appear in users’ path names, can we?\n>\n> (Fun fact: There are approximately 8,000 people in the United States with\n> the surname 'Pack'. Source: 2010 Census ; - )\n\nTrue. I will use `sed \"s/.pack$/.promisor/\"`, which will only look for\n\".pack\" substrings that appear at the end of the string.\n> \n> > +\t\toid1=$(git -C prom_test rev-parse HEAD) &&\n> > +\t\techo \"$oid1 ref1\" >\"$prom1\" &&\n> > +\t\ttest_commit_bulk -C prom_test --start=2 1 &&  # 3 objects\n> > +\t\tprom2=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom1|d\") &&\n> > +\t\toid2=$(git -C prom_test rev-parse HEAD) &&\n> > +\t\techo \"$oid2 ref2\" >\"$prom2\" &&\n> > +\n> > +\t\t# Create 1 pack with 12 objs, and manually create .promisor file\n> > +\t\ttest_commit_bulk -C prom_test --start=3 4 &&  # 12 objects\n> > +\t\tprom3=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom1|d; \\|$prom2|d\") &&\n> > +\t\toid3=$(git -C prom_test rev-parse HEAD) &&\n> > +\t\techo \"$oid3 ref3\" >\"$prom3\" &&\n> > +\n> > +\t\t# Geometric repack, and check if correct\n> > +\t\tgit -C prom_test repack --geometric 2 -d &&\n> > +\t\tprom=$(ls $path/*.pack | sed \"s/\\.pack/.promisor/; \\|$prom3|d\") &&\n> > +\t\t# $prom should have repacked only the first 2 small packs, so it should only\n> > +\t\t# contain the following: \"$oid1 ref1 <time>\" & \"$oid2 ref2 <time>\"\n> > +\t\ttest_grep \"$oid1 ref1 \" \"$prom\" &&\n> > +\t\ttest_grep \"$oid2 ref2 \" \"$prom\" &&\n> > +\t\ttest_grep ! \"$oid3 ref3\" \"$prom\"\n> > +\t)\n> > +'\n> > +\n> >   test_done\n> \n> Thanks, yuchen\n\nThanks,\nLorenzo\n"},{"id":"541808","messageId":"xmqqmrz2xlfb.fsf@gitster.g","threadId":"65326","inReplyTo":"CABPp-BGaN_PtvUVyR8sskpLUev16FjaUbTj21j_Lna73hEYGmw@mail.gmail.com","subject":"Re: [PATCH] CodingGuidelines: st_mtimespec vs st_mtim vs st_mtime","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-17T04:25:44Z","receivedAt":"2026-04-17T04:25:46Z","isPatch":true,"body":"Elijah Newren <newren@gmail.com> writes:\n\n>>  For Perl programs:\n>>\n>>   - Most of the C guidelines above apply.\n>\n> Looks good to me.  As a minor nit, \"need timestamp\" -> \"need a timestamp\".\n\nThanks.  Will amend.\n"},{"id":"541856","messageId":"cover.1776384902.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1775861047.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v6 0/6] preserve promisor files content after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-18T14:16:46Z","receivedAt":"2026-04-18T14:16:50Z","isPatch":true,"body":"The goal of this patch is to solve the NEEDSWORK comment added by\n5374a290 (fetch-pack: write fetched refs to .promisor, 14/10/2019). This\nis done by adding a helper function that takes the content of all\n.promisor files in the `repository`, and copies it inside the first\n.promisor file created by the repack.\n\nAlso, I added a comment explaining what is the purpose of the content of\nthe .promisor files, since this wasn't explained anywhere (I found\ninformation regarding this only in the message of the previously cited\ncommit).\n\nFinally, I added some tests to \"t7700-repack.sh\" and\n\"t7703-repack-geometric.sh\" that check if the content of .promisor files\nare correctly copied into the .promisor files created by a repack.\n\nV6 DIFF:\n * changed the name of the helper function to\n   `write_promisor_file_after_repack`.\n * modified the helper function to create the \".promisor\" file, so that\n   is not required anymore.\n * modified the logic of the helper function (as suggested by Tian\n   Yuchen)\n * modified the helper function to check for possible errors, and to\n   check if the lines of the \".promisor\" files are correctly formed.\n * fixed memory leak.\n * improved comments.\n\n\nLorenzoPegorari (6):\n  pack-write: add explanation to promisor file content\n  repack-promisor add helper to fill promisor file after repack\n  repack-promisor: preserve content of promisor files after repack\n  t7700: test for promisor file content after repack\n  t7703: test for promisor file content after geometric repack\n  repack-promisor: add missing headers\n\n Documentation/git-repack.adoc |   4 +-\n pack-write.c                  |   9 ++\n repack-promisor.c             | 194 ++++++++++++++++++++++++++++++----\n t/t7700-repack.sh             |  61 +++++++++++\n t/t7703-repack-geometric.sh   |  33 ++++++\n 5 files changed, 280 insertions(+), 21 deletions(-)\n\n-- \n2.53.0.584.g6b87e8e9dd\n\n"},{"id":"541857","messageId":"bc1e3eb244117ed0ad8a7fd3f2bf180f085eacdc.1776384902.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1776384902.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v6 1/6] pack-write: add explanation to promisor file content","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-18T14:16:54Z","receivedAt":"2026-04-18T14:16:58Z","isPatch":true,"body":"In the entire codebase there is no explanation as to why the \".promisor\"\nfiles may contain the ref names (and their associated hashes) that were\nfetched at the time the corresponding packfile was downloaded.\n\nAs explained in the log message of commit 5374a290 (fetch-pack: write\nfetched refs to .promisor, 2019-10-14), where this loop originally came\nfrom, these ref names (and associated hashes) are not used for anything\nin the production, but are solely there to help debugging.\n\nExplain this in a new comment.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n pack-write.c | 9 +++++++++\n 1 file changed, 9 insertions(+)\n\ndiff --git a/pack-write.c b/pack-write.c\nindex 83eaf88541..b8ab9510ff 100644\n--- a/pack-write.c\n+++ b/pack-write.c\n@@ -603,6 +603,15 @@ void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_\n \tint i, err;\n \tFILE *output = xfopen(promisor_name, \"w\");\n \n+\t/*\n+\t * Write in the .promisor file the ref names and associated hashes,\n+\t * obtained by fetch-pack, at the point of generation of the\n+\t * corresponding packfile. These pieces of info are only used to make\n+\t * it easier to debug issues with partial clones, as we can identify\n+\t * what refs (and their associated hashes) were fetched at the time\n+\t * the packfile was downloaded, and if necessary, compare those hashes\n+\t * against what the promisor remote reports now.\n+\t */\n \tfor (i = 0; i < nr_sought; i++)\n \t\tfprintf(output, \"%s %s\\n\", oid_to_hex(&sought[i]->old_oid),\n \t\t\tsought[i]->name);\n-- \n2.53.0.584.g6b87e8e9dd\n\n"},{"id":"541858","messageId":"de28baad3839f5c6e74352eae4de7763124e7fb8.1776384902.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1776384902.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v6 2/6] repack-promisor add helper to fill promisor file after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-18T14:17:01Z","receivedAt":"2026-04-18T14:17:06Z","isPatch":true,"body":"A \".promisor\" file may contain ref names (and their associated hashes)\nthat were fetched at the time the corresponding packfile was downloaded.\nThis information is used for debugging reasons. This information is\nstored as lines structured like this: \"<oid> <ref>\".\n\nCreate a `write_promisor_file_after_repack()` helper function that allows\nthis debugging info to not be lost after a `repack`, by copying it inside\na new \".promisor\" file.\n\nThe function logic is the following:\n * Take all \".promisor\" files contained inside the given `repo`.\n * Ignore those whose name is contained inside the given `strset\n   not_repacked_names`, which basically acts as a \"promisor ignorelist\"\n   (intended to be used for packfiles that have not been repacked).\n * Read each line of the remaining \".promisor\" files, which can be:\n    * \"<oid> <ref>\" if the \".promisor\" file was never repacked. If so,\n      add the time (in Unix time) at which the \".promisor\" file was last\n      modified <time> to the line, to obtain: \"<oid> <ref> <time>\".\n    * \"<oid> <ref> <time>\" if the \".promisor\" file was repacked. If so,\n      don't modify it.\n * Ignore the line if its <oid> is not present inside the\n   \"<packtmp>-<dest_hex>.idx\" file.\n * If the destination file \"<packtmp>-<dest_hex>.promisor\" does not\n   already contain the line, append it to the file.\n\nThe time of last data modification, for never-repacked \".promisor\" file,\ncan be used when comparing the entries in it with entries in another\n\".promisor\" file that did get repacked. With these timestamps, the\ndebugger will be able to tell at which time the refs at the remote\nrepository pointed at what object. Also, when looking at already\nrepacked \".promisor\" files, the same ref may appear multiple times, and\nhaving timestamps will help understanding what happened over time.\n\nThe function tries its best to deal with malformed \".promisor\" files,\nignoring those lines:\n * That cannot be split into \"<oid> <ref>\" or \"<oid> <ref> <time>\".\n * Whose <oid> is not a sane hexadecimal string.\n * Whose <ref> does not have the correct format for a refname.\n\nThe function `parse_pack_index()`, which is loose in validation, can be\nsafely used to obtain the `packed_git` of the packs created during the\n`repack` because, when `write_promisor_file_after_repack()` is called by\n`finish_repacking_promisor_objects()`, we know for a fact that they were\njust successfully created by `pack-objects` (also, these packs have not\nyet been finalized, and so they are not part of the repository). Anyway,\nif an error happens while trying to obtain the `packed_git`, the\n\".promisor\" file will be created empty.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Tian Yuchen <cat@malon.dev>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n repack-promisor.c | 149 ++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 149 insertions(+)\n\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 90318ce150..8fc541d2cf 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -4,6 +4,7 @@\n #include \"pack.h\"\n #include \"packfile.h\"\n #include \"path.h\"\n+#include \"refs.h\"\n #include \"repository.h\"\n #include \"run-command.h\"\n \n@@ -34,6 +35,154 @@ static int write_oid(const struct object_id *oid,\n \treturn 0;\n }\n \n+/*\n+ * Go through all .promisor files contained in repo (excluding those whose name\n+ * appears in not_repacked_basenames, which acts as a ignorelist), and copies\n+ * their content inside the destination file \"<packtmp>-<dest_hex>.promisor\".\n+ * Each line of a never repacked .promisor file is: \"<oid> <ref>\" (as described\n+ * in the write_promisor_file() function).\n+ * After a repack, the copied lines will be: \"<oid> <ref> <time>\", where <time>\n+ * is the time (in Unix time) at which the .promisor file was last modified.\n+ * Only the lines whose <oid> is present inside \"<packtmp>-<dest_hex>.idx\" will\n+ * be copied.\n+ * The contents of all .promisor files are assumed to be correctly formed.\n+ */\n+static void write_promisor_file_after_repack(struct repository *repo,\n+\t\t\t\t\t\t\t\tconst char *dest_hex,\n+\t\t\t\t\t\t\t\tconst char *packtmp,\n+\t\t\t\t\t\t\t\tstruct strset *not_repacked_basenames)\n+{\n+\tchar *dest_promisor_name;\n+\tchar *dest_idx_name;\n+\tFILE *dest;\n+\tstruct object_id dest_oid;\n+\tstruct packed_git *dest_pack, *p;\n+\tstruct strbuf source_promisor_name = STRBUF_INIT;\n+\tstruct strset seen_lines = STRSET_INIT;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tint err;\n+\n+\t/* First of all, let's create and open the .promisor dest file */\n+\tdest_promisor_name = mkpathdup(\"%s-%s.promisor\", packtmp, dest_hex);\n+\tdest = xfopen(dest_promisor_name, \"w\");\n+\n+\t/*\n+\t * Now let's retrieve the destination pack.\n+\t * We use parse_pack_index() because dest_hex/packtmp point to the packfile\n+\t * that \"pack-objects\" just created, which is about to become part of this\n+\t * repository, but has not yet been finalized.\n+\t * If we are here, we know that \"pack-objects\" did not fail, so\n+\t * parse_pack_index() being loose in validation does not pose a problem.\n+\t * If an error happens, we simply leave the \".promisor\" file empty.\n+\t */\n+\tif (get_oid_hex_algop(dest_hex, &dest_oid, repo->hash_algo)) {\n+\t\twarning(_(\"Promisor file left empty: '%s' not a hash\"), dest_hex);\n+\t\tif (fclose(dest))\n+\t\t\tdie(_(\"Could not close '%s' promisor file\"), dest_promisor_name);\n+\t\tfree(dest_promisor_name);\n+\t\treturn;\n+\t}\n+\tdest_idx_name = mkpathdup(\"%s-%s.idx\", packtmp, dest_hex);\n+\tdest_pack = parse_pack_index(repo, dest_oid.hash, dest_idx_name);\n+\tif (!dest_pack) {\n+\t\twarning(_(\"Promisor file left empty: couldn't open packfile '%s'\"), dest_idx_name);\n+\t\tif (fclose(dest))\n+\t\t\tdie(_(\"Could not close '%s' promisor file\"), dest_promisor_name);\n+\t\tfree(dest_promisor_name);\n+\t\tfree(dest_idx_name);\n+\t\treturn;\n+\t}\n+\n+\trepo_for_each_pack(repo, p) {\n+\t\tFILE *source;\n+\t\tstruct stat source_stat;\n+\n+\t\tif (!p->pack_promisor)\n+\t\t\tcontinue;\n+\n+\t\tif (not_repacked_basenames &&\n+\t\t\tstrset_contains(not_repacked_basenames, pack_basename(p)))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&source_promisor_name);\n+\t\tstrbuf_addstr(&source_promisor_name, p->pack_name);\n+\t\tstrbuf_strip_suffix(&source_promisor_name, \".pack\");\n+\t\tstrbuf_addstr(&source_promisor_name, \".promisor\");\n+\n+\t\tif (stat(source_promisor_name.buf, &source_stat))\n+\t\t\tdie(_(\"File not found: %s\"), source_promisor_name.buf);\n+\n+\t\tsource = xfopen(source_promisor_name.buf, \"r\");\n+\n+\t\twhile (strbuf_getline(&line, source) != EOF) {\n+\t\t\tstruct string_list line_sections = STRING_LIST_INIT_DUP;\n+\t\t\tstruct object_id oid;\n+\n+\t\t\t/* Split line into <oid>, <ref> and <time> (if <time> exists).\n+\t\t\t * Check that it was actually split into 2 or 3 parts. If it was\n+\t\t\t *  not, then it is malformed, so skip it.\n+\t\t\t */\n+\t\t\tstring_list_split(&line_sections, line.buf, \" \", 3);\n+\t\t\tif (line_sections.nr != 2 && line_sections.nr != 3) {\n+\t\t\t\tstring_list_clear(&line_sections, 0);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\t/* Skip the lines where <oid> is not a sane hexadecimal string */\n+\t\t\tif (get_oid_hex_algop(line_sections.items[0].string,\n+\t\t\t\t\t\t&oid, repo->hash_algo)) {\n+\t\t\t\tstring_list_clear(&line_sections, 0);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\t/* Ignore the lines where <oid> doesn't appear in the dest_pack */\n+\t\t\tif (!find_pack_entry_one(&oid, dest_pack)) {\n+\t\t\t\tstring_list_clear(&line_sections, 0);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * Skip the lines where <ref> does not have the\n+\t\t\t * correct format for a refname.\n+\t\t\t */\n+\t\t\tprintf(\"%s\\n\", line_sections.items[1].string);\n+\t\t\tif (check_refname_format(line_sections.items[1].string,\n+\t\t\t\t\t\t\tREFNAME_ALLOW_ONELEVEL)) {\n+\t\t\t\tstring_list_clear(&line_sections, 0);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\t/* If <time> doesn't exist, retrieve it and add it to line */\n+\t\t\tif (line_sections.nr != 3)\n+\t\t\t\tstrbuf_addf(&line, \" %\" PRItime,\n+\t\t\t\t\t\t\t(timestamp_t)source_stat.st_mtime);\n+\n+\t\t\t/* If the finalized line is new, append it to dest */\n+\t\t\tif (strset_add(&seen_lines, line.buf))\n+\t\t\t\tfprintf(dest, \"%s\\n\", line.buf);\n+\n+\t\t\tstring_list_clear(&line_sections, 0);\n+\t\t}\n+\n+\t\terr = ferror(source);\n+\t\terr |= fclose(source);\n+\t\tif (err)\n+\t\t\tdie(_(\"Could not read '%s' promisor file\"), source_promisor_name.buf);\n+\t}\n+\n+\terr = ferror(dest);\n+\terr |= fclose(dest);\n+\tif (err)\n+\t\tdie(_(\"Could not write '%s' promisor file\"), dest_promisor_name);\n+\n+\tclose_pack_index(dest_pack);\n+\tfree(dest_pack);\n+\tfree(dest_promisor_name);\n+\tfree(dest_idx_name);\n+\tstrbuf_release(&source_promisor_name);\n+\tstrbuf_release(&line);\n+\tstrset_clear(&seen_lines);\n+}\n+\n static void finish_repacking_promisor_objects(struct repository *repo,\n \t\t\t\t\t      struct child_process *cmd,\n \t\t\t\t\t      struct string_list *names,\n-- \n2.53.0.584.g6b87e8e9dd\n\n"},{"id":"541859","messageId":"cfa3fe43b027306805e7fcb5e7517d7dcbb473a8.1776384902.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1776384902.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v6 3/6] repack-promisor: preserve content of promisor files after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-18T14:17:08Z","receivedAt":"2026-04-18T14:17:13Z","isPatch":true,"body":"When a `repack` involving promisor packfiles happens, the new \".promisor\"\nfile is created empty, losing all the debug info that might be present\ninside the \".promisor\" files before the `repack`.\n\nUse the previously created \"write_promisor_file_after_repack()\" function\nto preserve the contents of all \".promisor\" files inside the \".promisor\"\nfiles created by the `repack`.\n\nFor geometric repacking, we have to create a `strset` that contains the\nbasenames of all excluded packs. For \"normal\" repacking this is not\nnecessary, since there should be no excluded packs.\n\nAlso, update the documentation accordingly.\n\nHelped-by: Tian Yuchen <cat@malon.dev>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n Documentation/git-repack.adoc |  4 ++--\n repack-promisor.c             | 39 ++++++++++++++++++-----------------\n 2 files changed, 22 insertions(+), 21 deletions(-)\n\ndiff --git a/Documentation/git-repack.adoc b/Documentation/git-repack.adoc\nindex 673ce91083..33d3c8afbd 100644\n--- a/Documentation/git-repack.adoc\n+++ b/Documentation/git-repack.adoc\n@@ -45,8 +45,8 @@ other objects in that pack they already have locally.\n +\n Promisor packfiles are repacked separately: if there are packfiles that\n have an associated \".promisor\" file, these packfiles will be repacked\n-into another separate pack, and an empty \".promisor\" file corresponding\n-to the new separate pack will be written.\n+into another separate pack, and a \".promisor\" file corresponding to the\n+new separate pack will be written (with arbitrary contents).\n \n -A::\n \tSame as `-a`, unless `-d` is used.  Then any unreachable\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 8fc541d2cf..06393ef06e 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -186,7 +186,8 @@ static void write_promisor_file_after_repack(struct repository *repo,\n static void finish_repacking_promisor_objects(struct repository *repo,\n \t\t\t\t\t      struct child_process *cmd,\n \t\t\t\t\t      struct string_list *names,\n-\t\t\t\t\t      const char *packtmp)\n+\t\t\t\t\t      const char *packtmp,\n+\t\t\t\t\t      struct strset *not_repacked_basenames)\n {\n \tstruct strbuf line = STRBUF_INIT;\n \tFILE *out;\n@@ -196,7 +197,6 @@ static void finish_repacking_promisor_objects(struct repository *repo,\n \tout = xfdopen(cmd->out, \"r\");\n \twhile (strbuf_getline_lf(&line, out) != EOF) {\n \t\tstruct string_list_item *item;\n-\t\tchar *promisor_name;\n \n \t\tif (line.len != repo->hash_algo->hexsz)\n \t\t\tdie(_(\"repack: Expecting full hex object ID lines only from pack-objects.\"));\n@@ -204,22 +204,16 @@ static void finish_repacking_promisor_objects(struct repository *repo,\n \n \t\t/*\n \t\t * pack-objects creates the .pack and .idx files, but not the\n-\t\t * .promisor file. Create the .promisor file, which is empty.\n-\t\t *\n-\t\t * NEEDSWORK: fetch-pack sometimes generates non-empty\n-\t\t * .promisor files containing the ref names and associated\n-\t\t * hashes at the point of generation of the corresponding\n-\t\t * packfile, but this would not preserve their contents. Maybe\n-\t\t * concatenate the contents of all .promisor files instead of\n-\t\t * just creating a new empty file.\n+\t\t * \".promisor\" file. To create the \"\".promisor\" file, we don't use the\n+\t\t * helper function write_promisor_file(), but instead we use the\n+\t\t * specific function write_promisor_file_after_repack(), which creates\n+\t\t * the file and appropriately fills it with the content of the\n+\t\t * \".promisor\" files used for the repack.\n \t\t */\n-\t\tpromisor_name = mkpathdup(\"%s-%s.promisor\", packtmp,\n-\t\t\t\t\t  line.buf);\n-\t\twrite_promisor_file(promisor_name, NULL, 0);\n+\t\twrite_promisor_file_after_repack(repo, line.buf, packtmp,\n+\t\t\t\t\t\tnot_repacked_basenames);\n \n \t\titem->util = generated_pack_populate(item->string, packtmp);\n-\n-\t\tfree(promisor_name);\n \t}\n \n \tfclose(out);\n@@ -256,7 +250,7 @@ void repack_promisor_objects(struct repository *repo,\n \t\treturn;\n \t}\n \n-\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp);\n+\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp, NULL);\n }\n \n void pack_geometry_repack_promisors(struct repository *repo,\n@@ -267,6 +261,7 @@ void pack_geometry_repack_promisors(struct repository *repo,\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tFILE *in;\n+\tstruct strset not_repacked_basenames = STRSET_INIT;\n \n \tif (!geometry->promisor_split)\n \t\treturn;\n@@ -280,9 +275,15 @@ void pack_geometry_repack_promisors(struct repository *repo,\n \tin = xfdopen(cmd.in, \"w\");\n \tfor (size_t i = 0; i < geometry->promisor_split; i++)\n \t\tfprintf(in, \"%s\\n\", pack_basename(geometry->promisor_pack[i]));\n-\tfor (size_t i = geometry->promisor_split; i < geometry->promisor_pack_nr; i++)\n-\t\tfprintf(in, \"^%s\\n\", pack_basename(geometry->promisor_pack[i]));\n+\tfor (size_t i = geometry->promisor_split; i < geometry->promisor_pack_nr; i++) {\n+\t\tconst char *name = pack_basename(geometry->promisor_pack[i]);\n+\t\tfprintf(in, \"^%s\\n\", name);\n+\t\tstrset_add(&not_repacked_basenames, name);\n+\t}\n \tfclose(in);\n \n-\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp);\n+\tfinish_repacking_promisor_objects(repo, &cmd, names, packtmp,\n+\t\t\tstrset_get_size(&not_repacked_basenames) ? &not_repacked_basenames : NULL);\n+\n+\tstrset_clear(&not_repacked_basenames);\n }\n-- \n2.53.0.584.g6b87e8e9dd\n\n"},{"id":"541860","messageId":"3a999ef230431473f5b79cff8e10ba0c1f072056.1776384902.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1776384902.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v6 4/6] t7700: test for promisor file content after repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-18T14:17:16Z","receivedAt":"2026-04-18T14:17:21Z","isPatch":true,"body":"Add tests that check if the content of \".promisor\" files are correctly\ncopied inside the \".promisor\" files created by a `repack`.\n\nThe `-f` flag is used when repacking to ensure that all the packs\n(created with `test_commit_bulk`) are repacked into a single new pack.\n\nHelped-by: Tian Yuchen <cat@malon.dev>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n t/t7700-repack.sh | 61 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 61 insertions(+)\n\ndiff --git a/t/t7700-repack.sh b/t/t7700-repack.sh\nindex 63ef63fc50..1decd7520a 100755\n--- a/t/t7700-repack.sh\n+++ b/t/t7700-repack.sh\n@@ -904,4 +904,65 @@ test_expect_success 'pending objects are repacked appropriately' '\n \t)\n '\n \n+test_expect_success 'check one .promisor file content after repack' '\n+\ttest_when_finished rm -rf prom_test prom_before_repack &&\n+\tgit init prom_test &&\n+\tpath=prom_test/.git/objects/pack &&\n+\n+\t(\n+\t\t# Create 1 pack\n+\t\ttest_commit_bulk -C prom_test 1 &&\n+\n+\t\t# Simulate .promisor file by creating it manually\n+\t\tprom=$(find $path -name \"*.pack\" | sed \"s/.pack$/.promisor/\") &&\n+\t\toid=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid ref\" >\"$prom\" &&\n+\n+\t\t# Repack, and check if correct\n+\t\tgit -C prom_test repack -a -d -f &&\n+\t\tprom=$(find $path -name \"*.promisor\") &&\n+\t\t# $prom should contain \"$oid ref <time>\"\n+\t\ttest_grep \"$oid ref \" \"$prom\" &&\n+\n+\t\t# Save the current .promisor content, repack, and check if correct\n+\t\tcp \"$prom\" prom_before_repack &&\n+\t\tgit -C prom_test repack -a -d -f &&\n+\t\tprom=$(find $path -name \"*.promisor\") &&\n+\t\t# $prom should be exactly the same as prom_before_repack\n+\t\ttest_cmp prom_before_repack \"$prom\"\n+\t)\n+'\n+\n+test_expect_success 'check multiple .promisor file content after repack' '\n+\ttest_when_finished rm -rf prom_test prom_before_repack &&\n+\tgit init prom_test &&\n+\tpath=prom_test/.git/objects/pack &&\n+\n+\t(\n+\t\t# Create 2 packs and simulate .promisor files by creating them manually\n+\t\ttest_commit_bulk -C prom_test 1 &&\n+\t\tprom=$(find $path -name \"*.pack\" | sed \"s/.pack$/.promisor/\") &&\n+\t\toid1=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid1 ref1\" >\"$prom\" &&\n+\t\ttest_commit_bulk -C prom_test 1 &&\n+\t\tprom=$(find $path -name \"*.pack\" | sed \"s/.pack$/.promisor/; \\|$prom|d\") &&\n+\t\toid2=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid2 ref2\" >\"$prom\" &&\n+\n+\t\t# Repack, and check if correct\n+\t\tgit -C prom_test repack -a -d -f &&\n+\t\tprom=$(find $path -name \"*.promisor\") &&\n+\t\t# $prom should contain \"$oid1 ref1 <time>\" & \"$oid2 ref2 <time>\"\n+\t\ttest_grep \"$oid1 ref1 \" \"$prom\" &&\n+\t\ttest_grep \"$oid2 ref2 \" \"$prom\" &&\n+\n+\t\t# Save the current .promisor content, repack, and check if correct\n+\t\tcp \"$prom\" prom_before_repack &&\n+\t\tgit -C prom_test repack -a -d -f &&\n+\t\tprom=$(find $path -name \"*.promisor\") &&\n+\t\t# $prom should be exactly the same as prom_before_repack\n+\t\ttest_cmp prom_before_repack \"$prom\"\n+\t)\n+'\n+\n test_done\n-- \n2.53.0.584.g6b87e8e9dd\n\n"},{"id":"541861","messageId":"e3d717283859c3123893882c40bda2395420d2a4.1776384902.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1776384902.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v6 5/6] t7703: test for promisor file content after geometric repack","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-18T14:17:23Z","receivedAt":"2026-04-18T14:17:27Z","isPatch":true,"body":"Add test that checks if the content of \".promisor\" files are correctly\ncopied inside the \".promisor\" files created by a geometric `repack`.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n t/t7703-repack-geometric.sh | 33 +++++++++++++++++++++++++++++++++\n 1 file changed, 33 insertions(+)\n\ndiff --git a/t/t7703-repack-geometric.sh b/t/t7703-repack-geometric.sh\nindex 04d5d8fc33..316247a3b9 100755\n--- a/t/t7703-repack-geometric.sh\n+++ b/t/t7703-repack-geometric.sh\n@@ -541,4 +541,37 @@ test_expect_success 'geometric repack works with promisor packs' '\n \t)\n '\n \n+test_expect_success 'check .promisor file content after geometric repack' '\n+\ttest_when_finished rm -rf prom_test &&\n+\tgit init prom_test &&\n+\tpath=prom_test/.git/objects/pack &&\n+\n+\t(\n+\t\t# Create 2 packs with 3 objs each, and manually create .promisor files\n+\t\ttest_commit_bulk -C prom_test --start=1 1 &&  # 3 objects\n+\t\tprom1=$(find $path -name \"*.pack\" | sed \"s/.pack$/.promisor/\") &&\n+\t\toid1=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid1 ref1\" >\"$prom1\" &&\n+\t\ttest_commit_bulk -C prom_test --start=2 1 &&  # 3 objects\n+\t\tprom2=$(find $path -name \"*.pack\" | sed \"s/.pack$/.promisor/; \\|$prom1|d\") &&\n+\t\toid2=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid2 ref2\" >\"$prom2\" &&\n+\n+\t\t# Create 1 pack with 12 objs, and manually create .promisor file\n+\t\ttest_commit_bulk -C prom_test --start=3 4 &&  # 12 objects\n+\t\tprom3=$(find $path -name \"*.pack\" | sed \"s/.pack$/.promisor/; \\|$prom1|d; \\|$prom2|d\") &&\n+\t\toid3=$(git -C prom_test rev-parse HEAD) &&\n+\t\techo \"$oid3 ref3\" >\"$prom3\" &&\n+\n+\t\t# Geometric repack, and check if correct\n+\t\tgit -C prom_test repack --geometric 2 -d &&\n+\t\tprom=$(find $path -name \"*.pack\" | sed \"s/.pack$/.promisor/; \\|$prom3|d\") &&\n+\t\t# $prom should have repacked only the first 2 small packs, so it should only\n+\t\t# contain the following: \"$oid1 ref1 <time>\" & \"$oid2 ref2 <time>\"\n+\t\ttest_grep \"$oid1 ref1 \" \"$prom\" &&\n+\t\ttest_grep \"$oid2 ref2 \" \"$prom\" &&\n+\t\ttest_grep ! \"$oid3 ref3\" \"$prom\"\n+\t)\n+'\n+\n test_done\n-- \n2.53.0.584.g6b87e8e9dd\n\n"},{"id":"541862","messageId":"6b87e8e9dd794ebb968461785cc3e496aa1fe996.1776384902.git.lorenzo.pegorari2002@gmail.com","threadId":"65326","inReplyTo":"cover.1776384902.git.lorenzo.pegorari2002@gmail.com","subject":"[GSoC PATCH v6 6/6] repack-promisor: add missing headers","fromName":"LorenzoPegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-04-18T14:17:29Z","receivedAt":"2026-04-18T14:17:33Z","isPatch":true,"body":"According to the coding guidelines, a C file must directly include the\nheader files that declare the facilities it uses.\n\nDirectly include these missing headers, in order to comply with the\ncoding guidelines.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: LorenzoPegorari <lorenzo.pegorari2002@gmail.com>\n---\n repack-promisor.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 06393ef06e..472aef0081 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -1,12 +1,18 @@\n #include \"git-compat-util.h\"\n #include \"repack.h\"\n+#include \"hash.h\"\n #include \"hex.h\"\n+#include \"odb.h\"\n #include \"pack.h\"\n #include \"packfile.h\"\n #include \"path.h\"\n #include \"refs.h\"\n #include \"repository.h\"\n #include \"run-command.h\"\n+#include \"strbuf.h\"\n+#include \"string-list.h\"\n+#include \"strmap.h\"\n+#include \"strvec.h\"\n \n struct write_oid_context {\n \tstruct child_process *cmd;\n-- \n2.53.0.584.g6b87e8e9dd\n\n"},{"id":"543146","messageId":"xmqqse7xm8av.fsf@gitster.g","threadId":"65326","inReplyTo":"cover.1776384902.git.lorenzo.pegorari2002@gmail.com","subject":"Re: [GSoC PATCH v6 0/6] preserve promisor files content after repack","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-12T06:49:28Z","receivedAt":"2026-05-12T06:49:32Z","isPatch":true,"body":"LorenzoPegorari <lorenzo.pegorari2002@gmail.com> writes:\n\n> The goal of this patch is to solve the NEEDSWORK comment added by\n> 5374a290 (fetch-pack: write fetched refs to .promisor, 14/10/2019). This\n> is done by adding a helper function that takes the content of all\n> .promisor files in the `repository`, and copies it inside the first\n> .promisor file created by the repack.\n>\n> Also, I added a comment explaining what is the purpose of the content of\n> the .promisor files, since this wasn't explained anywhere (I found\n> information regarding this only in the message of the previously cited\n> commit).\n>\n> Finally, I added some tests to \"t7700-repack.sh\" and\n> \"t7703-repack-geometric.sh\" that check if the content of .promisor files\n> are correctly copied into the .promisor files created by a repack.\n>\n> V6 DIFF:\n>  * changed the name of the helper function to\n>    `write_promisor_file_after_repack`.\n>  * modified the helper function to create the \".promisor\" file, so that\n>    is not required anymore.\n>  * modified the logic of the helper function (as suggested by Tian\n>    Yuchen)\n>  * modified the helper function to check for possible errors, and to\n>    check if the lines of the \".promisor\" files are correctly formed.\n>  * fixed memory leak.\n>  * improved comments.\n>\n> LorenzoPegorari (6):\n>   pack-write: add explanation to promisor file content\n>   repack-promisor add helper to fill promisor file after repack\n>   repack-promisor: preserve content of promisor files after repack\n>   t7700: test for promisor file content after repack\n>   t7703: test for promisor file content after geometric repack\n>   repack-promisor: add missing headers\n>\n>  Documentation/git-repack.adoc |   4 +-\n>  pack-write.c                  |   9 ++\n>  repack-promisor.c             | 194 ++++++++++++++++++++++++++++++----\n>  t/t7700-repack.sh             |  61 +++++++++++\n>  t/t7703-repack-geometric.sh   |  33 ++++++\n>  5 files changed, 280 insertions(+), 21 deletions(-)\n\n\nLorenzo, it seems that not many people are reviewing this final\nround, and then I noticed that the list of CC addresses lacks a big\nname in the promisor remote topic, so I added Christian to the To:\nline of this message.  Christian, you have no obligation to review\nthese patches if they do not interest you, but just in case you\nweren't aware of this effort, I thought it might interest you; I am\nsure we all would benefit from your expertise.\n\nThanks.\n"},{"id":"543641","messageId":"agx_GPfBKpkSc3Gx@lorenzo-VM","threadId":"65326","inReplyTo":"xmqqse7xm8av.fsf@gitster.g","subject":"Re: [GSoC PATCH v6 0/6] preserve promisor files content after repack","fromName":"Lorenzo Pegorari","fromEmail":"lorenzo.pegorari2002@gmail.com","sentAt":"2026-05-19T15:17:44Z","receivedAt":"2026-05-19T15:17:49Z","isPatch":true,"body":"On Tue, May 12, 2026 at 03:49:28PM +0900, Junio C Hamano wrote:\n> Lorenzo, it seems that not many people are reviewing this final\n> round, and then I noticed that the list of CC addresses lacks a big\n> name in the promisor remote topic, so I added Christian to the To:\n> line of this message.  Christian, you have no obligation to review\n> these patches if they do not interest you, but just in case you\n> weren't aware of this effort, I thought it might interest you; I am\n> sure we all would benefit from your expertise.\n> \n> Thanks.\n\nYeah, it does seem that way. This patch series is really messy, and\ngenerated way too much noise, so I don't blame them, and I'm sorry about\nthat.\n\nI still learned a ton, so I'm happy about that. In the mean time I will\nstick to solving simpler issues.\n\nThanks for your patience,\n\nLorenzo\n"}]}