git/builtin
Derrick Stolee a4c22c16fa pack-objects: add --full-name-hash option
The pack_name_hash() method has not been materially changed since it was
introduced in ce0bd64299 (pack-objects: improve path grouping
heuristics., 2006-06-05). The intention here is to group objects by path
name, but also attempt to group similar file types together by making
the most-significant digits of the hash be focused on the final
characters.

Here's the crux of the implementation:

	/*
	 * This effectively just creates a sortable number from the
	 * last sixteen non-whitespace characters. Last characters
	 * count "most", so things that end in ".c" sort together.
	 */
	while ((c = *name++) != 0) {
		if (isspace(c))
			continue;
		hash = (hash >> 2) + (c << 24);
	}

As the comment mentions, this only cares about the last sixteen
non-whitespace characters. This cause some filenames to collide more
than others. Here are some examples that I've seen while investigating
repositories that are growing more than they should be:

 * "/CHANGELOG.json" is 15 characters, and is created by the beachball
   [1] tool. Only the final character of the parent directory can
   differntiate different versions of this file, but also only the two
   most-significant digits. If that character is a letter, then this is
   always a collision. Similar issues occur with the similar
   "/CHANGELOG.md" path, though there is more opportunity for
   differences in the parent directory.

 * Localization files frequently have common filenames but differentiate
   via parent directories. In C#, the name "/strings.resx.lcl" is used
   for these localization files and they will all collide in name-hash.

[1] https://github.com/microsoft/beachball

I've come across many other examples where some internal tool uses a
common name across multiple directories and is causing Git to repack
poorly due to name-hash collisions.

It is clear that the existing name-hash algorithm is optimized for
repositories with short path names, but also is optimized for packing a
single snapshot of a repository, not a repository with many versions of
the same file. In my testing, this has proven out where the name-hash
algorithm does a good job of finding peer files as delta bases when
unable to use a historical version of that exact file.

However, for repositories that have many versions of most files and
directories, it is more important that the objects that appear at the
same path are grouped together.

Create a new pack_full_name_hash() method and a new --full-name-hash
option for 'git pack-objects' to call that method instead. Add a simple
pass-through for 'git repack --full-name-hash' for additional testing in
the context of a full repack, where I expect this will be most
effective.

The hash algorithm is as simple as possible to be reasonably effective:
for each character of the path string, add a multiple of that character
and a large prime number (chosen arbitrarily, but intended to be large
relative to the size of a uint32_t). Then, shift the current hash value
to the right by 5, with overlap. The addition and shift parameters are
standard mechanisms for creating hard-to-predict behaviors in the bits
of the resulting hash.

This is not meant to be cryptographic at all, but uniformly distributed
across the possible hash values. This creates a hash that appears
pseudorandom. There is no ability to consider similar file types as
being close to each other.

In a later change, a test-tool will be added so the effectiveness of
this hash can be demonstrated directly.

For now, let's consider how effective this mechanism is when repacking a
repository with and without the --full-name-hash option. Specifically,
let's use 'git repack -adf [--full-name-hash]' as our test.

On the Git repository, we do not expect much difference. All path names
are short. This is backed by our results:

| Stage                 | Pack Size | Repack Time |
|-----------------------|-----------|-------------|
| After clone           | 260 MB    | N/A         |
| Standard Repack       | 127MB     | 106s        |
| With --full-name-hash | 126 MB    | 99s         |

This example demonstrates how there is some natural overhead coming from
the cloned copy because the server is hosting many forks and has not
optimized for exactly this set of reachable objects. But the full repack
has similar characteristics with and without --full-name-hash.

However, we can test this in a repository that uses one of the
problematic naming conventions above. The fluentui [2] repo uses
beachball to generate CHANGELOG.json and CHANGELOG.md files, and these
files have very poor delta characteristics when comparing against
versions across parent directories.

| Stage                 | Pack Size | Repack Time |
|-----------------------|-----------|-------------|
| After clone           | 694 MB    | N/A         |
| Standard Repack       | 438 MB    | 728s        |
| With --full-name-hash | 168 MB    | 142s        |

[2] https://github.com/microsoft/fluentui

In this example, we see significant gains in the compressed packfile
size as well as the time taken to compute the packfile.

Using a collection of repositories that use the beachball tool, I was
able to make similar comparisions with dramatic results. While the
fluentui repo is public, the others are private so cannot be shared for
reproduction. The results are so significant that I find it important to
share here:

