git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v4 8/8] builtin/repack.c: add '--geometric' option

From
Junio C Hamano <gitster@pobox.com>
Date
Feb 24, 2021, 23:19 UTC
Message-ID
<xmqqv9ahxddp.fsf@gitster.g>
In-Reply-To
<51f57d5da23244ebde27ad6c14cbf4b63da3317d.1614047097.git.me@ttaylorr.com>
Taylor Blau <me@ttaylorr.com> writes:
Show 17 quoted lines
> Concretely, say that a repository has 'n' packfiles, labeled P1, P2,
> ..., up to Pn. Each packfile has an object count equal to 'objects(Pn)'.
> With a geometric factor of 'r', it should be that:
>
>   objects(Pi) > r*objects(P(i-1))
>
> for all i in [1, n], where the packs are sorted by
>
>   objects(P1) <= objects(P2) <= ... <= objects(Pn).
>
> Since finding a true optimal repacking is NP-hard, we approximate it
> along two directions:
>
>   1. We assume that there is a cutoff of packs _before starting the
>      repack_ where everything to the right of that cut-off already forms
>      a geometric progression (or no cutoff exists and everything must be
>      repacked).

When you order existing packs like how you explained the next "direction" below, do we assume loose ones would sit before (i.e. "newer and smaller" than) all of the packs?

Show 21 quoted lines
>   2. We assume that everything smaller than the cutoff count must be
>      repacked. This forms our base assumption, but it can also cause
>      even the "heavy" packs to get repacked, for e.g., if we have 6
>      packs containing the following number of objects:
>
>        1, 1, 1, 2, 4, 32
>
>      then we would place the cutoff between '1, 1' and '1, 2, 4, 32',
>      rolling up the first two packs into a pack with 2 objects. That
>      breaks our progression and leaves us:
>
>        2, 1, 2, 4, 32
>          ^
>
>      (where the '^' indicates the position of our split). To restore a
>      progression, we move the split forward (towards larger packs)
>      joining each pack into our new pack until a geometric progression
>      is restored. Here, that looks like:
>
>        2, 1, 2, 4, 32  ~>  3, 2, 4, 32  ~>  5, 4, 32  ~> ... ~> 9, 32
>          ^                   ^                ^                   ^

