aboutsummaryrefslogtreecommitdiffstats
path: root/t/helper/test-submodule-nested-repo-config.c (unfollow)
AgeCommit message (Collapse)AuthorFilesLines
2025-08-15commit-graph: stop passing in redundant repositoryPatrick Steinhardt4-81/+59
Many of the commit-graph related functions take in both a repository and the object database source (directly or via `struct commit_graph`) for which we are supposed to load such a commit-graph. In the best case this information is simply redundant as the source already contains a reference to its owning object database, which in turn has a reference to its repository. In the worst case this information could even mismatch when passing in a source that doesn't belong to the same repository. Refactor the code so that we only pass in the object database source in those cases. There is one exception though, namely `load_commit_graph_chain_fd_st()`, which is responsible for loading a commit-graph chain. It is expected that parts of the commit-graph chain aren't located in the same object source as the chain file itself, but in a different one. Consequently, this function doesn't work on the source level but on the database level instead. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-15commit-graph: stop using `the_repository`Patrick Steinhardt4-40/+42
There's still a bunch of uses of `the_repository` in "commit-graph.c", which we want to stop using due to it being a global variable. Refactor the code to stop using `the_repository` in favor of the repository provided via the calling context. This allows us to drop the `USE_THE_REPOSITORY_VARIABLE` macro. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-15commit-graph: stop using `the_hash_algo`Patrick Steinhardt3-15/+18
Stop using `the_hash_algo` as it implicitly relies on `the_repository`. Instead, we either use the hash algo provided via the context or, if there is no such hash algo, we use `the_repository` explicitly. Such uses will be removed in subsequent commits. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-15commit-graph: refactor `parse_commit_graph()` to take a repositoryPatrick Steinhardt3-16/+15
Refactor `parse_commit_graph()` so that it takes a repository instead of taking repository settings. On the one hand this allows us to get rid of instances where we access `the_hash_algo` by using the repository's hash algorithm instead. On the other hand it also allows us to move the call of `prepare_repo_settings()` into the function itself. Note that there's one small catch, as the commit-graph fuzzer calls this function directly without having a fully functional repository at hand. And while the fuzzer already initializes `the_repository` with relevant info, the call to `prepare_repo_settings()` would fail because we don't have a fully-initialized repository. Work around the issue by also settings `settings.initialized` to pretend that we've already read the settings. While at it, remove the redundant `parse_commit_graph()` declaration in the fuzzer. It was added together with aa658574bf (commit-graph, fuzz: add fuzzer for commit-graph, 2019-01-15), but as we also declared the same function in "commit-graph.h" it wasn't ever needed. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-15commit-graph: store the hash algorithm instead of its lengthPatrick Steinhardt2-19/+19
The commit-graph stores the length of the hash algorithm it uses. In subsequent commits we'll need to pass the whole hash algorithm around though, which we currently don't have access to. Refactor the code so that we store the hash algorithm instead of only its size. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-15commit-graph: stop using `the_hash_algo` via macrosPatrick Steinhardt1-9/+16
We have two macros `GRAPH_DATA_WIDTH` and `GRAPH_MIN_SIZE` that compute hash-dependent sizes. They do so by using the global `the_hash_algo` variable though, which we want to get rid of over time. Convert these macros into functions that accept the hash algorithm as input parameter. Adapt callers accordingly. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-14count-objects: document count-objects packDaniele Sassoli1-0/+2
0bdaa12169b (git-count-objects.txt: describe each line in -v output, 2013-02-08) forgot to include `packs`. Signed-off-by: Daniele Sassoli <danielesassoli@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-13t7005: sanitize test environment for subsequent testsD. Ben Knoble1-44/+39
Some of the editor tests manipulate the environment or config in ways that affect future tests, but those modifications are visible to future tests and create a footgun for them. Use test_config, subshells, single-command environment overrides, and test helpers to automatically undo environment and config modifications once finished. Best-viewed-with: --ignore-all-space Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-13t7005: stop abusing --exec-pathD. Ben Knoble1-2/+2
We want the editors in this test on PATH, so put them there. Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-13t7005: use modern test styleD. Ben Knoble1-46/+22
Tests in t7005 mask Git error codes and do not use our nice test helpers. Improve that, move some code into the setup test, and drop a few old-style blank lines while at it. Best-viewed-with: --ignore-all-space Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-13Git 2.51-rc2v2.51.0-rc2Junio C Hamano1-1/+1
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-12docs: remove stray bracket from git-clone synopsisKnut Harald Ryager1-1/+1
The synopsis section has an extra closing bracket, like this: [--filter=<filter>] [--also-filter-submodules]] The extra one is not the one at the end of this line; it is the one after "...=<filter>". The "--also-filter-submodules" option was added by f05da2b4 (clone, submodule: pass partial clone filters to submodules, 2022-02-04). Because it makes sense only when used with the "--filter=<filter>" option, these two options are enclosed in a pair of brackets. The extra one was added by 76880f05 (doc: git-clone: apply new documentation formatting guidelines, 2024-03-29) by mistake. Remove the extra and incorrect closing bracket, so that the line reads: [--filter=<filter> [--also-filter-submodules]] Signed-off-by: Knut Harald Ryager <e-k-nut@hotmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-11doc lint: check that synopsis manpages have synopsis inlinesJean-Noël Avila3-11/+17
When switching manpages to the synopsis style, the description lists of options need to be switched to inline synopsis for proper formatting. This is done by enclosing the option name in double backticks, e.g. `--option`. Signed-off-by: Jean-Noël Avila <jn.avila@free.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-11doc:git-for-each-ref: fix styling and typosJean-Noël Avila1-132/+132
This commit fixes the synopsis syntax and changes the wording of a few descriptions to be more consistent with the rest of the documentation. It is a prepartion for the next commit that checks that synopsis style is applied consistently across a manual page. Signed-off-by: Jean-Noël Avila <jn.avila@free.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-11doc: check for absence of the form --[no-]parameterJean-Noël Avila36-78/+159
For better searchability, this commit adds a check to ensure that parameters expressed in the form of `--[no-]parameter` are not used in the documentation. In the place of such parameters, the documentation should list two separate parameters: `--parameter` and `--no-parameter`. Signed-off-by: Jean-Noël Avila <jn.avila@free.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-11doc: check for absence of multiple terms in each entry of desc listJean-Noël Avila7-6/+51
For simplifying automated translation of the documentation, it is better to only present one term in each entry of a description list of options. This is because most of these terms can automatically be marked as notranslatable. Also, due to portability issues, the script generate-configlist.sh can no longer insert newlines in the output. However, the result is that it no longer correctly handles multiple terms in a single entry of definition lists. As a result, we now check that these entries do not exist in the documentation. Reviewed-by: Collin Funk <collin.funk1@gmail.com> Signed-off-by: Jean-Noël Avila <jn.avila@free.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-11doc: check well-formedness of delimited sectionsJean-Noël Avila14-4/+80
Having an empty line before each delimited sections is not required by asciidoc, but it is a safety measure that prevents generating malformed asciidoc when generating translated documentation. When a delimited section appears just after a paragraph, the asciidoc processor checks that the length of the delimited section header is different from the length of the paragraph. If it is not, the asciidoc processor will generate a title. In the original English documentation, this is not a problem because the authors always check the output of the asciidoc processor and fix the length of the delimited section header if it turns out to be the same as the paragraph length. However, this is not the case for translations, where the authors have no way to check the length of the delimited section header or the output of the asciidoc processor. This can lead to a section title that is not intended. Indeed, this test also checks that titles are correctly formed, that is, the length of the underline is equal to the length of the title (otherwise it would not be a title but a section header). Finally, this test checks that the delimited section are terminated within the same file. Signed-off-by: Jean-Noël Avila <jn.avila@free.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-11doc: test linkgit macros for well-formednessJean-Noël Avila2-1/+8
Some readers of man pages have reported that they found malformed linkgit macros in the documentation (absence or bad spelling). Signed-off-by: Jean-Noël Avila <jn.avila@free.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-11builtin/fmt-merge-msg: stop depending on 'the_repository'Ayush Chandekar2-3/+9
Refactor builtin/fmt-merge-msg.c to remove the dependancy on the global 'the_repository'. Remove the 'UNUSED' macro from the 'struct repository' parameter and replace 'git_config()' with 'repo_config()' so that configuration is read from the passed repository. Also, add a test to make sure that "git fmt-merge-msg -h" can be called outside a repository. Mentored-by: Christian Couder <christian.couder@gmail.com> Mentored-by: Ghanshyam Thakkar <shyamthakkar001@gmail.com> Signed-off-by: Ayush Chandekar <ayu.chandekar@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-11environment: remove the global variable 'merge_log_config'Ayush Chandekar5-8/+10
The global variable 'merge_log_config', set via the "merge.log" or "merge.summary" settings, is only used in 'cmd_fmt_merge_msg()' and 'cmd_merge()' to adjust the 'shortlog_len' variable. Remove 'merge_log_config' globally and localize it in 'cmd_fmt_merge_msg()' and 'cmd_merge()'. Set its value by passing it in 'fmt_merge_msg_config()' by passing its pointer to the function via the callback parameter. This change is part of an ongoing effort to eliminate global variables, improve modularity and help libify the codebase. Mentored-by: Christian Couder <christian.couder@gmail.com> Mentored-by: Ghanshyam Thakkar <shyamthakkar001@gmail.com> Signed-off-by: Ayush Chandekar <ayu.chandekar@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-11git-jump: make `diff` work with filenames containing spacesGreg Hurrell1-1/+1
In diff.c, we output a trailing "\t" at the end of any filename that contains a space: case DIFF_SYMBOL_FILEPAIR_PLUS: meta = diff_get_color_opt(o, DIFF_METAINFO); reset = diff_get_color_opt(o, DIFF_RESET); fprintf(o->file, "%s%s+++ %s%s%s\n", diff_line_prefix(o), meta, line, reset, strchr(line, ' ') ? "\t" : ""); break; That is, for a file "foo.txt", `git diff --no-prefix` will emit: +++ foo.txt but for "foo bar.txt" it will emit: +++ foo bar.txt\t This in turn leads `git-jump` to produce a quickfix format like this: foo bar.txt\t:1:1:contents Because no "foo bar.txt\t" file actually exists on disk, opening it in Vim will just land the user in an empty buffer. This commit takes the simple approach of unconditionally stripping any trailing tab. Consider the following three examples: 1. For file "foo", Git will emit "foo". 2. For file "foo bar", Git will emit "foo bar\t". 3. For file "foo\t", Git will emit "\"foo\t\"". 4. For file "foo bar\t", Git will emit "\"foo bar\t\"". Before this commit, `git-jump` correctly handled only case "1". After this commit, `git-jump` correctly handles cases "1" and "2". In reality, these are the only cases people are going to run into with any regularity, and the other two are rare edge cases, which probably aren't worth the effort to support unless somebody actually complains about them. Signed-off-by: Greg Hurrell <greg.hurrell@datadoghq.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-11bloom: enable bloom filter with wildcard pathspec in revision traversalLidong Yan2-17/+56
When traversing commits, a pathspec item can be used to limit the traversal to commits that modify the specified paths. And the commit-graph includes a Bloom filter to exclude commits that definitely did not modify a given pathspec item. During commit traversal, the Bloom filter can significantly improve performance. However, it is disabled if the specified pathspec item contains wildcard characters or magic signatures. For performance reason, enable Bloom filter even if a pathspec item contains wildcard characters by filtering only the non-wildcard part of the pathspec item. The function of pathspec magic signature is generally to narrow down the path specified by the pathspecs. So, enable Bloom filter when the magic signature is "top", "glob", "attr", "--depth" or "literal". "exclude" is used to select paths other than the specified path, rather than serving as a filtering function, so it cannot be used together with the Bloom filter. Since Bloom filter is not case insensitive even in case insensitive system (e.g. MacOS), it cannot be used together with "icase" magic. With this optimization, we get some improvements for pathspecs with wildcards or magic signatures. First, in the Git repository we see these modest results: git log -100 -- "t/*" Benchmark 1: new Time (mean ± σ): 20.4 ms ± 0.6 ms Range (min … max): 19.3 ms … 24.4 ms Benchmark 2: old Time (mean ± σ): 23.4 ms ± 0.5 ms Range (min … max): 22.5 ms … 24.7 ms git log -100 -- ":(top)t" Benchmark 1: new Time (mean ± σ): 16.2 ms ± 0.4 ms Range (min … max): 15.3 ms … 17.2 ms Benchmark 2: old Time (mean ± σ): 18.6 ms ± 0.5 ms Range (min … max): 17.6 ms … 20.4 ms But in a larger repo, such as the LLVM project repo below, we get even better results: git log -100 -- "libc/*" Benchmark 1: new Time (mean ± σ): 16.0 ms ± 0.6 ms Range (min … max): 14.7 ms … 17.8 ms Benchmark 2: old Time (mean ± σ): 26.7 ms ± 0.5 ms Range (min … max): 25.4 ms … 27.8 ms git log -100 -- ":(top)libc" Benchmark 1: new Time (mean ± σ): 15.6 ms ± 0.6 ms Range (min … max): 14.4 ms … 17.7 ms Benchmark 2: old Time (mean ± σ): 19.6 ms ± 0.5 ms Range (min … max): 18.6 ms … 20.6 ms Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Lidong Yan <yldhome2d2@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-09diff: --no-index should ignore the worktreeJunio C Hamano2-0/+32
The act of giving "--no-index" tells Git to pretend that the current directory is not under control of any Git index or repository, so even when you happen to be in a Git controlled working tree, where in that working tree should not matter. But the start-up sequence tries to discover the top of the working tree and chdir(2)'s there, even before Git passes control to the subcommand being run. When diff_no_index() starts running, it starts at a wrong (from the end-user's point of view who thinks "git diff --no-index" is merely a better version of GNU diff) directory, and the original directory the user started the command is at "prefix". Because the paths given from argv[] have already been adjusted to account for this path shuffling by prepending the prefix, and showing the resulting path by stripping the prefix, the effect of these nonsense operations (nonsense in the context of "--no-index", that is) is usually not observable. Except for special cases like "-", where it is not preprocessed by prepending the prefix. Instead of papering over by adding more special cases only to cater to the no-index codepath in the generic code, drive the diff machinery more faithfully to what is going on. If the user started "git diff --no-index" in directory X/Y/Z in a working tree controlled by Git, and the start up sequence of Git chdir(2)'ed up to directory X and left Y/Z in the prefix, revert the effect of the start up sequence by chdir'ing back to Y/Z and emptying the prefix. Reported-by: Gregoire Geis <opensource@gregoirege.is> Helped-by: Ramsay Jones <ramsay@ramsayjones.plus.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-09merge: don't document non-existing --compact-summary argumentRené Scharfe1-1/+1
3a54f5bd5d (merge/pull: add the "--compact-summary" option, 2025-06-12) added the option --compact-summary to both merge and pull. It takes no no argument, but for merge it got an argument help string. Remove it, since it is unnecessary. Signed-off-by: René Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-09for-each-ref: call --start-after argument "marker"René Scharfe1-1/+1
dabecb9db2 (for-each-ref: introduce a '--start-after' option, 2025-07-15) added the option --start-after and referred to its argument as "marker" in documentation and usage string, but not in the option's short help. Use "marker" there as well for consistency and brevity. Signed-off-by: René Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-08t6137-*.sh: fix test failure on cygwinRamsay Jones1-6/+6
Commit 6fd1106aa4 ("t3700: Skip a test with backslashes in pathspec", 2009-03-13) introduced the BSLASHPSPEC prerequisite. This prerequisite allows tests to check for systems that can use backslashes in pathspecs (e.g. to escape glob special characters). On windows (and cygwin), this does not work because backslashes are used as directory separators, and git eagerly converts them to forward slashes. This test file uses the FUNNYNAMES prerequisite to skip this test file on windows, despite not really being appropriate for this test, which does not hold on cygwin. The FUNNYNAMES prerequisite is set when the system can create files with embedded quotes ("), tabs or newlines in the name. Since cygwin can satisfy FUNNYNAMES, but not BSLASHPSPEC, this leads to test failures on cygwin. In order to skip these tests on cygwin, replace the FUNNYNAMES prerequisite with BSLASHPSPEC, so that this test file is skipped on both windows and cygwin. While here, fix a few test titles as well. Signed-off-by: Ramsay Jones <ramsay@ramsayjones.plus.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-08builtin: also setup gently for --help-allD. Ben Knoble5-7/+19
Git experts often check the help summary of a command to make sure they spell options right when suggesting advice to colleagues. Further, they might check hidden options when responding to queries about deprecated options like git-rebase(1)'s "preserve merges" option. But some commands don't support "--help-all" outside of a git directory. Running (for example) git rebase --help-all outside a directory fails in "setup_git_directory", erroring with the localized form of fatal: not a git repository (or any of the parent directories): .git Like 99caeed05d (Let 'git <command> -h' show usage without a git dir, 2009-11-09), we want to show the "--help-all" output even without a git dir. Make "--help-all" where we expect "-h" to mean "setup_git_directory_gently", and interpose early in the natural place ("show_usage_with_options_if_asked"). Do the same for usage callers with show_usage_if_asked. The exception is merge-recursive, whose help block doesn't use newer APIs. Best-viewed-with: --ignore-space-change Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-08parse-options: refactor flags for usage_with_options_internalD. Ben Knoble1-5/+15
When reading or editing calls to usage_with_options_internal, it is difficult to tell what trailing "0, 0", "0, 1", "1, 0" arguments mean (NB there is never a "1, 1" case). Give the flags readable names to improve call-sites without changing any behavior. Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-08doc: git-log: fix description listKristoffer Haugsbakk1-2/+2
b27be108c89 (doc: git-log: convert log config to new doc format, 2025-07-07) intended to convert a paragraph describing the different options for `log.decorate` into a description list. But the literal block syntax was used by mistake. Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-08remote.c: convert if-else ladder to switchDenton Liu1-7/+12
For better readability, convert the if-else ladder into a switch statement. Suggested-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Denton Liu <liu.denton@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-08remote.c: remove BUG in show_push_unqualified_ref_name_error()Denton Liu2-2/+8
When "git push <remote> <src>:<dst>" does not spell out the destination side of the ref fully, and when <src> is not given as a reference but an object name, the code tries to give advice messages based on the type of that object. The type is determined by calling odb_read_object_info() and signalled by its return value. The code however reported a programming error with BUG() when this function said that there is no such object, which happens when the object name is given as a full hexadecimal (if the object name is given as a partial hexadecimal or an non-existing ref, the function would have died without returning, so this BUG() wouldn't have triggered). This is wrong. It is an ordinary end-user mistake to give an object name that does not exist and treated as such. An example of the error message produced is as follows: error: The destination you provided is not a full refname (i.e., starting with "refs/"). We tried to guess what you meant by: - Looking for a ref that matches 'branch' on the remote side. - Checking if the <src> being pushed ('0000000000000000000000000000000000000001') is a ref in "refs/{heads,tags}/". If so we add a corresponding refs/{heads,tags}/ prefix on the remote side. Neither worked, so we gave up. You must fully qualify the ref. BUG: remote.c:1221: '0000000000000000000000000000000000000001' should be commit/tag/tree/blob, is '-1' fatal: the remote end hung up unexpectedly Aborted (core dumped) Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-08t5516: remove surrounding empty lines in test bodiesDenton Liu1-51/+0
This style with the empty lines in test bodies was from when the test suite was being developed. Remove the empty lines to match the modern test style. Signed-off-by: Denton Liu <liu.denton@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-08diff: ensure consistent diff behavior with ignore optionsLidong Yan5-23/+70
In git-diff, options like `-w` and `-I<regex>`, two files are considered equivalent under the specified "ignore" rules, even when they are not bit-for-bit identical. For options like `--raw`, `--name-status`, and `--name-only`, git-diff deliberately compares only the SHA values to determine whether two files are equivalent, for performance reasons. As a result, a file shown in `git diff --name-status` may not appear in `git diff --patch`. To quickly determine whether two files are equivalent, add a helper function diff_flush_patch_quietly() in diff.c. Add `.dry_run` field in `struct diff_options`. When `.dry_run` is true, builtin_diff() returns immediately upon finding any change. Call diff_flush_patch_quietly() to determine if we should flush `--raw`, `--name-only` or `--name-status` output. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Lidong Yan <yldhome2d2@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-08t5304: move `prune -h` test from t1517Usman Akinyemi2-7/+5
t1517 is now focused on testing subcommands outside a repository. Move the in-repo `-h` test for `prune` to t5304, which covers this command. Suggested-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-08t5200: move `update-server-info -h` test from t1517Usman Akinyemi2-5/+5
t1517 is now focused on testing subcommands outside a repository. Move the in-repo `-h` test for `update-server-info` to t5200, which covers this command. Suggested-by: Patrick Steinhardt <ps@pks.im> Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-08t/t1517: automate `git subcmd -h` tests outside a repositoryUsman Akinyemi1-2/+28
Replace manual `-h` tests with a loop over all subcommands using `git --list-cmds=main`. This ensures consistent coverage of `-h` behavior outside a repo and future-proofs the test by covering new commands automatically. Known exceptions are skipped or marked as expected failures. Suggested-by: Patrick Steinhardt <ps@pks.im> Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: D. Ben Knoble <ben.knoble+github@gmail.com> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-07diff: teach tree-diff a max-depth parameterJeff King7-3/+257
When you are doing a tree-diff, there are basically two options: do not recurse into subtrees at all, or recurse indefinitely. While most callers would want to always recurse and see full pathnames, some may want the efficiency of looking only at a particular level of the tree. This is currently easy to do for the top-level (just turn off recursion), but you cannot say "show me what changed in subdir/, but do not recurse". This patch adds a max-depth parameter which is measured from the closest pathspec match, so that you can do: git log --raw --max-depth=1 -- a/b/c and see the raw output for a/b/c/, but not those of a/b/c/d/ (instead of the raw output you would see for a/b/c/d). Co-authored-by: Toon Claes <toon@iotcl.com> Signed-off-by: Toon Claes <toon@iotcl.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-07within_depth: fix return for empty pathToon Claes4-1/+50
The within_depth() function is used to check whether pathspecs limited by a max-depth parameter are acceptable. It takes a path to check, a maximum depth, and a "base" depth. It counts the components in the path (by counting slashes), adds them to the base, and compares them to the maximum. However, if the base does not have any slashes at all, we always return `true`. If the base depth is 0, then this is correct; no matter what the maximum is, we are always within it. However, if the base depth is greater than 0, then we might return an erroneous result. This ends up not causing any user-visible bugs in the current code. The call sites in dir.c always pass a base depth of 0, so are unaffected. But tree_entry_interesting() uses this function differently: it will pass the prefix of the current entry, along with a `1` if the entry is a directory, in essence checking whether items inside the entry would be of interest. It turns out not to make a difference in behavior, but the reasoning is complex. Given a tree like: file a/file a/b/file walking the tree and calling tree_entry_interesting() will yield the following results: (with max_depth=0): file: yes a: yes a/file: no a/b: no (with max_depth=1): file: yes a: yes a/file: yes a/b: no So we have inconsistent behavior in considering directories interesting. If they are at the edge of our depth but at the root, we will recurse into them, but then find all of their entries uninteresting (e.g., in the first case, we will look at "a" but find "a/*" uninteresting). But if they are at the edge of our depth and not at the root, then we will not recurse (in the second example, we do not even bother entering "a/b"). This turns out not to matter because the only caller which uses max-depth pathspecs is cmd_grep(), which only cares about blob entries. From its perspective, it is exactly the same to not recurse into a subtree, or to recurse and find that it contains no matching entries. Not recursing is merely an optimization. It is debatable whether tree_entry_interesting() should consider such an entry interesting. The only caller does not care if it sees the tree itself, and can benefit from the optimization. But if we add a "max-depth" limiter to regular diffs, then a diff with DIFF_OPT_TREE_IN_RECURSIVE would probably want to show the tree itself, but not what it contains. This patch just fixes within_depth(), which means we consider such entries uninteresting (and makes the current caller happy). If we want to change that in the future, then this fix is still the correct first step, as the current behavior is simply inconsistent. This has the effect the function tree_entry_interesting() now behaves like following on the first example: (with max_depth=0): file: yes a: no a/file: no a/b: no Meaning we won't step in "a/" no more to realize all "a/*" entries are uninterested, but we stop at the tree entry itself. Based-on-patch-by: Jeff King <peff@peff.net> Signed-off-by: Toon Claes <toon@iotcl.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-07combine-diff: zero memory used for callback filepairsJeff King1-1/+1
In commit 25e5e2bf85 (combine-diff: support format_callback, 2011-08-19), the combined-diff code learned how to make a multi-sourced `diff_filepair` to pass to a diff callback. When we create each filepair, we do not bother to fill in many of the fields, because they would make no sense (e.g. there can be no rename score or broken_pair flag because we do not go through the diffcore filters). However, we did not even bother to zero them, leading to random values. Let's make sure everything is blank with xcalloc(), just as the regular diff code does. We would potentially want to set the `status` flag to something non-zero, but it is not clear to what. Possibly a new DIFF_STATUS_COMBINED would make sense, as this is not strictly a modification, nor does it fit any other category. Since it is not yet clear what callers would want, this patch simply leaves it as `0`, the same empty flag that is seen when `diffcore_std` is not used at all. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Toon Claes <toon@iotcl.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-07merge-ort: fix directory rename on top of source of other rename/deleteElijah Newren2-20/+355
At GitHub, we've got a real-world repository that has been triggering failures of the form: git: merge-ort.c:3007: process_renames: Assertion `newinfo && !newinfo->merged.clean' failed. which comes from the line: VERIFY_CI(newinfo); Unfortunately, this one has been quite complex to unravel, and is a bit complex to explain. So, I'm going to carefully try to explain each relevant piece needed to understand the fix, then carefully build up from a simple testcase to some of the relevant testcases. == New special case we need to consider == Rename pairs in the diffcore machinery connect the source path of a rename with the destination path of a rename. Since we have rename pairs to consider on both sides of history since the merge base, merging has to consider a few special cases of possible overlap: A) two rename pairs having the same target path B) two rename pairs having the same source path C) the source path of one rename pair being the target path of a different rename pair Some of these came up often enough that we gave them names: A) a rename/rename(2to1) conflict (looks similar to an add/add conflict) B) a rename/rename(1to2) conflict, which represents the same path being renamed differently on the two sides of history C) not yet named merge-ort is well-prepared to handle cases (A) and (B), as was merge-recursive (which was merge-ort's predecessor). Case (C) was briefly considered during the years of merge-recursive maintenance, but the full extent of support it got was a few FIXME/TODO comments littered around the code highlighting some of the places that would probably need to be fixed to support it. When I wrote merge-ort I ignored case (C) entirely, since I believed that case (C) was only possible if we were to support break detection during merges. Not only had break detection never been supported by any merge algorithm, I thought break detection wasn't worth the effort to support in a merge algorithm. However, it turns out that case (C) can be triggered without break detection, if there's enough moving pieces. Before I dive into how to trigger case (C) with directory renames plus other renames, it might be helpful to use a simpler example with break detection first. And before we get to that it may help to explain some more basics of handling renames in the merge algorithm. So, let me first backup and provide a quick refresher on each of * handling renames * what break detection would mean, if supported in merging * handling directory renames From there, I'll build up from a basic directory rename detection case to one that triggers a failure currently. == Handling renames == In the merge machinery when we have a rename of a path A -> B, processing that rename needs to remove path A, and make sure that path B has the relevant information. Note that if the content was also modified on both sides, this may mean that we have 3 different stages that need to be stored at path B instead of having some stored at path A. Having all stages stored at path B makes it much easier for users to investigate and resolve the content conflict associated with a renamed path. For example: * "git status" doesn't have to figure out how to list paths A & B and attempt to connect them for users; it can just list path B. * Users can use "git ls-files -u B" (instead of trying to find the previous name of the file so they can list both, i.e. "git ls-files -u A B") * Users can resolve via "git add B" (without needing to "git rm A") == What break detection would mean == If break detection were supported, we might have cases where A -> B *and* C -> A, meaning that both rename pairs might believe they need to update A. In particular, the processing of A -> B would need to be careful to not clear out all stages of A and mark it resolved, while both renames would need to figure out which stages of A belong with A and which belong with B, so that both paths have the right stages associated with them. merge-ort (like merge-recursive before it) makes no attempt to handle break detection; it runs with break detection turned off. It would need to be retrofitted to handle such cases. == Directory rename detection == If one side of history renames directory D/ -> E/, and the other side of history adds new files to D/, then directory rename detection notices and suggests moving those new files to E/. A similar thing is done for paths renamed into D/, causing them to be transitively renamed into E/. The default in the merge machinery is to report a conflict whenever a directory rename might modify the location of a path, so that users can decide whether they wanted the original path or the directory-rename-induced location. However, that means the default codepath still runs through all the directory rename detection logic, it just supplements it with providing conflict notices when it is done. == Building up increasingly complex testcases == I'll start with a really simple directory rename example, and then slowly add twists that explain new pieces until we get to the problematic cases: === Testcase 1 === Let's start with a concrete example, where particular files/directories of interest that exist or are changed on each side are called out: Original: <nothing of note> our side: rename B/file -> C/file their side: rename C/ -> A/ For this case, we'd expect to see the original B/file appear not at C/file but at A/file. (We would also expect a conflict notice that the user will want to choose between C/file and A/file, but I'm going to ignore conflict notices from here on by assuming merge.directoryRenames is set to `true` rather than `conflict`; the only difference that assumption makes is whether that makes the merge be considered to be conflicted and whether it prints a conflict notice; what is written to the index or working directory is unchanged.) === Testcase 2 === Modify testcase 1 by having A/file exist from the start: Original: A/file exists our side: rename B/file -> C/file their side: rename C/ -> A/ In such a case, to avoid user confusion at what looks kind of like an add/add conflict (even though the original path at A/file was not added by either side of the merge), we turn off directory rename detection for this path and print a "in the way" warning to the user: CONFLICT (implicit dir rename): Existing file/dir ... in the way ... The testcases in section 5 of t6423 explore these in more detail. === Testcase 3 === Let's modify testcase 1 in a slightly different way: have A/file be added by their side rather than it already existing. Original: <nothing of note> our side: rename B/file -> C/file their side: rename C/ -> A/ add A/file In this case, the directory rename detection basically transforms our side's original B/file -> C/file into a B/file -> A/file, and so we get a rename/add conflict, with one version of A/file coming from the renamed file, and another coming from the new A/file, each stored as stages 2 and 3 in conflicts. This kind of add/add conflict is perhaps slightly more complex than a regular add/add conflict, but with the printed messages it makes sense where it came from and we have different stages of the file to work with to resolve the conflict. === Testcase 4 === Let's do something similar to testcase 3, but have the opposite side of history add A/file: Original: <nothing of note> our side: rename B/file -> C/file add A/file their side: rename C/ -> A/ Now if we allow directory rename detection to modify C/file to A/file, then we also get a rename/add conflict, but in this case we'd need both higher order stages being recorded on side 2, which makes no sense. The index can't store multiple stage 2 entries, and even if we could, it would probably be confusing for users to work with. So, similar to what we do when there was an A/file in the original version, we simply turn off directory rename detection for cases like this and provide the "in the way" CONFLICT notice to the user. === Testcase 5 === We're slowly getting closer. Let's mix it up by having A/file exist at the beginning but not exist on their side: original: A/file exists our side: rename B/file -> C/file their side: rename C/ -> A/ rename A/file -> D/file For this case, you could say that since A/file -> D/file, it's no longer in the way of C/file being moved by directory rename detection to A/file. But that would give us a case where A/file is both the source and the target of a rename, similar to break detection, which the code isn't currently equipped to handle. This is not yet the case that causes current failures; to the current code, this kind of looks like testcase 4 in that A/file is in the way on our side (since A/file was in the original and was umodified by our side). So, it results in a "in the way" notification with directory rename detection being turned off for A/file so that B/file ends up at C/file. Perhaps the resolution could be improved in the future, but our "in the way" checks prevented such problems by noticing that A/file exists on our side and thus turns off directory rename detection from affecting C/file's location. So, while the merge result could be perhaps improved, the fact that this is currently handled by giving the user an "in the way" message gives the user a chance to resolve and prevents the code from tripping itself up. === Testcase 6 === Let's modify testcase 5 a bit more, to also delete A/file on our side: original: A/file exists our side: rename B/file -> C/file delete A/file their side: rename C/ -> A/ rename A/file -> D/file Now the "in the way" logic doesn't detect that there's an A/file in the way (neither side has an A/file anymore), so it's fine to transitively rename C/file further to A/file...except that we end up with A/file being both the source of one rename, and the target of a different rename. Each rename pair tries to handle the resolution of the source and target paths of its own rename. But when we go to process the second rename pair in process_renames(), we do not expect either the source or the destination to be marked as handled already; so, when we hit the sanity checks that these are not handled: VERIFY_CI(oldinfo); VERIFY_CI(newinfo); then one of these is going to throw an assertion failure since the previous rename pair already marked both of its paths as handled. This will give us an error of the form: git: merge-ort.c:3007: process_renames: Assertion `newinfo && !newinfo->merged.clean' failed. This is the failure we're currently triggering, and it fundamentally depends on: * a path existing in the original * that original path being removed or renamed on *both* sides * some kind of directory rename moving some *other* path into that original path This was added as testcase 12q in t6423. === Testcase 7 === Bonus bug found while investigating! Let's go back to the comparison between testcases 2 & 3, and set up a file present on their side that we need to consider: Original: A/file exists our side: rename B/file -> C/file rename A/file -> D/file their side: rename C/ -> A/ Here, there is no A/file in the way on our side like testcase 4. There is an A/file present on their side like testcase 3, which was an add/add conflict, but that's associated with the file be renamed to D/file. So, that really shouldn't be an add/add conflict because we instead want all modes of the original A/file to be transported to D/file. Unfortunately, the current code kind of treats it like an add/add conflict instead...but even worse. There is also a valid mode for A/file in the original, which normally goes to stage 1. However, an add/add conflict should be represented in the index with no mode at stage 1 (for the original side), only modes at stages 2 and 3 (for our and their side), so for an add/add we'd expect that mode for A/file in the original version to be cleared out (or be transported to D/file). Unfortunately, the code currently leaves not only the stage 3 entry for A/file intact, it also leaves the stage 1 entry for A/file. This results in `git ls-files -u A/file` output of the form: 100644 d00491fd7e5bb6fa28c517a0bb32b8b506539d4d 1 A/file 100644 0cfbf08886fca9a91cb753ec8734c84fcbe52c9f 2 A/file 100644 d00491fd7e5bb6fa28c517a0bb32b8b506539d4d 3 A/file This would likely cause users to believe this isn't an add/add conflict; rather, this would lead them to believe that A/file was only modified on our side and that therefore it should not have been a conflict in the first place. And while resolving the conflict in favor of our side is the correct resolution (because stages 1 and 3 should have been cleared out in the first place), this is certainly likely to cause confusion for anyone attempting to investigate why this path was marked as conflicted. This was added as testcase 12p in t6423. == Attempted solutions that I discarded == 1) For each side of history, create a strset of the sources of each rename on the other side of history. Then when using directory renames to modify existing renames, verify that we aren't renaming to a source of another rename. Unfortunately, the "relevant renames" optimization in merge-ort means we often don't detect renames -- we just see a delete and an add -- which is easy to forget and makes debugging testcases harder, but it also turns out that this solution in insufficient to solve the related problems in the area (more on that below). 2) Modify the code to be aware of the possibility of renaming to the source of another side's rename, and make all the conflict resolution logic for each case (including existing rename/rename(2to1) and rename/rename(1to2) cases) handle the additional complexity. It turns out there was much more code to audit than I wanted, for a really niche case. I didn't like how many changes were needed, and aborted. == Solution == We do not want the stages of unrelated files appearing at the same path in the index except when dealing with an add/add conflict. While we previously handled this for stages 2 & 3, we also need to worry about stage 1. So check for a stage 1 index entry being in the way of a directory rename. However, if we can detect that the stage 1 index entry is actually from a related file due to a directory-rename-causes-rename-to-self situation, then we can allow the stage 1 entry to remain. From this wording, you may note that it's not just rename cases that are a problem; bugs could be triggered with directory renames vs simple adds. That leads us to... == Testcases 8+ == Another bonus bug, found via understanding our final solutions (and the failure of our first attempted solution)! Let's tweak testcase 7 a bit: Original: A/file exists our side: delete A/file add -> C/file their side: delete A/file rename C/ -> A/ Here, there doesn't seem to be a big problem. Sure C/file gets modified via the directory rename of C/ -> A/ so that it becomes A/file, but there's no file in the way, right? Actually, here we have a problem that the stage 1 entry of A/file would be combined with the stage 2 entry of C/file, and make it look like a modify/delete conflict. Perhaps there is some extra checking that could be added to the code to make it attempt to clear out the stage 1 entry of A/file, but the various rename-to-self-via-directory-rename testcases make that a bit more difficult. For now, it's easier to just treat this as a path-in-the-way situation and not allow the directory rename to modify C/file. That sounds all well and good, but it does have an interesting side effect. Due to the "relevant renames" optimizations in merge-ort (i.e. only detect the renames you need), 100% renames whose files weren't modified on the other side often go undetected. This means that if we modify this testcase slightly to: Original: A/file exists our side: A/file -> C/file their side: rename C/ -> A/ Then although this looks like where the directory rename just moves C/file back to A/file and there's no problem, we may not detect the A/file -> C/file rename. Instead it will look like a deletion of A/file and an addition of C/file. The directory rename then appears to be moving C/file to A/file, which is on top of an "unrelated" file (or at least a file it doesn't know is related). So, we will report path-in-the-way conflicts now in cases where we didn't before. That's better than silently and accidentally combining stages of unrelated files and making them look like a modify/delete; users can investigate the reported conflict and simply resolve it. This means we tweak the expected solution for testcases 12i, 12j, and 12k. (Those three tests are basically the same test repeated three times, but I was worried when I added those that subtle differences in parent/child, sibling/sibling, and toplevel directories might mess up how rename-to-self testcases actually get handled.) Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-07merge-ort: fix incorrect file handlingElijah Newren2-3/+80
We have multiple bugs here -- accidental silent file deletion, accidental silent file retention for files that should be deleted, and incorrect number of entries left in the index. The series merged at commit d3b88be1b450 (Merge branch 'en/merge-dir-rename-corner-case-fix', 2021-07-16) introduced testcase 12i-12k in t6423 which checked for rename-to-self cases, and fixed bugs that merge-ort and merge-recursive had with these testcases. At the time, I noted that merge-ort had one bug for these cases, while merge-recursive had two. It turns out that merge-ort did in fact have another bug, but the "relevant renames" optimizations were masking it. If we modify testcase 12i from t6423 to modify the file in the commit that renames it (but only modify it enough that it can still be detected as a rename), then we can trigger silent deletion of the file. Tweak testcase 12i slightly to make the file in question have more than one line in it. This leaves the testcase intact other than changing the initial contents of this one file. The purpose of this tweak is to minimize the changes between this testcase and a new one that we want to add. Then duplicate testcase 12i as 12i2, changing it so that it adds a single line to the file in question when it is renamed; testcase 12i2 then serves as a testcase for this merge-ort bug that I previously overlooked. Further, commit 98a1a00d5301 (t6423: add a testcase causing a failed assertion in process_renames, 2025-03-06), fixed an issue with rename-to-self but added a new testcase, 12n, that only checked for whether the merge ran to completion. A few commits ago, we modified this test to check for the number of entries in the index -- but noted that the number was wrong. And we also noted a silently-keep-instead-of-delete bug at the same time in the new testcase 12n2. In summary, we have the following bugs with rename-to-self cases: * silent deletion of file expected to be kept (t6423 testcase 12i2) * silent retention of file expected to be removed (t6423 testcase 12n2) * wrong number of extries left in the index (t6423 testcase 12n) All of these bugs arise because in a rename-to-self case, when we have a rename A->B, both A and B name the same file. The code in process_renames() assumes A & B are different, and tries to move the higher order stages and file contents so that they are associated just with the new path, but the assumptions of A & B being different can cause A to be deleted when it's not supposed to be or mark B as resolved and kept in place when it's supposed to be deleted. Since A & B are already the same path in the rename-to-self case, simply skip the steps in process_renames() for such files to fix these bugs. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-07merge-ort: clarify the interning of strings in opt->priv->pathElijah Newren1-3/+8
Because merge-ort is dealing with potentially all the pathnames in the repository, it sometimes needs to do an awful lot of string comparisons. Because of this, struct merge_options_internal's path member was envisioned from the beginning to contain an interned value for every path in order to allow us to compare strings via pointer comparison instead of using strcmp. See * 5b59c3db059d (merge-ort: setup basic internal data structures, 2020-12-13) * f591c4724615 (merge-ort: copy and adapt merge_3way() from merge-recursive.c, 2021-01-01) for some of the early comments. However, the original comment was slightly misleading when it switched from mentioning paths to only mentioning directories. Fix that, and while at it also point to an example in the code which applies the extra needed care to permit the pointer comparison optimization. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-07t6423: fix missed staging of file in testcases 12i,12j,12kElijah Newren1-3/+6
Commit 806f83287f8d (t6423: test directory renames causing rename-to-self, 2021-06-30) introduced testcase 12i-12k but omitted staging one of the files and copy-pasted that mistake to the other tests. This means the merge runs with an unstaged change, even though that isn't related to what is being tested and makes the test look more complicated than it is. The cover letter for the series associated with the above commit (see Message-ID: pull.1039.git.git.1624727121.gitgitgadget@gmail.com) noted that these testcases triggered two bugs in merge-recursive but only one in merge-ort; in merge-recursive these testcases also triggered a silent deletion of the file in question when it shouldn't be deleted. What I didn't realize at the time was that the deletion bug in merge-ort was merely being sidestepped by the "relevant renames" optimization but can actually be triggered. A subsequent commit will deal with that additional bug, but it was complicated by the mistaken forgotten staging, so this commit first fixes that issue. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-07t6423: document two bugs with rename-to-self testcasesElijah Newren1-2/+98
When commit 98a1a00d5301 (t6423: add a testcase causing a failed assertion in process_renames, 2025-03-06) was added, I tweaked the commit message, and moved the test into t6423. However, that still left two other things missing that made this test unlike the others in the same testfile: * It didn't have an English description of the test setup like all other tests in t6423 * It didn't check that the right number of files were present at the end The former issue is a minor detail that isn't that critical, but the latter feels more important. If it had been done, I might have noticed another bug. In particular, this testcase involves Side A: rename world -> tools/world and Side B: rename tools/ -> <the toplevel> Side B: remove world The tools/ -> <toplevel> rename turns the world -> tools/world rename into world -> world, i.e. a rename-to-self case. But, it's a path conflict because merge.directoryRenames defaults to false. There's no content conflict because Side A didn't modify world, so we should just take the content of world from Side B -- i.e. delete it. So, we have a conflict on the path, but not on its content. We could consider letting the content trump since it is unconflicted, but if we are going to leave a conflict, it should certainly represent that 'world' existed both in the base version and on Side A. Currently it doesn't. Add a description of this test, add some checking of the number of entries in the index at the end of the merge, and mark the test as expecting to fail for now. A subsequent commit will fix this bug. While at it, I found another related bug from a nearly identical setup but setting merge.directoryRenames=true. Copy testcase 12n into 12n2, changing it to use merge instead of cherry-pick, and turn on directory renames for this test. In this case, since there is no content conflict and no path conflict, it should be okay to delete the file. Unfortunately, the code resolves without conflict but silently leaves world despite the fact it should be deleted. It might also be okay if the code spuriously thought there was a modify/delete conflict here; that would at least notify users to look closer and then when they notice there was no change since the base version, they can easily resolve. A conflict notice is much better than silently providing the wrong resolution. Cover this with the 12n2 testcase, which for now is marked as expecting to fail as well. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-07merge-ort: drop unnecessary temporary in check_for_directory_rename()Elijah Newren1-4/+2
check_for_directory_rename() had a weirdly coded check for whether a strmap contained a certain key. Replace the temporary variable and call to strmap_get_entry() with the more natural strmap_contains() call. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-07merge-ort: update comments to modern testfile locationElijah Newren1-3/+3
In commit 919df3195553 (Collect merge-related tests to t64xx, 2020-08-10), merge related tests were moved from t60xx to t64xx. Some comments in merge-ort relating to some tricky code referenced specific testcases within certain testfiles for additional information, but referred to their historical testfile names; update the testfile names to mention their modern location. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-07Git 2.51-rc1v2.51.0-rc1Junio C Hamano2-1/+7
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-07Documentation/RelNotes/2.51.0: improve wording for a couple entriesPatrick Steinhardt1-5/+5
Improve wording and fix typos for a couple entries part of the Git 2.51 release notes. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-06builtin/remote: only iterate through refs that are to be renamedPatrick Steinhardt3-10/+13
When renaming a remote we also need to rename all references accordingly. But while we only need to rename references that are contained in the "refs/remotes/$OLDNAME/" namespace, we end up using `refs_for_each_rawref()` that iterates through _all_ references. We know to exit early in the callback in case we see an irrelevant reference, but ultimately this is still a waste of compute as we knowingly iterate through references that we won't ever care about. Improve this by using `refs_for_each_rawref_in()`, which knows to only iterate through (potentially broken) references in a given prefix. The following benchmark renames a remote with a single reference in a repository that has 100k unrelated references. This shows a sizeable improvement with the "files" backend: Benchmark 1: rename remote (refformat = files, revision = HEAD~) Time (mean ± σ): 42.6 ms ± 0.9 ms [User: 29.1 ms, System: 8.4 ms] Range (min … max): 40.1 ms … 43.3 ms 10 runs Benchmark 2: rename remote (refformat = files, revision = HEAD) Time (mean ± σ): 31.7 ms ± 4.0 ms [User: 19.6 ms, System: 6.9 ms] Range (min … max): 27.1 ms … 36.0 ms 10 runs Summary rename remote (refformat = files, revision = HEAD) ran 1.35 ± 0.17 times faster than rename remote (refformat = files, revision = HEAD~) The "reftable" backend shows roughly the same absolute improvement, but given that it's already significantly faster than the "files" backend this translates to a much larger relative improvement: Benchmark 1: rename remote (refformat = reftable, revision = HEAD~) Time (mean ± σ): 18.2 ms ± 0.5 ms [User: 12.7 ms, System: 3.0 ms] Range (min … max): 17.3 ms … 21.4 ms 110 runs Benchmark 2: rename remote (refformat = reftable, revision = HEAD) Time (mean ± σ): 8.8 ms ± 0.5 ms [User: 3.8 ms, System: 2.9 ms] Range (min … max): 7.5 ms … 9.9 ms 167 runs Summary rename remote (refformat = reftable, revision = HEAD) ran 2.07 ± 0.12 times faster than rename remote (refformat = reftable, revision = HEAD~) Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-08-06builtin/remote: rework how remote refs get renamedPatrick Steinhardt2-99/+270
It was recently reported [1] that renaming a remote that has dangling symrefs is broken. This issue can be trivially reproduced: $ git init repo Initialized empty Git repository in /tmp/repo/.git/ $ cd repo/ $ git remote add origin /dev/null $ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/master $ git remote rename origin renamed $ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/master $ git symbolic-ref refs/remotes/renamed/HEAD fatal: ref refs/remotes/renamed/HEAD is not a symbolic ref As one can see, the "HEAD" reference did not get renamed but stays in the same place. There are two issues here: - We use `refs_resolve_ref_unsafe()` to resolve references, but we don't pass the `RESOLVE_REF_NO_RECURSE` flag. Consequently, if the reference does not resolve, the function will fail and we thus ignore this branch. - We use `refs_for_each_ref()` to iterate through the old remote's references, but that function ignores broken references. Both of these issues are easy to fix. But having a closer look at the logic that renames remote references surfaces that it leaves a lot to be desired overall. The problem is that we're using O(|refs| + |symrefs| * 2) many reference transactions to perform the renames. We first delete all symrefs, then individually rename every direct reference and finally we recreate the symrefs. On the one hand this isn't even remotely an atomic operation, so if we hit any error we'll already have deleted all references. But more importantly it is also extremely inefficient. The number of transactions for symrefs doesn't really bother us too much, as there should generally only be a single symref anyway ("HEAD"). But the renames are very expensive: - For the "reftable" backend we perform auto-compaction after every single rename, which does add up. - For the "files" backend we potentially have to rewrite the "packed-refs" file on every single rename in case they are packed. The consequence here is quadratic runtime performance. Renaming a 100k references takes hours to complete. Refactor the code to use a single transaction to perform all the reference updates atomically, which speeds up the transaction quite significantly: Benchmark 1: rename remote (refformat = files, revision = HEAD~) Time (mean ± σ): 238.770 s ± 13.857 s [User: 91.473 s, System: 143.793 s] Range (min … max): 204.863 s … 247.699 s 10 runs Benchmark 2: rename remote (refformat = files, revision = HEAD) Time (mean ± σ): 2.103 s ± 0.036 s [User: 0.360 s, System: 1.313 s] Range (min … max): 2.011 s … 2.141 s 10 runs Summary rename remote (refformat = files, revision = HEAD) ran 113.53 ± 6.87 times faster than rename remote (refformat = files, revision = HEAD~) For the "reftable" backend we see a significant speedup, as well, but given that we don't have quadratic runtime behaviour there it's way less extreme: Benchmark 1: rename remote (refformat = reftable, revision = HEAD~) Time (mean ± σ): 8.604 s ± 0.539 s [User: 4.985 s, System: 2.368 s] Range (min … max): 7.880 s … 9.556 s 10 runs Benchmark 2: rename remote (refformat = reftable, revision = HEAD) Time (mean ± σ): 1.177 s ± 0.103 s [User: 0.446 s, System: 0.270 s] Range (min … max): 1.023 s … 1.410 s 10 runs Summary rename remote (refformat = reftable, revision = HEAD) ran 7.31 ± 0.79 times faster than rename remote (refformat = reftable, revision = HEAD~) There is one issue though with using atomic transactions: when nesting a remote into itself it can happen that renamed references conflict with the old referencse. For example, when we have a reference "refs/remotes/origin/foo" and we rename "origin" to "origin/foo", then we'll end up with an F/D conflict when we try to create the renamed reference "refs/remotes/origin/foo/foo". This situation is overall quite unlikely to happen: people tend to not use nested remotes, and if they do they must at the same time also have a conflicting refname. But the end result would be that the old remote references stay intact whereas all the other parts of the repository have been adjusted for the new remote name. Address this by queueing and preparing the reference update before we touch any other part of the repository. Like this we can make sure that the reference update will go through before rewriting the configuration. Otherwise, if the transaction fails to prepare we can gracefully abort the whole operation without any changes having been performed in the repository yet. Furthermore, we can detect the conflict and print some helpful advice for how the user can resolve this situation. So overall, the tradeoff is that: - Reference transactions are now all-or-nothing. This is a significant improvement over the previous state where we may have ended up with partially-renamed references. - Rewriting references is now significantly faster. - We only rewrite the configuration in case we know that all references can be updated. - But we may refuse to rename a remote in case references conflict. Overall this seems like an acceptable tradeoff. While at it, fix the handling of symbolic/broken references by using `refs_for_each_rawref()`. Add tests that cover both this reported issue and tests that exercise nesting of remotes. One thing to note: with this change we cannot provide a proper progress monitor anymore as we queue the references into the transactions as we iterate through them. Consequently, as we don't know yet how many refs there are in total, we cannot report how many percent of the operation is done anymore. But that's a small price to pay considering that you now shouldn't need the progress monitor in most situations at all anymore. [1]: <CANrWfmQWa=RJnm7d3C7ogRX6Tth2eeuGwvwrNmzS2gr+eP0OpA@mail.gmail.com> Reported-by: Han Jiang <jhcarl0814@gmail.com> Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>