| Repo     | Standard Repack | With --full-name-hash |
|----------|-----------------|-----------------------|
| fluentui |         438 MB  |               168 MB  |
| Repo B   |       6,255 MB  |               829 MB  |
| Repo C   |      37,737 MB  |             7,125 MB  |
| Repo D   |     130,049 MB  |             6,190 MB  |

Future changes could include making --full-name-hash implied by a config
value or even implied by default during a full repack.

Signed-off-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2024-09-19 14:43:00 -07:00
..
add.c add-i: finally retire add.interactive.useBuiltin 2024-06-05 14:53:26 -07:00
am.c hooks: remove implicit dependency on the_repository 2024-08-13 10:01:01 -07:00
annotate.c
apply.c apply: fix uninitialized hash function 2024-05-21 09:07:48 -07:00
archive.c builtin/archive: fix leaking OPT_FILENAME() value 2024-08-22 09:18:04 -07:00
bisect.c drop trailing newline from warning/error/die messages 2024-09-05 09:07:12 -07:00
blame.c Merge branch 'ps/leakfixes-more' 2024-07-08 14:53:10 -07:00
branch.c ref-filter: add ref_format_clear() function 2024-09-09 16:26:11 -07:00
bugreport.c hooks: remove implicit dependency on the_repository 2024-08-13 10:01:01 -07:00
bundle.c Merge branch 'ps/leakfixes-part-5' 2024-09-03 09:15:00 -07:00
cat-file.c builtin/cat-file: mark 'git cat-file' sparse-index compatible 2024-09-04 09:19:04 -07:00
check-attr.c builtin: stop using the_index 2024-04-18 12:30:42 -07:00
check-ignore.c global: improve const correctness when assigning string constants 2024-06-07 10:30:48 -07:00
check-mailmap.c check-mailmap: add options for additional mailmap sources 2024-08-27 14:51:29 -07:00
check-ref-format.c cache.h: remove this no-longer-used header 2023-06-21 13:39:53 -07:00
checkout--worker.c git-compat-util: move alloc macros to git-compat-util.h 2023-07-05 11:42:31 -07:00
checkout-index.c builtin: stop using the_index 2024-04-18 12:30:42 -07:00
checkout.c Merge branch 'ps/config-wo-the-repository' 2024-08-23 09:02:34 -07:00
clean.c builtin: stop using the_index 2024-04-18 12:30:42 -07:00
clone.c Merge branch 'ps/config-wo-the-repository' 2024-08-23 09:02:34 -07:00
column.c column: disallow negative padding 2024-02-13 10:18:50 -08:00
commit-graph.c Merge branch 'ja/doc-placeholders-fix' 2024-02-08 13:20:34 -08:00
commit-tree.c commit: fix leaking parents when calling commit_tree_extended() 2024-06-11 13:15:07 -07:00
commit.c Merge branch 'tn/doc-commit-fix' into maint-2.46 2024-08-16 12:50:54 -07:00
config.c Merge branch 'jc/config-doc-update' 2024-09-03 09:15:04 -07:00
count-objects.c path: stop relying on the_repository when reporting garbage 2024-08-13 10:01:01 -07:00
credential-cache--daemon.c Merge branch 'bc/credential-scheme-enhancement' 2024-05-08 10:18:44 -07:00
credential-cache.c builtin/credential-cache: fix trivial leaks 2024-08-01 08:47:37 -07:00
credential-store.c builtin/credential-store: fix leaking credential 2024-08-01 08:47:36 -07:00
credential.c builtin/credential: clear credential before exit 2024-05-27 11:20:01 -07:00
describe.c Merge branch 'jc/range-diff-lazy-setup' 2024-09-16 14:22:55 -07:00
diagnose.c global: improve const correctness when assigning string constants 2024-06-07 10:30:48 -07:00
diff-files.c remerge-diff: clean up temporary objdir at a central place 2024-08-09 15:42:40 -07:00
diff-index.c Merge branch 'jc/range-diff-lazy-setup' 2024-09-16 14:22:55 -07:00
diff-tree.c Merge branch 'jc/range-diff-lazy-setup' 2024-09-16 14:22:55 -07:00
diff.c Merge branch 'jc/range-diff-lazy-setup' 2024-09-16 14:22:55 -07:00
difftool.c Merge branch 'ps/leakfixes-more' 2024-07-08 14:53:10 -07:00
fast-export.c builtin/fast-export: plug leaking tag names 2024-08-14 10:07:59 -07:00
fast-import.c Merge branch 'ps/config-wo-the-repository' 2024-08-23 09:02:34 -07:00
fetch-pack.c builtin/fetch-pack: fix leaking refs 2024-08-22 09:18:05 -07:00
fetch.c drop trailing newline from warning/error/die messages 2024-09-05 09:07:12 -07:00
fmt-merge-msg.c parse-options: fix leaks for users of OPT_FILENAME 2024-06-11 13:15:04 -07:00
for-each-ref.c ref-filter: add ref_format_clear() function 2024-09-09 16:26:11 -07:00
for-each-repo.c for-each-repo: optionally keep going on an error 2024-04-24 10:46:03 -07:00
fsck.c Merge branch 'ps/config-wo-the-repository' 2024-08-23 09:02:34 -07:00
fsmonitor--daemon.c treewide: remove unnecessary includes in source files 2023-12-26 12:04:33 -08:00
gc.c Merge branch 'jk/maybe-unused-cleanup' 2024-09-06 10:38:52 -07:00
get-tar-commit-id.c set errno=0 before strtoX calls 2024-08-05 10:59:20 -07:00
grep.c object-name: free leaking object contexts 2024-06-11 13:15:05 -07:00
hash-object.c builtin/hash-object: fix uninitialized hash function 2024-05-21 09:05:13 -07:00
help.c Merge branch 'gc/config-context' 2023-07-06 11:54:48 -07:00
hook.c hooks: remove implicit dependency on the_repository 2024-08-13 10:01:01 -07:00
index-pack.c builtin/index-pack: fix segfaults when running outside of a repo 2024-09-04 07:40:00 -07:00
init-db.c refs: convert ref storage format to an enum 2024-06-06 09:04:31 -07:00
interpret-trailers.c interpret-trailers: handle message without trailing newline 2024-09-06 09:21:44 -07:00
log.c Merge branch 'jc/range-diff-lazy-setup' 2024-09-16 14:22:55 -07:00
ls-files.c factor out strbuf_expand_bad_format() 2024-03-25 11:59:24 -07:00
ls-remote.c Merge branch 'ps/ls-remote-out-of-repo-fix' 2024-08-14 14:54:49 -07:00
ls-tree.c object-name: free leaking object contexts 2024-06-11 13:15:05 -07:00
mailinfo.c treewide: remove unnecessary includes in source files 2023-12-26 12:04:31 -08:00
mailsplit.c global: improve const correctness when assigning string constants 2024-06-07 10:30:48 -07:00
merge-base.c commit-reach(repo_get_merge_bases_many_dirty): pass on errors 2024-02-29 08:06:01 -08:00
merge-file.c merge-file: add --diff-algorithm option 2023-11-22 14:23:06 +09:00
merge-index.c builtin: stop using the_index 2024-04-18 12:30:42 -07:00
merge-ours.c diff.h: remove unnecessary include of oidset.h 2023-06-21 13:39:53 -07:00
merge-recursive.c merge-recursive: honor diff.algorithm 2024-07-13 18:10:49 -07:00
merge-tree.c builtin/merge-tree: fix leaking -X strategy options 2024-08-22 09:18:04 -07:00
merge.c hooks: remove implicit dependency on the_repository 2024-08-13 10:01:01 -07:00
mktag.c fsck: make "fsck_error" callback generic 2024-08-08 09:36:52 -07:00
mktree.c git-compat-util: move alloc macros to git-compat-util.h 2023-07-05 11:42:31 -07:00
multi-pack-index.c midx: implement support for writing incremental MIDX chains 2024-08-06 12:01:39 -07:00
mv.c mv: replace src_dir with a strvec 2024-05-30 08:55:29 -07:00
name-rev.c Merge branch 'jc/refs-symref-referent' 2024-08-15 13:22:15 -07:00
notes.c Merge branch 'ps/config-wo-the-repository' 2024-08-23 09:02:34 -07:00
pack-objects.c pack-objects: add --full-name-hash option 2024-09-19 14:43:00 -07:00
pack-redundant.c hash: require hash algorithm in oidread() and oidclr() 2024-06-14 10:26:32 -07:00
pack-refs.c builtin/pack-refs: introduce new "--auto" flag 2024-03-25 09:54:07 -07:00
patch-id.c Revert "Merge branch 'jc/patch-id' into maint-2.46" 2024-09-16 15:12:06 -07:00
prune-packed.c treewide: be explicit about dependence on gettext.h 2023-03-21 10:56:51 -07:00
prune.c Merge branch 'en/header-split-cache-h-part-3' 2023-06-29 16:43:21 -07:00
pull.c Merge branch 'ps/use-the-repository' 2024-07-02 09:59:00 -07:00
push.c builtin/push: call set_refspecs after validating remote 2024-07-12 09:14:11 -07:00
range-diff.c treewide: remove unnecessary includes in source files 2023-12-26 12:04:31 -08:00
read-tree.c builtin: stop using the_index 2024-04-18 12:30:42 -07:00
rebase.c Merge branch 'ps/config-wo-the-repository' 2024-08-23 09:02:34 -07:00
receive-pack.c Merge branch 'ps/config-wo-the-repository' 2024-08-23 09:02:34 -07:00
reflog.c refs: remove dwim_log() 2024-05-17 10:33:39 -07:00
refs.c builtin/refs: add verify subcommand 2024-08-08 09:36:53 -07:00
remote-ext.c builtins: annotate always-empty prefix parameters 2023-03-28 14:11:24 -07:00
remote-fd.c builtins: annotate always-empty prefix parameters 2023-03-28 14:11:24 -07:00
remote.c remote: plug memory leaks at early returns 2024-08-23 14:20:07 -07:00
repack.c pack-objects: add --full-name-hash option 2024-09-19 14:43:00 -07:00
replace.c refs: add referent to each_ref_fn 2024-08-09 08:47:34 -07:00
replay.c Merge branch 'ps/leakfixes-part-3' 2024-08-14 14:54:47 -07:00
rerere.c builtin/rerere: fix various trivial memory leaks 2024-08-01 08:47:37 -07:00
reset.c Merge branch 'ps/refs-without-the-repository' 2024-05-16 10:10:14 -07:00
rev-list.c builtin/rev-list: fix leaking bitmap index when calculating disk usage 2024-06-11 13:15:05 -07:00
rev-parse.c Merge branch 'jc/refs-symref-referent' 2024-08-15 13:22:15 -07:00
revert.c global: improve const correctness when assigning string constants 2024-06-07 10:30:48 -07:00
rm.c hash: require hash algorithm in oidread() and oidclr() 2024-06-14 10:26:32 -07:00
send-pack.c builtin/send-pack: fix leaking refspecs 2024-08-22 09:18:05 -07:00
shortlog.c builtin/shortlog: fix various trivial memory leaks 2024-08-01 08:47:37 -07:00
show-branch.c Merge branch 'jc/refs-symref-referent' 2024-08-15 13:22:15 -07:00
show-index.c cache.h: remove this no-longer-used header 2023-06-21 13:39:53 -07:00
show-ref.c Merge branch 'jc/refs-symref-referent' 2024-08-15 13:22:15 -07:00
sparse-checkout.c sparse-checkout: use fdopen_lock_file() instead of xfdopen() 2024-09-06 08:02:26 -07:00
stash.c Merge branch 'jc/range-diff-lazy-setup' 2024-09-16 14:22:55 -07:00
stripspace.c strbuf: accept a comment string for strbuf_add_commented_lines() 2024-03-12 13:28:10 -07:00
submodule--helper.c Merge branch 'jc/range-diff-lazy-setup' 2024-09-16 14:22:55 -07:00
symbolic-ref.c Merge branch 'kn/ref-transaction-symref' 2024-05-20 11:20:04 -07:00
tag.c ref-filter: add ref_format_clear() function 2024-09-09 16:26:11 -07:00
unpack-file.c treewide: remove unnecessary includes for wrapper.h 2023-07-05 11:41:59 -07:00
unpack-objects.c hash: require hash algorithm in oidread() and oidclr() 2024-06-14 10:26:32 -07:00
update-index.c config: pass repo to git_config_get_split_index() 2024-08-13 10:01:03 -07:00
update-ref.c update-ref: mark more unused parameters in parser callbacks 2024-08-17 09:46:10 -07:00
update-server-info.c cache.h: remove this no-longer-used header 2023-06-21 13:39:53 -07:00
upload-archive.c builtin/upload-archive: fix leaking args passed to write_archive() 2024-08-22 09:18:04 -07:00
upload-pack.c Sync with 2.44.1 2024-04-29 20:42:30 +02:00
var.c var(win32): do report the GIT_SHELL_PATH that is actually used 2024-07-13 16:23:37 -07:00
verify-commit.c treewide: remove unnecessary includes in source files 2023-12-26 12:04:31 -08:00
verify-pack.c builtin.h: remove unneccessary includes 2023-06-21 13:39:54 -07:00
verify-tag.c ref-filter: add ref_format_clear() function 2024-09-09 16:26:11 -07:00
worktree.c Merge branch 'ps/config-wo-the-repository' 2024-08-23 09:02:34 -07:00
write-tree.c builtin: stop using the_index 2024-04-18 12:30:42 -07:00