This explanation is very intuitive and easy to understand (I assume we aren't actually repacking 1+1 into 2 and then 2+1 into 3 and then choosing to repack 3+2 to create 5, but we scan before doing any repacking and decide to repack 2+1+2+4 into a single 9).

What is not so clear is how this picture changes depending on the value of 'r'.

> ... Another wrinkle
> is that we assume that loose, indexed, and reflog'd objects are
> insignificant, and lump them into any new pack that we create.

In the example of 2 above, these are treated as insignificant compared to the first '1' in the 1+1+1+2+4+32, so the choice of repacked packs are made by computing 1+1+1+2+4 and noticing that is where we should stop, but we pack these insignificant ones together with these repacked packs into the new pack that is supposed to contain "9" objects?

> This can
> lead to non-idempotent results.
Let me try to follow aloud to see if I got this right.

If we start from 1+1+1+2+4+32+... (similarly to the example given to explain 2 above, but with more larger packs---but the assumption here is that everything larger than 32 is already in good progression), depending on how many loose objects we have, the result of packing 1+1+1+2+4+loose might not necessarily be 9 but 100 (collecting too many loose objects), and the set of packs would be 32+... (from before the "repack -g") plus a 100-object pack, not 9+32+... as the above explanation for 2 suggested. Starting from that state, re-running "repack -g" again would then have to repack the packs existed before the first repack (i.e. 32+...) into one. In other words, the second "git repack -g" in back-to-back "git repack -g && git repack -g" may necessarily be a no-op.

Is that what you meant by non-idempotent?
Show 55 quoted lines
> diff --git a/builtin/repack.c b/builtin/repack.c
> index 01440de2d5..bcf280b10d 100644
> --- a/builtin/repack.c
> +++ b/builtin/repack.c
> @@ -297,6 +297,124 @@ static void repack_promisor_objects(const struct pack_objects_args *args,
>  #define ALL_INTO_ONE 1
>  #define LOOSEN_UNREACHABLE 2
>  
> +struct pack_geometry {
> +	struct packed_git **pack;
> +	uint32_t pack_nr, pack_alloc;
> +	uint32_t split;
> +};
> +
> +static uint32_t geometry_pack_weight(struct packed_git *p)
> +{
> +	if (open_pack_index(p))
> +		die(_("cannot open index for %s"), p->pack_name);
> +	return p->num_objects;
> +}
> +
> +static int geometry_cmp(const void *va, const void *vb)
> +{
> +	uint32_t aw = geometry_pack_weight(*(struct packed_git **)va),
> +		 bw = geometry_pack_weight(*(struct packed_git **)vb);
> +
> +	if (aw < bw)
> +		return -1;
> +	if (aw > bw)
> +		return 1;
> +	return 0;
> +}
> +
> +static void init_pack_geometry(struct pack_geometry **geometry_p)
> +{
> +	struct packed_git *p;
> +	struct pack_geometry *geometry;
> +
> +	*geometry_p = xcalloc(1, sizeof(struct pack_geometry));
> +	geometry = *geometry_p;
> +
> +	for (p = get_all_packs(the_repository); p; p = p->next) {
> +		if (!pack_kept_objects && p->pack_keep)
> +			continue;
> +
> +		ALLOC_GROW(geometry->pack,
> +			   geometry->pack_nr + 1,
> +			   geometry->pack_alloc);
> +
> +		geometry->pack[geometry->pack_nr] = p;
> +		geometry->pack_nr++;
> +	}
> +
> +	QSORT(geometry->pack, geometry->pack_nr, geometry_cmp);
> +}

After calling this helper, we get geometry->pack[] that is sorted by the number of objects in each pack, packs with fewer objects sort before the ones with more objects. OK.

Show 10 quoted lines
> +static void split_pack_geometry(struct pack_geometry *geometry, int factor)
> +{
> +	uint32_t i;
> +	uint32_t split;
> +	off_t total_size = 0;
> +
> +	if (geometry->pack_nr <= 1) {
> +		geometry->split = geometry->pack_nr;
> +		return;
> +	}

When there is a single pack (or no pack), we place the split to 1 (let's keep reading with the need to find out what split means in mind; it is not yet clear if it points at the pack that will be part of the kept set, or at the pack that is the last one among the repacked set, at this point in the code).

Show 14 quoted lines
> +	split = geometry->pack_nr - 1;
> +
> +	/*
> +	 * First, count the number of packs (in descending order of size) which
> +	 * already form a geometric progression.
> +	 */
> +	for (i = geometry->pack_nr - 1; i > 0; i--) {
> +		struct packed_git *ours = geometry->pack[i];
> +		struct packed_git *prev = geometry->pack[i - 1];
> +		if (geometry_pack_weight(ours) >= factor * geometry_pack_weight(prev))
> +			split--;
> +		else
> +			break;
> +	}

Instead of rolling up from smaller ones like explained in the log message, we scan from the larger end and see where the existing progression is broken. When the loop breaks in the middle, the pack at position 'i-1' (prev) is too big.

Why do we need to initialize 'split' before the loop and decrement it? Wouldn't it be equivalent to assign 'i' after the loop breaks to 'split'?

In any case, after the loop breaks, the packs starting at position 'i+1' (one after ours when the loop broke) thru to the end of the geometry->pack[] array are in good progression. We have 'i' in 'split' at this point, so ...

Show 9 quoted lines
> +	if (split) {
> +		/*
> +		 * Move the split one to the right, since the top element in the
> +		 * last-compared pair can't be in the progression. Only do this
> +		 * when we split in the middle of the array (otherwise if we got
> +		 * to the end, then the split is in the right place).
> +		 */
> +		split++;
> +	}

... we increment it. It means geometry->pack[split] is small enough relative to geometry->pack[split+1] and so on thru to the end of the array.

What if split==0 when we exited the loop? That would mean that the everything in the array was in good progression, which is in line with the "in the middle" case. Either way, the pack at 'split' and later are in good progression.

Show 11 quoted lines
> +	/*
> +	 * Then, anything to the left of 'split' must be in a new pack. But,
> +	 * creating that new pack may cause packs in the heavy half to no longer
> +	 * form a geometric progression.
> +	 *
> +	 * Compute an expected size of the new pack, and then determine how many
> +	 * packs in the heavy half need to be joined into it (if any) to restore
> +	 * the geometric progression.
> +	 */
> +	for (i = 0; i < split; i++)
> +		total_size += geometry_pack_weight(geometry->pack[i]);

We guestimate the number of objects in the rolled-up pack to be created. Some objects may appear in multiple packs, but the number of them ought to be insignificant. OK.

> +	for (i = split; i < geometry->pack_nr; i++) {
> +		struct packed_git *ours = geometry->pack[i];
> +		if (geometry_pack_weight(ours) < factor * total_size) {

If the pack at the bottom end of the range we previously thought to keep turns out to be too small, we'd also roll that one in, by shifting the split point to the right. And of course we update the expected size of the new pack. OK.

Show 7 quoted lines
> +			split++;
> +			total_size += geometry_pack_weight(ours);
> +		} else
> +			break;
> +	}
> +
> +	geometry->split = split;

The code makes me wonder if we can compute all of the above in a single pass, but that is purely an intellectual curiosity. The logic in the code is crystal clear (the "what if everything was already in a good progression" case was the only part that made me stop and think about the correctness of the logic) and the implementation looks good, except for a few small nits:

 - why initialize 'split' so early before the first loop, which I
   already mentioned.
 - we know many numbers are in uint32_t because that is how
   packfiles limit their contents, but is it safe to perform the
   multiplication with factor and comparison in that type?
Show 52 quoted lines
>  int cmd_repack(int argc, const char **argv, const char *prefix)
>  {
>  	struct child_process cmd = CHILD_PROCESS_INIT;
> @@ -304,6 +422,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
>  	struct string_list names = STRING_LIST_INIT_DUP;
>  	struct string_list rollback = STRING_LIST_INIT_NODUP;
>  	struct string_list existing_packs = STRING_LIST_INIT_DUP;
> +	struct pack_geometry *geometry = NULL;
>  	struct strbuf line = STRBUF_INIT;
>  	int i, ext, ret;
>  	FILE *out;
> @@ -316,6 +435,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
>  	struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
>  	int no_update_server_info = 0;
>  	struct pack_objects_args po_args = {NULL};
> +	int geometric_factor = 0;
>  
>  	struct option builtin_repack_options[] = {
>  		OPT_BIT('a', NULL, &pack_everything,
> @@ -356,6 +476,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
>  				N_("repack objects in packs marked with .keep")),
>  		OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"),
>  				N_("do not repack this pack")),
> +		OPT_INTEGER('g', "geometric", &geometric_factor,
> +			    N_("find a geometric progression with factor <N>")),
>  		OPT_END()
>  	};
>  
> @@ -382,6 +504,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
>  	if (write_bitmaps && !(pack_everything & ALL_INTO_ONE))
>  		die(_(incremental_bitmap_conflict_error));
>  
> +	if (geometric_factor) {
> +		if (pack_everything)
> +			die(_("--geometric is incompatible with -A, -a"));
> +		init_pack_geometry(&geometry);
> +		split_pack_geometry(geometry, geometric_factor);
> +	}
> +
>  	packdir = mkpathdup("%s/pack", get_object_directory());
>  	packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid());
>  
> @@ -396,9 +525,19 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
>  		strvec_pushf(&cmd.args, "--keep-pack=%s",
>  			     keep_pack_list.items[i].string);
>  	strvec_push(&cmd.args, "--non-empty");
> -	strvec_push(&cmd.args, "--all");
> -	strvec_push(&cmd.args, "--reflog");
> -	strvec_push(&cmd.args, "--indexed-objects");
> +	if (!geometry) {
> +		/*
> +		 * 'git pack-objects' will up all objects loose or packed

"git pack-objects --stdin-packs" will? What verb is missing in "will VERB up all objects"?

Show 5 quoted lines
> +		 * (either rolling them up or leaving them alone), so don't pass
> +		 * these options.
> +		 *
> +		 * The implementation of 'git pack-objects --stdin-packs'
> +		 * makes them redundant (and the two are incompatible).
I am not sure if that is true.

More importantly, if you read this comment after you are done with the series and no longer feel that geometric repacking is the most important thing in the world, you'd realize that an important piece of information is missing to help readers. It talks about what "geometric" code does (i.e. uses --stdin-packs hence no need to pass these options) in a block that is for !geometric.

	We need to grab all reachable objects, including those that
	are reachable from reflogs and the index.
	When repacking into a geometric progression of packs,
	however, we ask 'git pack-objects --stdin-packs', and it is
	not about packing objects based on reachability but about
	repacking all the objects in specified packs and loose ones
	(indeed, --stdin-packs is incompatible with these options).

or something? I suspect that --stdin-packs does not make --all and others "redundant". The operation is about creating a new pack out of the objects contained in these packs, regardless of the objects' reachability from the usual "refs, index and reflogs" anchor points, no?

Show 25 quoted lines
> +		 */
> +		strvec_push(&cmd.args, "--all");
> +		strvec_push(&cmd.args, "--reflog");
> +		strvec_push(&cmd.args, "--indexed-objects");
> +	}
>  	if (has_promisor_remote())
>  		strvec_push(&cmd.args, "--exclude-promisor-objects");
>  	if (write_bitmaps > 0)
> @@ -429,17 +568,37 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
>  				strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
>  			}
>  		}
> +	} else if (geometry) {
> +		strvec_push(&cmd.args, "--stdin-packs");
> +		strvec_push(&cmd.args, "--unpacked");
>  	} else {
>  		strvec_push(&cmd.args, "--unpacked");
>  		strvec_push(&cmd.args, "--incremental");
>  	}
>  
> -	cmd.no_stdin = 1;
> +	if (geometry)
> +		cmd.in = -1;
> +	else
> +		cmd.no_stdin = 1;

It is a bit sad that we need to do this before start_command() in that the code structure does not make it clear why two modes have different handling of the standard input stream, but I do not think of anything better, so I'll let it pass.

Show 44 quoted lines
>  	ret = start_command(&cmd);
>  	if (ret)
>  		return ret;
>  
> +	if (geometry) {
> +		FILE *in = xfdopen(cmd.in, "w");
> +		/*
> +		 * The resulting pack should contain all objects in packs that
> +		 * are going to be rolled up, but exclude objects in packs which
> +		 * are being left alone.
> +		 */
> +		for (i = 0; i < geometry->split; i++)
> +			fprintf(in, "%s\n", pack_basename(geometry->pack[i]));
> +		for (i = geometry->split; i < geometry->pack_nr; i++)
> +			fprintf(in, "^%s\n", pack_basename(geometry->pack[i]));
> +		fclose(in);
> +	}
> +
>  	out = xfdopen(cmd.out, "r");
>  	while (strbuf_getline_lf(&line, out) != EOF) {
>  		if (line.len != the_hash_algo->hexsz)
> @@ -507,6 +666,25 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
>  			if (!string_list_has_string(&names, sha1))
>  				remove_redundant_pack(packdir, item->string);
>  		}
> +
> +		if (geometry) {
> +			struct strbuf buf = STRBUF_INIT;
> +
> +			uint32_t i;
> +			for (i = 0; i < geometry->split; i++) {
> +				struct packed_git *p = geometry->pack[i];
> +				if (string_list_has_string(&names,
> +							   hash_to_hex(p->hash)))
> +					continue;
> +
> +				strbuf_reset(&buf);
> +				strbuf_addstr(&buf, pack_basename(p));
> +				strbuf_strip_suffix(&buf, ".pack");
> +
> +				remove_redundant_pack(packdir, buf.buf);
> +			}
> +			strbuf_release(&buf);
> +		}

Before this new code, we seem to remove all pre-existing packfiles that are not in the output from the pack-objects already. The only reason that code does not harm the geometry case is we assume get_non_kept_pack_filenames() call is never made while doing geometric repack (iow, ALL_INTO_ONE is not set) and the list of pre-existing packfiles &existing_packs is empty. Am I reading the code correctly?

 - It is a bit unnerving to learn (and it will be a maintenance
   burden in the future) that a variable whose name is
   existing_packs does not necessarily have a list of existing packs
   depending on the mode we are operating in.
 - The guard to make geometric incompatible with ALL_INTO_ONE does
   not mention ALL_INTO_ONE, even though that bit is what would
   corrupt the resulting repository if overlooked.  We should
   probably need s/pack_everything/& \& ALL_INTO_ONE/ in the hunk
    below.
Show 14 quoted lines
> @@ -382,6 +504,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
>  	if (write_bitmaps && !(pack_everything & ALL_INTO_ONE))
>  		die(_(incremental_bitmap_conflict_error));
>  
> +	if (geometric_factor) {
> +		if (pack_everything)
> +			die(_("--geometric is incompatible with -A, -a"));
> +		init_pack_geometry(&geometry);
> +		split_pack_geometry(geometry, geometric_factor);
> +	}
> +
>  	packdir = mkpathdup("%s/pack", get_object_directory());
>  	packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid());
>  
Other than that, it was a fun patch to read.
Thanks.
Previous: Taylor BlauNext: Junio C Hamano
Message 105 of 120 in “repack: support repacking into a geometric sequence”
  1. 00/10 repack: support repacking into a geometric sequenceTaylor Blau, Jan 19, 2021
  2. 02/10 revision: learn '--no-kept-objects'Taylor Blau, Jan 19, 2021
  3. Junio C HamanoJan 29, 2021
  4. Taylor BlauJan 29, 2021
  5. 01/10 packfile: introduce 'find_kept_pack_entry()'Taylor Blau, Jan 19, 2021
  6. Derrick StoleeJan 20, 2021
  7. Taylor BlauJan 20, 2021
  8. Junio C HamanoJan 29, 2021
  9. Taylor BlauJan 29, 2021
  10. Jeff KingJan 29, 2021
  11. Junio C HamanoJan 29, 2021
  12. 07/10 packfile: add kept-pack cache for find_kept_pack_entry()Taylor Blau, Jan 19, 2021
  13. 06/10 pack-objects: rewrite honor-pack-keep logicTaylor Blau, Jan 19, 2021
  14. 05/10 p5303: measure time to repack with keepTaylor Blau, Jan 19, 2021
  15. Junio C HamanoJan 29, 2021
  16. Jeff KingJan 29, 2021
  17. p5303: avoid sed GNU-ismJeff King, Jan 29, 2021
  18. Eric SunshineJan 29, 2021
  19. Jeff KingJan 29, 2021
  20. Eric SunshineJan 29, 2021
  21. Taylor BlauJan 29, 2021
  22. Junio C HamanoJan 29, 2021
  23. Jeff KingJan 29, 2021
  24. Junio C HamanoJan 29, 2021
  25. 08/10 builtin/pack-objects.c: teach '--keep-pack-stdin'Taylor Blau, Jan 19, 2021
  26. 10/10 builtin/repack.c: add '--geometric' optionTaylor Blau, Jan 19, 2021
  27. 03/10 builtin/pack-objects.c: learn '--assume-kept-packs-closed'Taylor Blau, Jan 19, 2021
  28. Junio C HamanoJan 29, 2021
  29. Jeff KingJan 29, 2021
  30. Taylor BlauJan 29, 2021
  31. Jeff KingJan 29, 2021
  32. Taylor BlauJan 29, 2021
  33. Jeff KingJan 29, 2021
  34. Junio C HamanoJan 29, 2021
  35. Taylor BlauJan 29, 2021
  36. Taylor BlauFeb 2, 2021
  37. Jeff KingJan 29, 2021
  38. Junio C HamanoJan 29, 2021
  39. Junio C HamanoJan 29, 2021
  40. Jeff KingJan 29, 2021
  41. Taylor BlauJan 29, 2021
  42. Jeff KingJan 29, 2021
  43. Junio C HamanoJan 29, 2021
  44. 09/10 builtin/repack.c: extract loose object handlingTaylor Blau, Jan 19, 2021
  45. Derrick StoleeJan 20, 2021
  46. Taylor BlauJan 20, 2021
  47. Derrick StoleeJan 20, 2021
  48. Junio C HamanoJan 21, 2021
  49. 04/10 p5303: add missing &&-chainsTaylor Blau, Jan 19, 2021
  50. Derrick StoleeJan 20, 2021
  51. 0/8 repack: support repacking into a geometric sequenceTaylor Blau, Feb 4, 2021
  52. 4/8 p5303: add missing &&-chainsTaylor Blau, Feb 4, 2021
  53. 3/8 builtin/pack-objects.c: add '--stdin-packs' optionTaylor Blau, Feb 4, 2021
  54. Jeff KingFeb 16, 2021
  55. Taylor BlauFeb 17, 2021
  56. Jeff KingFeb 17, 2021
  57. 2/8 revision: learn '--no-kept-objects'Taylor Blau, Feb 4, 2021
  58. Jeff KingFeb 16, 2021
  59. Taylor BlauFeb 17, 2021
  60. 1/8 packfile: introduce 'find_kept_pack_entry()'Taylor Blau, Feb 4, 2021
  61. Jeff KingFeb 16, 2021
  62. Taylor BlauFeb 16, 2021
  63. 5/8 p5303: measure time to repack with keepTaylor Blau, Feb 4, 2021
  64. Jeff KingFeb 16, 2021
  65. Jeff KingFeb 17, 2021
  66. Taylor BlauFeb 17, 2021
  67. Jeff KingFeb 17, 2021
  68. 7/8 packfile: add kept-pack cache for find_kept_pack_entry()Taylor Blau, Feb 4, 2021
  69. Jeff KingFeb 17, 2021
  70. Taylor BlauFeb 17, 2021
  71. Jeff KingFeb 17, 2021
  72. Taylor BlauFeb 17, 2021
  73. Jeff KingFeb 17, 2021
  74. 8/8 builtin/repack.c: add '--geometric' optionTaylor Blau, Feb 4, 2021
  75. Jeff KingFeb 17, 2021
  76. Taylor BlauFeb 17, 2021
  77. 6/8 builtin/pack-objects.c: rewrite honor-pack-keep logicTaylor Blau, Feb 4, 2021
  78. Jeff KingFeb 17, 2021
  79. Taylor BlauFeb 17, 2021
  80. Jeff KingFeb 17, 2021
  81. Jeff KingFeb 17, 2021
  82. Jeff KingFeb 17, 2021
  83. 0/8 repack: support repacking into a geometric sequenceTaylor Blau, Feb 18, 2021
  84. 1/8 packfile: introduce 'find_kept_pack_entry()'Taylor Blau, Feb 18, 2021
  85. 2/8 revision: learn '--no-kept-objects'Taylor Blau, Feb 18, 2021
  86. 4/8 p5303: add missing &&-chainsTaylor Blau, Feb 18, 2021
  87. 3/8 builtin/pack-objects.c: add '--stdin-packs' optionTaylor Blau, Feb 18, 2021
  88. 6/8 builtin/pack-objects.c: rewrite honor-pack-keep logicTaylor Blau, Feb 18, 2021
  89. 5/8 p5303: measure time to repack with keepTaylor Blau, Feb 18, 2021
  90. 7/8 packfile: add kept-pack cache for find_kept_pack_entry()Taylor Blau, Feb 18, 2021
  91. 8/8 builtin/repack.c: add '--geometric' optionTaylor Blau, Feb 18, 2021
  92. Jeff KingFeb 23, 2021
  93. Taylor BlauFeb 23, 2021
  94. Jeff KingFeb 23, 2021
  95. 0/8 repack: support repacking into a geometric sequenceTaylor Blau, Feb 23, 2021
  96. 1/8 packfile: introduce 'find_kept_pack_entry()'Taylor Blau, Feb 23, 2021
  97. 2/8 revision: learn '--no-kept-objects'Taylor Blau, Feb 23, 2021
  98. 3/8 builtin/pack-objects.c: add '--stdin-packs' optionTaylor Blau, Feb 23, 2021
  99. Junio C HamanoFeb 23, 2021
  100. Jeff KingFeb 23, 2021
  101. 4/8 p5303: add missing &&-chainsTaylor Blau, Feb 23, 2021
  102. 6/8 builtin/pack-objects.c: rewrite honor-pack-keep logicTaylor Blau, Feb 23, 2021
  103. 5/8 p5303: measure time to repack with keepTaylor Blau, Feb 23, 2021
  104. 8/8 builtin/repack.c: add '--geometric' optionTaylor Blau, Feb 23, 2021
  105. Junio C HamanoFeb 24, 2021
  106. Junio C HamanoFeb 24, 2021
  107. Taylor BlauMar 4, 2021
  108. Taylor BlauMar 4, 2021
  109. 7/8 packfile: add kept-pack cache for find_kept_pack_entry()Taylor Blau, Feb 23, 2021
  110. Jeff KingFeb 23, 2021
  111. Junio C HamanoFeb 23, 2021
  112. Jeff KingFeb 23, 2021
  113. Martin FickFeb 23, 2021
  114. Taylor BlauFeb 23, 2021
  115. Martin FickFeb 23, 2021
  116. Jeff KingFeb 23, 2021
  117. Martin FickFeb 23, 2021
  118. Jeff KingFeb 23, 2021
  119. Martin FickFeb 24, 2021
  120. Jeff KingFeb 26, 2021

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.