diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f2e93f54611b62..826f2f5d3a6a88 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -150,7 +150,7 @@ jobs: - uses: git-for-windows/setup-git-for-windows-sdk@v1 - name: test shell: bash - run: . /etc/profile && ci/run-test-slice.sh ${{matrix.nr}} 10 + run: . /etc/profile && ci/run-test-slice.sh $((${{matrix.nr}} + 1)) 10 - name: print test failures if: failure() && env.FAILED_TEST_ARTIFACTS != '' shell: bash @@ -237,7 +237,7 @@ jobs: shell: bash env: NO_SVN_TESTS: 1 - run: . /etc/profile && ci/run-test-slice.sh ${{matrix.nr}} 10 + run: . /etc/profile && ci/run-test-slice.sh $((${{matrix.nr}} + 1)) 10 - name: print test failures if: failure() && env.FAILED_TEST_ARTIFACTS != '' shell: bash @@ -297,8 +297,8 @@ jobs: name: windows-meson-artifacts path: build - name: Test - shell: pwsh - run: ci/run-test-slice-meson.sh build ${{matrix.nr}} 10 + shell: bash + run: ci/run-test-slice-meson.sh build $((${{matrix.nr}} + 1)) 10 - name: print test failures if: failure() && env.FAILED_TEST_ARTIFACTS != '' shell: bash diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b419a84e2cc660..71b8a6e642d9cf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -157,6 +157,8 @@ test:mingw64: parallel: 10 .msvc-meson: + variables: + TEST_OUTPUT_DIRECTORY: "C:/Git-Test" tags: - saas-windows-medium-amd64 before_script: @@ -164,12 +166,13 @@ test:mingw64: - choco install -y git meson ninja rust-ms - Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1 - refreshenv + - New-Item -Path $env:TEST_OUTPUT_DIRECTORY -ItemType Directory build:msvc-meson: extends: .msvc-meson stage: build script: - - meson setup build --vsenv -Dperl=disabled -Dbackend_max_links=1 -Dcredential_helpers=wincred + - meson setup build --vsenv -Dperl=disabled -Dbackend_max_links=1 -Dcredential_helpers=wincred -Dtest_output_directory="$TEST_OUTPUT_DIRECTORY" - meson compile -C build artifacts: paths: @@ -183,11 +186,21 @@ test:msvc-meson: - job: "build:msvc-meson" artifacts: true script: - - meson test -C build --no-rebuild --print-errorlogs --slice $Env:CI_NODE_INDEX/$Env:CI_NODE_TOTAL + - | + & "C:/Program Files/Git/usr/bin/bash.exe" -l -c 'ci/run-test-slice-meson.sh build $CI_NODE_INDEX $CI_NODE_TOTAL' + after_script: + - | + if ($env:CI_JOB_STATUS -ne "success") { + & "C:/Program Files/Git/usr/bin/bash.exe" -l -c 'ci/print-test-failures.sh' + Move-Item -Path "$env:TEST_OUTPUT_DIRECTORY/failed-test-artifacts" -Destination t/ + } parallel: 10 artifacts: + paths: + - t/failed-test-artifacts reports: junit: build/meson-logs/testlog.junit.xml + when: on_failure test:fuzz-smoke-tests: image: ubuntu:latest diff --git a/Documentation/RelNotes/2.54.0.adoc b/Documentation/RelNotes/2.54.0.adoc index 9988979772019c..7c8c2fa466e0ec 100644 --- a/Documentation/RelNotes/2.54.0.adoc +++ b/Documentation/RelNotes/2.54.0.adoc @@ -10,7 +10,6 @@ UI, Workflows & Features * "git history" history rewriting (experimental) command has been added. - * "git replay" is taught to drop commits that become empty (not the ones that are empty in the original). @@ -32,6 +31,11 @@ UI, Workflows & Features * Command line completion (in contrib/) update for "stash import/export". + * "git repo info" learns "--keys" action to list known keys. + + * Extend the alias configuration syntax to allow aliases using + characters outside ASCII alphanumeric (plus '-'). + Performance, Internal Implementation, Development Support etc. -------------------------------------------------------------- @@ -76,6 +80,12 @@ Performance, Internal Implementation, Development Support etc. already do on Linux and Windows. Also adjust the way Windows implementation reports this information to match the other two. + * A handful of places used refs_for_each_ref_in() API incorrectly, + which has been corrected. + + * Some tests assumed "iconv" is available without honoring ICONV + prerequisite, which has been corrected. + Fixes since v2.53 ----------------- @@ -130,6 +140,21 @@ Fixes since v2.53 been fixed. (merge f4eff7116d ps/pack-concat-wo-backfill later to maint). + * "git switch ", in an attempt to create a local branch + after a remote tracking branch of the same name gave an advise + message to disambiguate using "git checkout", which has been + updated to use "git switch". + (merge 12fee11f21 jc/checkout-switch-restore later to maint). + + * It does not make much sense to apply the "incomplete-line" + whitespace rule to symbolic links, whose contents almost always + lack the final newline. "git apply" and "git diff" are now taught + to exclude them for a change to symbolic links. + (merge 6a41481c6d jc/whitespace-incomplete-line later to maint). + + * "git format-patch --from=" did not honor the command line + option when writing out the cover letter, which has been corrected. + * Other code cleanup, docfix, build fix, etc. (merge d79fff4a11 jk/remote-tracking-ref-leakfix later to maint). (merge 7a747f972d dd/t5403-modernise later to maint). @@ -151,3 +176,7 @@ Fixes since v2.53 (merge 2668b6bdc4 jc/doc-rerere-update later to maint). (merge 2f99f50f2d jc/doc-cg-c-comment later to maint). (merge a454cdca42 kh/doc-am-format-sendmail later to maint). + (merge 8b0061b5c5 jk/ref-filter-lrstrip-optim later to maint). + (merge 5133837392 ps/ci-gitlab-msvc-updates later to maint). + (merge 143e84958c db/doc-fetch-jobs-auto later to maint). + (merge 0678e01f02 ap/use-test-seq-f-more later to maint). diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc index 80ce17d2deb269..115fdbb1e3f3ba 100644 --- a/Documentation/config/alias.adoc +++ b/Documentation/config/alias.adoc @@ -1,12 +1,46 @@ alias.*:: - Command aliases for the linkgit:git[1] command wrapper - e.g. - after defining `alias.last = cat-file commit HEAD`, the invocation - `git last` is equivalent to `git cat-file commit HEAD`. To avoid - confusion and troubles with script usage, aliases that - hide existing Git commands are ignored except for deprecated - commands. Arguments are split by - spaces, the usual shell quoting and escaping are supported. - A quote pair or a backslash can be used to quote them. +alias.*.command:: + Command aliases for the linkgit:git[1] command wrapper. Aliases + can be defined using two syntaxes: ++ +-- +1. Without a subsection, e.g., `[alias] co = checkout`. The alias + name ("co" in this example) is + limited to ASCII alphanumeric characters and `-`, + and is matched case-insensitively. +2. With a subsection, e.g., `[alias "co"] command = checkout`. The + alias name can contain any characters (except for newlines and NUL bytes), + including UTF-8, and is matched case-sensitively as raw bytes. + You define the action of the alias in the `command`. +-- ++ +Examples: ++ +---- +# Without subsection (ASCII alphanumeric and dash only) +[alias] + co = checkout + st = status + +# With subsection (allows any characters, including UTF-8) +[alias "hämta"] + command = fetch +[alias "rätta till"] + command = commit --amend +---- ++ +With a Git alias defined, e.g., + + $ git config --global alias.last "cat-file commit HEAD" + # Which is equivalent to + $ git config --global alias.last.command "cat-file commit HEAD" + +`git last` is equivalent to `git cat-file commit HEAD`. To avoid +confusion and troubles with script usage, aliases that +hide existing Git commands are ignored except for deprecated +commands. Arguments are split by +spaces, the usual shell quoting and escaping are supported. +A quote pair or a backslash can be used to quote them. + Note that the first word of an alias does not necessarily have to be a command. It can be a command-line option that will be passed into the diff --git a/Documentation/config/pack.adoc b/Documentation/config/pack.adoc index 75402d5579d4b2..fa997c8597d5bd 100644 --- a/Documentation/config/pack.adoc +++ b/Documentation/config/pack.adoc @@ -160,12 +160,13 @@ pack.usePathWalk:: processes. See linkgit:git-pack-objects[1] for full details. pack.preferBitmapTips:: + Specifies a ref hierarchy (e.g., "refs/heads/"); can be + given multiple times to specify more than one hierarchies. When selecting which commits will receive bitmaps, prefer a - commit at the tip of any reference that is a suffix of any value - of this configuration over any other commits in the "selection - window". + commit at the tip of a reference that is contained in any of + the configured hierarchies. + -Note that setting this configuration to `refs/foo` does not mean that +Note that setting this configuration to `refs/foo/` does not mean that the commits at the tips of `refs/foo/bar` and `refs/foo/baz` will necessarily be selected. This is because commits are selected for bitmaps from within a series of windows of variable length. diff --git a/Documentation/fetch-options.adoc b/Documentation/fetch-options.adoc index a0cfb50d89e2ef..81a9d7f9bbc11d 100644 --- a/Documentation/fetch-options.adoc +++ b/Documentation/fetch-options.adoc @@ -253,6 +253,8 @@ endif::git-pull[] `--jobs=`:: Parallelize all forms of fetching up to __ jobs at a time. + +A value of 0 will use some reasonable default. ++ If the `--multiple` option was specified, the different remotes will be fetched in parallel. If multiple submodules are fetched, they will be fetched in parallel. To control them independently, use the config settings diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc index bac9b818f3b418..36146006fa9855 100644 --- a/Documentation/git-format-patch.adoc +++ b/Documentation/git-format-patch.adoc @@ -282,11 +282,12 @@ e.g., `--rfc='-(WIP)'` results in "PATCH (WIP)". --from:: --from=:: - Use `ident` in the `From:` header of each commit email. If the - author ident of the commit is not textually identical to the - provided `ident`, place a `From:` header in the body of the - message with the original author. If no `ident` is given, use - the committer ident. + Use `ident` in the `From:` header of each email. In case of a + commit email, if the author ident of the commit is not textually + identical to the provided `ident`, place a `From:` header in the + body of the message with the original author. If no `ident` is + given, or if the option is not passed at all, use the ident of + the current committer. + Note that this option is only useful if you are actually sending the emails and want to identify yourself as the sender, but retain the diff --git a/Documentation/git-history.adoc b/Documentation/git-history.adoc index 154e262b76a698..cc019de69764d2 100644 --- a/Documentation/git-history.adoc +++ b/Documentation/git-history.adoc @@ -8,7 +8,7 @@ git-history - EXPERIMENTAL: Rewrite history SYNOPSIS -------- [synopsis] -git history reword [--ref-action=(branches|head|print)] +git history reword [--dry-run] [--update-refs=(branches|head)] DESCRIPTION ----------- @@ -60,13 +60,17 @@ The following commands are available to rewrite history in different ways: OPTIONS ------- -`--ref-action=(branches|head|print)`:: +`--dry-run`:: + Do not update any references, but instead print any ref updates in a + format that can be consumed by linkgit:git-update-ref[1]. Necessary new + objects will be written into the repository, so applying these printed + ref updates is generally safe. + +`--update-refs=(branches|head)`:: Control which references will be updated by the command, if any. With `branches`, all local branches that point to commits which are descendants of the original commit will be rewritten. With `head`, only - the current `HEAD` reference will be rewritten. With `print`, all - updates as they would be performed with `branches` are printed in a - format that can be consumed by linkgit:git-update-ref[1]. + the current `HEAD` reference will be rewritten. Defaults to `branches`. GIT --- diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index 7d70270dfa5716..319d30bd86e7f4 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -8,8 +8,9 @@ git-repo - Retrieve information about the repository SYNOPSIS -------- [synopsis] -git repo info [--format=(keyvalue|nul) | -z] [--all | ...] -git repo structure [--format=(table|keyvalue|nul) | -z] +git repo info [--format=(lines|nul) | -z] [--all | ...] +git repo info --keys [--format=(lines|nul) | -z] +git repo structure [--format=(table|lines|nul) | -z] DESCRIPTION ----------- @@ -19,7 +20,7 @@ THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE. COMMANDS -------- -`info [--format=(keyvalue|nul) | -z] [--all | ...]`:: +`info [--format=(lines|nul) | -z] [--all | ...]`:: Retrieve metadata-related information about the current repository. Only the requested data will be returned based on their keys (see "INFO KEYS" section below). @@ -30,21 +31,32 @@ requested. The `--all` flag requests the values for all the available keys. The output format can be chosen through the flag `--format`. Two formats are supported: + -`keyvalue`::: + +`lines`::: output key-value pairs one per line using the `=` character as the delimiter between the key and the value. Values containing "unusual" characters are quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]). This is the default. `nul`::: - similar to `keyvalue`, but using a newline character as the delimiter + similar to `lines`, but using a newline character as the delimiter between the key and the value and using a NUL character after each value. This format is better suited for being parsed by another applications than - `keyvalue`. Unlike in the `keyvalue` format, the values are never quoted. + `lines`. Unlike in the `lines` format, the values are never quoted. + `-z` is an alias for `--format=nul`. -`structure [--format=(table|keyvalue|nul) | -z]`:: +`info --keys [--format=(lines|nul) | -z]`:: + List all the available keys, one per line. The output format can be chosen + through the flag `--format`. The following formats are supported: ++ +`lines`::: + Output the keys one per line. This is the default. + +`nul`::: + Similar to `lines`, but using a _NUL_ character after each value. + +`structure [--format=(table|lines|nul) | -z]`:: Retrieve statistics about the current repository structure. The following kinds of information are reported: + @@ -61,17 +73,17 @@ supported: change and is not intended for machine parsing. This is the default format. -`keyvalue`::: +`lines`::: Each line of output contains a key-value pair for a repository stat. The '=' character is used to delimit between the key and the value. Values containing "unusual" characters are quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]). `nul`::: - Similar to `keyvalue`, but uses a NUL character to delimit between + Similar to `lines`, but uses a NUL character to delimit between key-value pairs instead of a newline. Also uses a newline character as the delimiter between the key and value instead of '='. Unlike the - `keyvalue` format, values containing "unusual" characters are never + `lines` format, values containing "unusual" characters are never quoted. + `-z` is an alias for `--format=nul`. diff --git a/alias.c b/alias.c index 1a1a141a0aebbb..0d636278bc1d11 100644 --- a/alias.c +++ b/alias.c @@ -13,23 +13,53 @@ struct config_alias_data { struct string_list *list; }; -static int config_alias_cb(const char *key, const char *value, +static int config_alias_cb(const char *var, const char *value, const struct config_context *ctx UNUSED, void *d) { struct config_alias_data *data = d; - const char *p; + const char *subsection, *key; + size_t subsection_len; - if (!skip_prefix(key, "alias.", &p)) + if (parse_config_key(var, "alias", &subsection, &subsection_len, + &key) < 0) + return 0; + + /* + * Two config syntaxes: + * - alias.name = value (without subsection, case-insensitive) + * - [alias "name"] + * command = value (with subsection, case-sensitive) + */ + if (subsection && strcmp(key, "command")) return 0; if (data->alias) { - if (!strcasecmp(p, data->alias)) { + int match; + + if (subsection) + match = (strlen(data->alias) == subsection_len && + !strncmp(data->alias, subsection, + subsection_len)); + else + match = !strcasecmp(data->alias, key); + + if (match) { FREE_AND_NULL(data->v); return git_config_string(&data->v, - key, value); + var, value); } } else if (data->list) { - string_list_append(data->list, p); + struct string_list_item *item; + + if (!value) + return config_error_nonbool(var); + + if (subsection) + item = string_list_append_nodup(data->list, + xmemdupz(subsection, subsection_len)); + else + item = string_list_append(data->list, key); + item->util = xstrdup(value); } return 0; diff --git a/apply.c b/apply.c index e4c4bf7af925b9..d044c95d50b8a4 100644 --- a/apply.c +++ b/apply.c @@ -1725,6 +1725,26 @@ static int parse_fragment(struct apply_state *state, unsigned long oldlines, newlines; unsigned long leading, trailing; + /* do not complain a symbolic link being an incomplete line */ + if (patch->ws_rule & WS_INCOMPLETE_LINE) { + /* + * We want to figure out if the postimage is a + * symbolic link when applying the patch normally, or + * if the preimage is a symbolic link when applying + * the patch in reverse. A normal patch only has + * old_mode without new_mode. If it changes the + * filemode, new_mode has value, which is different + * from old_mode. + */ + unsigned mode = (state->apply_in_reverse + ? patch->old_mode + : patch->new_mode + ? patch->new_mode + : patch->old_mode); + if (mode && S_ISLNK(mode)) + patch->ws_rule &= ~WS_INCOMPLETE_LINE; + } + offset = parse_fragment_header(line, len, fragment); if (offset < 0) return -1; diff --git a/bisect.c b/bisect.c index b313f1324009b6..2bdad4ee42e937 100644 --- a/bisect.c +++ b/bisect.c @@ -1180,8 +1180,7 @@ int estimate_bisect_steps(int all) static int mark_for_removal(const struct reference *ref, void *cb_data) { struct string_list *refs = cb_data; - char *bisect_ref = xstrfmt("refs/bisect%s", ref->name); - string_list_append(refs, bisect_ref); + string_list_append(refs, ref->name); return 0; } @@ -1190,16 +1189,15 @@ int bisect_clean_state(void) int result = 0; /* There may be some refs packed during bisection */ - struct string_list refs_for_removal = STRING_LIST_INIT_NODUP; - refs_for_each_ref_in(get_main_ref_store(the_repository), - "refs/bisect", mark_for_removal, - (void *) &refs_for_removal); - string_list_append(&refs_for_removal, xstrdup("BISECT_HEAD")); - string_list_append(&refs_for_removal, xstrdup("BISECT_EXPECTED_REV")); + struct string_list refs_for_removal = STRING_LIST_INIT_DUP; + refs_for_each_fullref_in(get_main_ref_store(the_repository), + "refs/bisect/", NULL, mark_for_removal, + &refs_for_removal); + string_list_append(&refs_for_removal, "BISECT_HEAD"); + string_list_append(&refs_for_removal, "BISECT_EXPECTED_REV"); result = refs_delete_refs(get_main_ref_store(the_repository), "bisect: remove", &refs_for_removal, REF_NO_DEREF); - refs_for_removal.strdup_strings = 1; string_list_clear(&refs_for_removal, 0); unlink_or_warn(git_path_bisect_ancestors_ok()); unlink_or_warn(git_path_bisect_log()); diff --git a/builtin/checkout.c b/builtin/checkout.c index f7b313816e7708..7d63e7292483db 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -43,22 +43,6 @@ #include "parallel-checkout.h" #include "add-interactive.h" -static const char * const checkout_usage[] = { - N_("git checkout [] "), - N_("git checkout [] [] -- ..."), - NULL, -}; - -static const char * const switch_branch_usage[] = { - N_("git switch [] []"), - NULL, -}; - -static const char * const restore_usage[] = { - N_("git restore [] [--source=] ..."), - NULL, -}; - struct checkout_opts { int patch_mode; int patch_context; @@ -1293,9 +1277,17 @@ static void setup_new_branch_info_and_source_tree( } } + +enum checkout_command { + CHECKOUT_CHECKOUT = 1, + CHECKOUT_SWITCH = 2, + CHECKOUT_RESTORE = 3, +}; + static char *parse_remote_branch(const char *arg, struct object_id *rev, - int could_be_checkout_paths) + int could_be_checkout_paths, + enum checkout_command which_command) { int num_matches = 0; char *remote = unique_tracking_name(arg, rev, &num_matches); @@ -1308,14 +1300,30 @@ static char *parse_remote_branch(const char *arg, if (!remote && num_matches > 1) { if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) { + const char *cmdname; + + switch (which_command) { + case CHECKOUT_CHECKOUT: + cmdname = "checkout"; + break; + case CHECKOUT_SWITCH: + cmdname = "switch"; + break; + default: + BUG("command <%d> should not reach parse_remote_branch", + which_command); + break; + } + advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n" "you can do so by fully qualifying the name with the --track option:\n" "\n" - " git checkout --track origin/\n" + " git %s --track origin/\n" "\n" "If you'd like to always have checkouts of an ambiguous prefer\n" "one remote, e.g. the 'origin' remote, consider setting\n" - "checkout.defaultRemote=origin in your config.")); + "checkout.defaultRemote=origin in your config."), + cmdname); } die(_("'%s' matched multiple (%d) remote tracking branches"), @@ -1327,6 +1335,7 @@ static char *parse_remote_branch(const char *arg, static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, + enum checkout_command which_command, struct branch_info *new_branch_info, struct checkout_opts *opts, struct object_id *rev) @@ -1436,7 +1445,8 @@ static int parse_branchname_arg(int argc, const char **argv, if (recover_with_dwim) { remote = parse_remote_branch(arg, rev, - could_be_checkout_paths); + could_be_checkout_paths, + which_command); if (remote) { *new_branch = arg; arg = remote; @@ -1767,12 +1777,44 @@ static char cb_option = 'b'; static int checkout_main(int argc, const char **argv, const char *prefix, struct checkout_opts *opts, struct option *options, - const char * const usagestr[]) + enum checkout_command which_command) { int parseopt_flags = 0; struct branch_info new_branch_info = { 0 }; int ret; + static const char * const checkout_usage[] = { + N_("git checkout [] "), + N_("git checkout [] [] -- ..."), + NULL, + }; + + static const char * const switch_branch_usage[] = { + N_("git switch [] []"), + NULL, + }; + + static const char * const restore_usage[] = { + N_("git restore [] [--source=] ..."), + NULL, + }; + + const char * const *usagestr; + + switch (which_command) { + case CHECKOUT_CHECKOUT: + usagestr = checkout_usage; + break; + case CHECKOUT_SWITCH: + usagestr = switch_branch_usage; + break; + case CHECKOUT_RESTORE: + usagestr = restore_usage; + break; + default: + BUG("no such checkout variant %d", which_command); + } + opts->overwrite_ignore = 1; opts->prefix = prefix; opts->show_progress = -1; @@ -1893,7 +1935,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, opts->dwim_new_local_branch && opts->track == BRANCH_TRACK_UNSPECIFIED && !opts->new_branch; - int n = parse_branchname_arg(argc, argv, dwim_ok, + int n = parse_branchname_arg(argc, argv, dwim_ok, which_command, &new_branch_info, opts, &rev); argv += n; argc -= n; @@ -2032,7 +2074,7 @@ int cmd_checkout(int argc, options = add_checkout_path_options(&opts, options); return checkout_main(argc, argv, prefix, &opts, options, - checkout_usage); + CHECKOUT_CHECKOUT); } int cmd_switch(int argc, @@ -2071,7 +2113,7 @@ int cmd_switch(int argc, cb_option = 'c'; return checkout_main(argc, argv, prefix, &opts, options, - switch_branch_usage); + CHECKOUT_SWITCH); } int cmd_restore(int argc, @@ -2107,5 +2149,5 @@ int cmd_restore(int argc, options = add_checkout_path_options(&opts, options); return checkout_main(argc, argv, prefix, &opts, options, - restore_usage); + CHECKOUT_RESTORE); } diff --git a/builtin/help.c b/builtin/help.c index c09cbc8912deb6..86a3d03a9bfc02 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -54,6 +54,7 @@ static enum help_action { HELP_ACTION_DEVELOPER_INTERFACES, HELP_ACTION_CONFIG_FOR_COMPLETION, HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, + HELP_ACTION_ALIASES_FOR_COMPLETION, } cmd_mode; static char *html_path; @@ -90,6 +91,8 @@ static struct option builtin_help_options[] = { HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN), OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "", HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN), + OPT_CMDMODE_F(0, "aliases-for-completion", &cmd_mode, "", + HELP_ACTION_ALIASES_FOR_COMPLETION, PARSE_OPT_HIDDEN), OPT_END(), }; @@ -691,6 +694,16 @@ int cmd_help(int argc, help_format); list_config_help(SHOW_CONFIG_SECTIONS); return 0; + case HELP_ACTION_ALIASES_FOR_COMPLETION: { + struct string_list alias_list = STRING_LIST_INIT_DUP; + opt_mode_usage(argc, "--aliases-for-completion", help_format); + list_aliases(&alias_list); + for (size_t i = 0; i < alias_list.nr; i++) + printf("%s%c%s%c", alias_list.items[i].string, '\n', + (char *)alias_list.items[i].util, '\0'); + string_list_clear(&alias_list, 1); + return 0; + } case HELP_ACTION_CONFIG: opt_mode_usage(argc, "--config", help_format); setup_pager(the_repository); diff --git a/builtin/history.c b/builtin/history.c index 8dcb9a604665f8..1cf6c668cfd814 100644 --- a/builtin/history.c +++ b/builtin/history.c @@ -18,7 +18,7 @@ #include "wt-status.h" #define GIT_HISTORY_REWORD_USAGE \ - N_("git history reword [--ref-action=(branches|head|print)]") + N_("git history reword [--dry-run] [--update-refs=(branches|head)]") static void change_data_free(void *util, const char *str UNUSED) { @@ -155,7 +155,6 @@ enum ref_action { REF_ACTION_DEFAULT, REF_ACTION_BRANCHES, REF_ACTION_HEAD, - REF_ACTION_PRINT, }; static int parse_ref_action(const struct option *opt, const char *value, int unset) @@ -167,40 +166,58 @@ static int parse_ref_action(const struct option *opt, const char *value, int uns *action = REF_ACTION_BRANCHES; } else if (!strcmp(value, "head")) { *action = REF_ACTION_HEAD; - } else if (!strcmp(value, "print")) { - *action = REF_ACTION_PRINT; } else { - return error(_("%s expects one of 'branches', 'head' or 'print'"), + return error(_("%s expects one of 'branches' or 'head'"), opt->long_name); } return 0; } -static int handle_reference_updates(enum ref_action action, - struct repository *repo, - struct commit *original, - struct commit *rewritten, - const char *reflog_msg) +static int revwalk_contains_merges(struct repository *repo, + const struct strvec *revwalk_args) { - const struct name_decoration *decoration; - struct replay_revisions_options opts = { 0 }; - struct replay_result result = { 0 }; - struct ref_transaction *transaction = NULL; struct strvec args = STRVEC_INIT; - struct strbuf err = STRBUF_INIT; - struct commit *head = NULL; struct rev_info revs; - char hex[GIT_MAX_HEXSZ + 1]; - bool detached_head; - int head_flags = 0; int ret; - refs_read_ref_full(get_main_ref_store(repo), "HEAD", - RESOLVE_REF_NO_RECURSE, NULL, &head_flags); - detached_head = !(head_flags & REF_ISSYMREF); + strvec_pushv(&args, revwalk_args->v); + strvec_push(&args, "--min-parents=2"); repo_init_revisions(repo, &revs, NULL); + + setup_revisions_from_strvec(&args, &revs, NULL); + if (args.nr != 1) + BUG("revisions were set up with invalid argument"); + + if (prepare_revision_walk(&revs) < 0) { + ret = error(_("error preparing revisions")); + goto out; + } + + if (get_revision(&revs)) { + ret = error(_("replaying merge commits is not supported yet!")); + goto out; + } + + reset_revision_walk(); + ret = 0; + +out: + release_revisions(&revs); + strvec_clear(&args); + return ret; +} + +static int setup_revwalk(struct repository *repo, + enum ref_action action, + struct commit *original, + struct rev_info *revs) +{ + struct strvec args = STRVEC_INIT; + int ret; + + repo_init_revisions(repo, revs, NULL); strvec_push(&args, "ignored"); strvec_push(&args, "--reverse"); strvec_push(&args, "--topo-order"); @@ -224,6 +241,7 @@ static int handle_reference_updates(enum ref_action action, */ if (action == REF_ACTION_HEAD) { struct commit_list *from_list = NULL; + struct commit *head; head = lookup_commit_reference_by_name("HEAD"); if (!head) { @@ -240,7 +258,7 @@ static int handle_reference_updates(enum ref_action action, goto out; } else if (!ret) { ret = error(_("rewritten commit must be an ancestor " - "of HEAD when using --ref-action=head")); + "of HEAD when using --update-refs=head")); goto out; } @@ -250,92 +268,131 @@ static int handle_reference_updates(enum ref_action action, strvec_push(&args, "HEAD"); } - setup_revisions_from_strvec(&args, &revs, NULL); + ret = revwalk_contains_merges(repo, &args); + if (ret < 0) + goto out; + + setup_revisions_from_strvec(&args, revs, NULL); if (args.nr != 1) BUG("revisions were set up with invalid argument"); + ret = 0; + +out: + strvec_clear(&args); + return ret; +} + +static int handle_ref_update(struct ref_transaction *transaction, + const char *refname, + const struct object_id *new_oid, + const struct object_id *old_oid, + const char *reflog_msg, + struct strbuf *err) +{ + if (!transaction) { + printf("update %s %s %s\n", + refname, oid_to_hex(new_oid), oid_to_hex(old_oid)); + return 0; + } + + return ref_transaction_update(transaction, refname, new_oid, old_oid, + NULL, NULL, 0, reflog_msg, err); +} + +static int handle_reference_updates(struct rev_info *revs, + enum ref_action action, + struct commit *original, + struct commit *rewritten, + const char *reflog_msg, + int dry_run) +{ + const struct name_decoration *decoration; + struct replay_revisions_options opts = { 0 }; + struct replay_result result = { 0 }; + struct ref_transaction *transaction = NULL; + struct strbuf err = STRBUF_INIT; + char hex[GIT_MAX_HEXSZ + 1]; + bool detached_head; + int head_flags = 0; + int ret; + + refs_read_ref_full(get_main_ref_store(revs->repo), "HEAD", + RESOLVE_REF_NO_RECURSE, NULL, &head_flags); + detached_head = !(head_flags & REF_ISSYMREF); + opts.onto = oid_to_hex_r(hex, &rewritten->object.oid); - ret = replay_revisions(&revs, &opts, &result); + ret = replay_revisions(revs, &opts, &result); if (ret) goto out; - switch (action) { - case REF_ACTION_BRANCHES: - case REF_ACTION_HEAD: - transaction = ref_store_transaction_begin(get_main_ref_store(repo), 0, &err); + if (action != REF_ACTION_BRANCHES && action != REF_ACTION_HEAD) + BUG("unsupported ref action %d", action); + + if (!dry_run) { + transaction = ref_store_transaction_begin(get_main_ref_store(revs->repo), 0, &err); if (!transaction) { ret = error(_("failed to begin ref transaction: %s"), err.buf); goto out; } + } - for (size_t i = 0; i < result.updates_nr; i++) { - ret = ref_transaction_update(transaction, - result.updates[i].refname, - &result.updates[i].new_oid, - &result.updates[i].old_oid, - NULL, NULL, 0, reflog_msg, &err); - if (ret) { - ret = error(_("failed to update ref '%s': %s"), - result.updates[i].refname, err.buf); - goto out; - } + for (size_t i = 0; i < result.updates_nr; i++) { + ret = handle_ref_update(transaction, + result.updates[i].refname, + &result.updates[i].new_oid, + &result.updates[i].old_oid, + reflog_msg, &err); + if (ret) { + ret = error(_("failed to update ref '%s': %s"), + result.updates[i].refname, err.buf); + goto out; } + } + + /* + * `replay_revisions()` only updates references that are + * ancestors of `rewritten`, so we need to manually + * handle updating references that point to `original`. + */ + for (decoration = get_name_decoration(&original->object); + decoration; + decoration = decoration->next) + { + if (decoration->type != DECORATION_REF_LOCAL && + decoration->type != DECORATION_REF_HEAD) + continue; + + if (action == REF_ACTION_HEAD && + decoration->type != DECORATION_REF_HEAD) + continue; /* - * `replay_revisions()` only updates references that are - * ancestors of `rewritten`, so we need to manually - * handle updating references that point to `original`. + * We only need to update HEAD separately in case it's + * detached. If it's not we'd already update the branch + * it is pointing to. */ - for (decoration = get_name_decoration(&original->object); - decoration; - decoration = decoration->next) - { - if (decoration->type != DECORATION_REF_LOCAL && - decoration->type != DECORATION_REF_HEAD) - continue; - - if (action == REF_ACTION_HEAD && - decoration->type != DECORATION_REF_HEAD) - continue; - - /* - * We only need to update HEAD separately in case it's - * detached. If it's not we'd already update the branch - * it is pointing to. - */ - if (action == REF_ACTION_BRANCHES && - decoration->type == DECORATION_REF_HEAD && - !detached_head) - continue; - - ret = ref_transaction_update(transaction, - decoration->name, - &rewritten->object.oid, - &original->object.oid, - NULL, NULL, 0, reflog_msg, &err); - if (ret) { - ret = error(_("failed to update ref '%s': %s"), - decoration->name, err.buf); - goto out; - } - } - - if (ref_transaction_commit(transaction, &err)) { - ret = error(_("failed to commit ref transaction: %s"), err.buf); + if (action == REF_ACTION_BRANCHES && + decoration->type == DECORATION_REF_HEAD && + !detached_head) + continue; + + ret = handle_ref_update(transaction, + decoration->name, + &rewritten->object.oid, + &original->object.oid, + reflog_msg, &err); + if (ret) { + ret = error(_("failed to update ref '%s': %s"), + decoration->name, err.buf); goto out; } + } - break; - case REF_ACTION_PRINT: - for (size_t i = 0; i < result.updates_nr; i++) - printf("update %s %s %s\n", - result.updates[i].refname, - oid_to_hex(&result.updates[i].new_oid), - oid_to_hex(&result.updates[i].old_oid)); - break; - default: - BUG("unsupported ref action %d", action); + if (transaction && ref_transaction_commit(transaction, &err)) { + ret = error(_("failed to commit ref transaction: %s"), err.buf); + goto out; } ret = 0; @@ -343,9 +400,7 @@ static int handle_reference_updates(enum ref_action action, out: ref_transaction_free(transaction); replay_result_release(&result); - release_revisions(&revs); strbuf_release(&err); - strvec_clear(&args); return ret; } @@ -359,14 +414,18 @@ static int cmd_history_reword(int argc, NULL, }; enum ref_action action = REF_ACTION_DEFAULT; + int dry_run = 0; struct option options[] = { - OPT_CALLBACK_F(0, "ref-action", &action, N_(""), - N_("control ref update behavior (branches|head|print)"), + OPT_CALLBACK_F(0, "update-refs", &action, N_(""), + N_("control which refs should be updated (branches|head)"), PARSE_OPT_NONEG, parse_ref_action), + OPT_BOOL('n', "dry-run", &dry_run, + N_("perform a dry-run without updating any refs")), OPT_END(), }; struct strbuf reflog_msg = STRBUF_INIT; struct commit *original, *rewritten; + struct rev_info revs; int ret; argc = parse_options(argc, argv, prefix, options, usage, 0); @@ -385,6 +444,10 @@ static int cmd_history_reword(int argc, goto out; } + ret = setup_revwalk(repo, action, original, &revs); + if (ret) + goto out; + ret = commit_tree_with_edited_message(repo, "reworded", original, &rewritten); if (ret < 0) { ret = error(_("failed writing reworded commit")); @@ -393,8 +456,8 @@ static int cmd_history_reword(int argc, strbuf_addf(&reflog_msg, "reword: updating %s", argv[0]); - ret = handle_reference_updates(action, repo, original, rewritten, - reflog_msg.buf); + ret = handle_reference_updates(&revs, action, original, rewritten, + reflog_msg.buf, dry_run); if (ret < 0) { ret = error(_("failed replaying descendants")); goto out; @@ -404,6 +467,7 @@ static int cmd_history_reword(int argc, out: strbuf_release(&reflog_msg); + release_revisions(&revs); return ret; } diff --git a/builtin/log.c b/builtin/log.c index 8ab6d3a9439afd..3c76ee516901c0 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1331,7 +1331,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, int quiet, const struct format_config *cfg) { - const char *committer; + const char *from; struct shortlog log; struct strbuf sb = STRBUF_INIT; int i; @@ -1344,7 +1344,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, if (!cmit_fmt_is_mail(rev->commit_format)) die(_("cover letter needs email format")); - committer = git_committer_info(0); + from = cfg->from ? cfg->from : git_committer_info(0); if (use_separate_file && open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) @@ -1367,7 +1367,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, pp.date_mode.type = DATE_RFC2822; pp.rev = rev; pp.encode_email_headers = rev->encode_email_headers; - pp_user_info(&pp, NULL, &sb, committer, encoding); + pp_user_info(&pp, NULL, &sb, from, encoding); prepare_cover_text(&pp, description_file, branch_name, &sb, encoding, need_8bit_cte, cfg); fprintf(rev->diffopt.file, "%s\n", sb.buf); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index cfb03d4c09e556..433d77cf278320 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -4561,22 +4561,6 @@ static int mark_bitmap_preferred_tip(const struct reference *ref, void *data UNU return 0; } -static void mark_bitmap_preferred_tips(void) -{ - struct string_list_item *item; - const struct string_list *preferred_tips; - - preferred_tips = bitmap_preferred_tips(the_repository); - if (!preferred_tips) - return; - - for_each_string_list_item(item, preferred_tips) { - refs_for_each_ref_in(get_main_ref_store(the_repository), - item->string, mark_bitmap_preferred_tip, - NULL); - } -} - static inline int is_oid_uninteresting(struct repository *repo, struct object_id *oid) { @@ -4717,7 +4701,8 @@ static void get_object_list(struct rev_info *revs, struct strvec *argv) load_delta_islands(the_repository, progress); if (write_bitmap_index) - mark_bitmap_preferred_tips(); + for_each_preferred_bitmap_tip(the_repository, mark_bitmap_preferred_tip, + NULL); if (!fn_show_object) fn_show_object = show_object; diff --git a/builtin/repo.c b/builtin/repo.c index 0ea045abc13f8c..6a62a6020a5115 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -17,8 +17,9 @@ #include "utf8.h" static const char *const repo_usage[] = { - "git repo info [--format=(keyvalue|nul) | -z] [--all | ...]", - "git repo structure [--format=(table|keyvalue|nul) | -z]", + "git repo info [--format=(lines|nul) | -z] [--all | ...]", + "git repo info --keys [--format=(lines|nul) | -z]", + "git repo structure [--format=(table|lines|nul) | -z]", NULL }; @@ -26,7 +27,7 @@ typedef int get_value_fn(struct repository *repo, struct strbuf *buf); enum output_format { FORMAT_TABLE, - FORMAT_KEYVALUE, + FORMAT_NEWLINE_TERMINATED, FORMAT_NUL_TERMINATED, }; @@ -91,7 +92,7 @@ static void print_field(enum output_format format, const char *key, const char *value) { switch (format) { - case FORMAT_KEYVALUE: + case FORMAT_NEWLINE_TERMINATED: printf("%s=", key); quote_c_style(value, NULL, stdout, 0); putchar('\n'); @@ -148,6 +149,29 @@ static int print_all_fields(struct repository *repo, return 0; } +static int print_keys(enum output_format format) +{ + char sep; + + switch (format) { + case FORMAT_NEWLINE_TERMINATED: + sep = '\n'; + break; + case FORMAT_NUL_TERMINATED: + sep = '\0'; + break; + default: + die(_("--keys can only be used with --format=lines or --format=nul")); + } + + for (size_t i = 0; i < ARRAY_SIZE(repo_info_fields); i++) { + const struct field *field = &repo_info_fields[i]; + printf("%s%c", field->key, sep); + } + + return 0; +} + static int parse_format_cb(const struct option *opt, const char *arg, int unset UNUSED) { @@ -157,8 +181,8 @@ static int parse_format_cb(const struct option *opt, *format = FORMAT_NUL_TERMINATED; else if (!strcmp(arg, "nul")) *format = FORMAT_NUL_TERMINATED; - else if (!strcmp(arg, "keyvalue")) - *format = FORMAT_KEYVALUE; + else if (!strcmp(arg, "lines")) + *format = FORMAT_NEWLINE_TERMINATED; else if (!strcmp(arg, "table")) *format = FORMAT_TABLE; else @@ -170,8 +194,9 @@ static int parse_format_cb(const struct option *opt, static int cmd_repo_info(int argc, const char **argv, const char *prefix, struct repository *repo) { - enum output_format format = FORMAT_KEYVALUE; + enum output_format format = FORMAT_NEWLINE_TERMINATED; int all_keys = 0; + int show_keys = 0; struct option options[] = { OPT_CALLBACK_F(0, "format", &format, N_("format"), N_("output format"), @@ -181,11 +206,19 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, PARSE_OPT_NONEG | PARSE_OPT_NOARG, parse_format_cb), OPT_BOOL(0, "all", &all_keys, N_("print all keys/values")), + OPT_BOOL(0, "keys", &show_keys, N_("show keys")), OPT_END() }; argc = parse_options(argc, argv, prefix, options, repo_usage, 0); - if (format != FORMAT_KEYVALUE && format != FORMAT_NUL_TERMINATED) + + if (show_keys && (all_keys || argc)) + die(_("--keys cannot be used with a or --all")); + + if (show_keys) + return print_keys(format); + + if (format != FORMAT_NEWLINE_TERMINATED && format != FORMAT_NUL_TERMINATED) die(_("unsupported output format")); if (all_keys && argc) @@ -671,7 +704,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix, stats_table_setup_structure(&table, &stats); stats_table_print_structure(&table); break; - case FORMAT_KEYVALUE: + case FORMAT_NEWLINE_TERMINATED: structure_keyvalue_print(&stats, '=', '\n'); break; case FORMAT_NUL_TERMINATED: diff --git a/ci/run-test-slice-meson.sh b/ci/run-test-slice-meson.sh index 961c94fba0b2ee..a6df927ba591f8 100755 --- a/ci/run-test-slice-meson.sh +++ b/ci/run-test-slice-meson.sh @@ -9,5 +9,5 @@ group "Run tests" \ meson test -C "$1" --no-rebuild --print-errorlogs \ - --test-args="$GIT_TEST_OPTS" --slice "$((1+$2))/$3" || + --test-args="$GIT_TEST_OPTS" --slice "$(($2))/$3" || handle_failed_tests diff --git a/ci/run-test-slice.sh b/ci/run-test-slice.sh index 0444c79c023c82..ff948e397fcb70 100755 --- a/ci/run-test-slice.sh +++ b/ci/run-test-slice.sh @@ -5,9 +5,9 @@ . ${0%/*}/lib.sh -group "Run tests" make --quiet -C t T="$(cd t && - ./helper/test-tool path-utils slice-tests "$1" "$2" t[0-9]*.sh | - tr '\n' ' ')" || +TESTS=$(cd t && ./helper/test-tool path-utils slice-tests "$1" "$2" t[0-9]*.sh) + +group "Run tests" make --quiet -C t T="$(echo "$TESTS" | tr '\n' ' ')" || handle_failed_tests # We only have one unit test at the moment, so run it in the first slice diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh index f5877bd7a1ea59..c32186a977e2d1 100644 --- a/contrib/completion/git-completion.zsh +++ b/contrib/completion/git-completion.zsh @@ -202,7 +202,7 @@ __git_zsh_cmd_common () __git_zsh_cmd_alias () { local -a list - list=(${${(0)"$(git config -z --get-regexp '^alias\.*')"}#alias.}) + list=(${(0)"$(git help --aliases-for-completion)"}) list=(${(f)"$(printf "%s:alias for '%s'\n" ${(f@)list})"}) _describe -t alias-commands 'aliases' list && _ret=0 } diff --git a/diff.c b/diff.c index 35b903a9a0966a..7fcaa1a0359273 100644 --- a/diff.c +++ b/diff.c @@ -1837,6 +1837,7 @@ static void emit_rewrite_diff(const char *name_a, const char *a_prefix, *b_prefix; char *data_one, *data_two; size_t size_one, size_two; + unsigned ws_rule; struct emit_callback ecbdata; struct strbuf out = STRBUF_INIT; @@ -1859,9 +1860,15 @@ static void emit_rewrite_diff(const char *name_a, size_one = fill_textconv(o->repo, textconv_one, one, &data_one); size_two = fill_textconv(o->repo, textconv_two, two, &data_two); + ws_rule = whitespace_rule(o->repo->index, name_b); + + /* symlink being an incomplete line is not a news */ + if (DIFF_FILE_VALID(two) && S_ISLNK(two->mode)) + ws_rule &= ~WS_INCOMPLETE_LINE; + memset(&ecbdata, 0, sizeof(ecbdata)); ecbdata.color_diff = o->use_color; - ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b); + ecbdata.ws_rule = ws_rule; ecbdata.opt = o; if (ecbdata.ws_rule & WS_BLANK_AT_EOF) { mmfile_t mf1, mf2; @@ -3759,6 +3766,7 @@ static void builtin_diff(const char *name_a, xpparam_t xpp; xdemitconf_t xecfg; struct emit_callback ecbdata; + unsigned ws_rule; const struct userdiff_funcname *pe; if (must_show_header) { @@ -3770,6 +3778,12 @@ static void builtin_diff(const char *name_a, mf1.size = fill_textconv(o->repo, textconv_one, one, &mf1.ptr); mf2.size = fill_textconv(o->repo, textconv_two, two, &mf2.ptr); + ws_rule = whitespace_rule(o->repo->index, name_b); + + /* symlink being an incomplete line is not a news */ + if (DIFF_FILE_VALID(two) && S_ISLNK(two->mode)) + ws_rule &= ~WS_INCOMPLETE_LINE; + pe = diff_funcname_pattern(o, one); if (!pe) pe = diff_funcname_pattern(o, two); @@ -3781,7 +3795,7 @@ static void builtin_diff(const char *name_a, lbl[0] = NULL; ecbdata.label_path = lbl; ecbdata.color_diff = o->use_color; - ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b); + ecbdata.ws_rule = ws_rule; if (ecbdata.ws_rule & WS_BLANK_AT_EOF) check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.opt = o; @@ -3988,6 +4002,10 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, data.ws_rule = whitespace_rule(o->repo->index, attr_path); data.conflict_marker_size = ll_merge_marker_size(o->repo->index, attr_path); + /* symlink being an incomplete line is not a news */ + if (DIFF_FILE_VALID(two) && S_ISLNK(two->mode)) + data.ws_rule &= ~WS_INCOMPLETE_LINE; + if (fill_mmfile(o->repo, &mf1, one) < 0 || fill_mmfile(o->repo, &mf2, two) < 0) die("unable to read files to diff"); diff --git a/help.c b/help.c index 08b5c602046ba2..95f576c5c81d9f 100644 --- a/help.c +++ b/help.c @@ -20,6 +20,8 @@ #include "prompt.h" #include "fsmonitor-ipc.h" #include "repository.h" +#include "alias.h" +#include "utf8.h" #ifndef NO_CURL #include "git-curl-compat.h" /* For LIBCURL_VERSION only */ @@ -107,7 +109,7 @@ static void print_command_list(const struct cmdname_help *cmds, for (i = 0; cmds[i].name; i++) { if (cmds[i].category & mask) { - size_t len = strlen(cmds[i].name); + size_t len = utf8_strwidth(cmds[i].name); printf(" %s ", cmds[i].name); if (longest > len) mput_char(' ', longest - len); @@ -468,20 +470,6 @@ void list_developer_interfaces_help(void) putchar('\n'); } -static int get_alias(const char *var, const char *value, - const struct config_context *ctx UNUSED, void *data) -{ - struct string_list *list = data; - - if (skip_prefix(var, "alias.", &var)) { - if (!value) - return config_error_nonbool(var); - string_list_append(list, var)->util = xstrdup(value); - } - - return 0; -} - static void list_all_cmds_help_external_commands(void) { struct string_list others = STRING_LIST_INIT_DUP; @@ -501,11 +489,11 @@ static void list_all_cmds_help_aliases(int longest) struct cmdname_help *aliases; int i; - repo_config(the_repository, get_alias, &alias_list); + list_aliases(&alias_list); string_list_sort(&alias_list); for (i = 0; i < alias_list.nr; i++) { - size_t len = strlen(alias_list.items[i].string); + size_t len = utf8_strwidth(alias_list.items[i].string); if (longest < len) longest = len; } @@ -586,7 +574,8 @@ static int git_unknown_cmd_config(const char *var, const char *value, void *cb) { struct help_unknown_cmd_config *cfg = cb; - const char *p; + const char *subsection, *key; + size_t subsection_len; if (!strcmp(var, "help.autocorrect")) { int v = parse_autocorrect(value); @@ -601,8 +590,18 @@ static int git_unknown_cmd_config(const char *var, const char *value, } /* Also use aliases for command lookup */ - if (skip_prefix(var, "alias.", &p)) - add_cmdname(&cfg->aliases, p, strlen(p)); + if (!parse_config_key(var, "alias", &subsection, &subsection_len, + &key)) { + if (subsection) { + /* [alias "name"] command = value */ + if (!strcmp(key, "command")) + add_cmdname(&cfg->aliases, subsection, + subsection_len); + } else { + /* alias.name = value */ + add_cmdname(&cfg->aliases, key, strlen(key)); + } + } return 0; } diff --git a/pack-bitmap.c b/pack-bitmap.c index 972203f12b6d9b..1c93871484a24f 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -3314,7 +3314,7 @@ int bitmap_is_midx(struct bitmap_index *bitmap_git) return !!bitmap_git->midx; } -const struct string_list *bitmap_preferred_tips(struct repository *r) +static const struct string_list *bitmap_preferred_tips(struct repository *r) { const struct string_list *dest; @@ -3323,6 +3323,33 @@ const struct string_list *bitmap_preferred_tips(struct repository *r) return NULL; } +void for_each_preferred_bitmap_tip(struct repository *repo, + each_ref_fn cb, void *cb_data) +{ + struct string_list_item *item; + const struct string_list *preferred_tips; + struct strbuf buf = STRBUF_INIT; + + preferred_tips = bitmap_preferred_tips(repo); + if (!preferred_tips) + return; + + for_each_string_list_item(item, preferred_tips) { + const char *pattern = item->string; + + if (!ends_with(pattern, "/")) { + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/", pattern); + pattern = buf.buf; + } + + refs_for_each_ref_in(get_main_ref_store(repo), + pattern, cb, cb_data); + } + + strbuf_release(&buf); +} + int bitmap_is_preferred_refname(struct repository *r, const char *refname) { const struct string_list *preferred_tips = bitmap_preferred_tips(r); diff --git a/pack-bitmap.h b/pack-bitmap.h index 1bd7a791e2a0d0..d0611d04813010 100644 --- a/pack-bitmap.h +++ b/pack-bitmap.h @@ -5,6 +5,7 @@ #include "khash.h" #include "pack.h" #include "pack-objects.h" +#include "refs.h" #include "string-list.h" struct commit; @@ -99,6 +100,13 @@ int for_each_bitmapped_object(struct bitmap_index *bitmap_git, show_reachable_fn show_reach, void *payload); +/* + * Iterate over all references that are configured as preferred bitmap tips via + * "pack.preferBitmapTips" and invoke the callback on each function. + */ +void for_each_preferred_bitmap_tip(struct repository *repo, + each_ref_fn cb, void *cb_data); + #define GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL \ "GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL" @@ -182,7 +190,6 @@ char *pack_bitmap_filename(struct packed_git *p); int bitmap_is_midx(struct bitmap_index *bitmap_git); -const struct string_list *bitmap_preferred_tips(struct repository *r); int bitmap_is_preferred_refname(struct repository *r, const char *refname); int verify_bitmap_files(struct repository *r); diff --git a/ref-filter.c b/ref-filter.c index 3917c4ccd9f73a..6bbb6fac18a333 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -2173,32 +2173,34 @@ static inline char *copy_advance(char *dst, const char *src) return dst; } -static const char *lstrip_ref_components(const char *refname, int len) +static int normalize_component_count(const char *refname, int len) { - long remaining = len; - const char *start = xstrdup(refname); - const char *to_free = start; - if (len < 0) { - int i; - const char *p = refname; + int slashes = 0; + + for (const char *p = refname; *p; p++) { + if (*p == '/') + slashes++; + } - /* Find total no of '/' separated path-components */ - for (i = 0; p[i]; p[i] == '/' ? i++ : *p++) - ; /* * The number of components we need to strip is now * the total minus the components to be left (Plus one * because we count the number of '/', but the number * of components is one more than the no of '/'). */ - remaining = i + len + 1; + len = slashes + len + 1; } + return len; +} + +static const char *lstrip_ref_components(const char *refname, int len) +{ + int remaining = normalize_component_count(refname, len); while (remaining > 0) { - switch (*start++) { + switch (*refname++) { case '\0': - free((char *)to_free); return xstrdup(""); case '/': remaining--; @@ -2206,42 +2208,21 @@ static const char *lstrip_ref_components(const char *refname, int len) } } - start = xstrdup(start); - free((char *)to_free); - return start; + return xstrdup(refname); } static const char *rstrip_ref_components(const char *refname, int len) { - long remaining = len; - const char *start = xstrdup(refname); - const char *to_free = start; - - if (len < 0) { - int i; - const char *p = refname; + int remaining = normalize_component_count(refname, len); + const char *end = refname + strlen(refname); - /* Find total no of '/' separated path-components */ - for (i = 0; p[i]; p[i] == '/' ? i++ : *p++) - ; - /* - * The number of components we need to strip is now - * the total minus the components to be left (Plus one - * because we count the number of '/', but the number - * of components is one more than the no of '/'). - */ - remaining = i + len + 1; - } - - while (remaining-- > 0) { - char *p = strrchr(start, '/'); - if (!p) { - free((char *)to_free); + while (remaining > 0) { + if (end == refname) return xstrdup(""); - } else - p[0] = '\0'; + if (*--end == '/') + remaining--; } - return start; + return xmemdupz(refname, end - refname); } static const char *show_ref(struct refname_atom *atom, const char *refname) diff --git a/repack-midx.c b/repack-midx.c index 74bdfa3a6e913f..0682b80c4278d4 100644 --- a/repack-midx.c +++ b/repack-midx.c @@ -40,7 +40,6 @@ static int midx_snapshot_ref_one(const struct reference *ref, void *_data) void midx_snapshot_refs(struct repository *repo, struct tempfile *f) { struct midx_snapshot_ref_data data; - const struct string_list *preferred = bitmap_preferred_tips(repo); data.repo = repo; data.f = f; @@ -51,16 +50,9 @@ void midx_snapshot_refs(struct repository *repo, struct tempfile *f) die(_("could not open tempfile %s for writing"), get_tempfile_path(f)); - if (preferred) { - struct string_list_item *item; - - data.preferred = 1; - for_each_string_list_item(item, preferred) - refs_for_each_ref_in(get_main_ref_store(repo), - item->string, - midx_snapshot_ref_one, &data); - data.preferred = 0; - } + data.preferred = 1; + for_each_preferred_bitmap_tip(repo, midx_snapshot_ref_one, &data); + data.preferred = 0; refs_for_each_ref(get_main_ref_store(repo), midx_snapshot_ref_one, &data); diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index f5f33751da620d..874542ec3462a5 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -477,14 +477,20 @@ int cmd__path_utils(int argc, const char **argv) if (argc > 5 && !strcmp(argv[1], "slice-tests")) { int res = 0; - long offset, stride, i; + long slice, slices_total, i; struct string_list list = STRING_LIST_INIT_NODUP; struct stat st; - offset = strtol(argv[2], NULL, 10); - stride = strtol(argv[3], NULL, 10); - if (stride < 1) - stride = 1; + slices_total = strtol(argv[3], NULL, 10); + if (slices_total < 1) + die("there must be at least one slice, got '%s'", + argv[3]); + + slice = strtol(argv[2], NULL, 10); + if (1 > slice || slice > slices_total) + die("slice must be in the range 1 <= slice <= %ld, got '%s'", + slices_total, argv[2]); + for (i = 4; i < argc; i++) if (stat(argv[i], &st)) res = error_errno("Cannot stat '%s'", argv[i]); @@ -492,7 +498,7 @@ int cmd__path_utils(int argc, const char **argv) string_list_append(&list, argv[i])->util = (void *)(intptr_t)st.st_size; QSORT(list.items, list.nr, cmp_by_st_size); - for (i = offset; i < list.nr; i+= stride) + for (i = slice - 1; i < list.nr; i+= slices_total) printf("%s\n", list.items[i].string); return !!res; diff --git a/t/meson.build b/t/meson.build index e5174ee57595e0..6d91470ebc1cf7 100644 --- a/t/meson.build +++ b/t/meson.build @@ -1213,6 +1213,7 @@ endif test_environment = script_environment test_environment.set('GIT_BUILD_DIR', git_build_dir) +test_environment.set('MERGE_TOOLS_DIR', meson.project_source_root() / 'mergetools') foreach integration_test : integration_tests test(fs.stem(integration_test), shell, diff --git a/t/pack-refs-tests.sh b/t/pack-refs-tests.sh index 81086c369089b9..2fdaccb6c7f86a 100644 --- a/t/pack-refs-tests.sh +++ b/t/pack-refs-tests.sh @@ -354,8 +354,8 @@ do # Create 14 additional references, which brings us to # 15 together with the default branch. - printf "create refs/heads/loose-%d HEAD\n" $(test_seq 14) >stdin && - git update-ref --stdin stdin && - git update-ref --stdin stdin && - git update-ref --stdin stdin && - git update-ref --stdin stdin && - git update-ref --stdin >.git/config <<-\EOF && + [alias] + noval + EOF + test_must_fail git noval 2>error && + test_grep "alias.noval" error +' + +test_expect_success 'subsection syntax works' ' + test_config alias.testnew.command "!echo ran-subsection" && + git testnew >output && + test_grep "ran-subsection" output +' + +test_expect_success 'subsection syntax only accepts command key' ' + test_config alias.invalid.notcommand value && + test_must_fail git invalid 2>error && + test_grep -i "not a git command" error +' + +test_expect_success 'subsection syntax requires value for command' ' + test_when_finished "git config --remove-section alias.noval" && + cat >>.git/config <<-\EOF && + [alias "noval"] + command + EOF + test_must_fail git noval 2>error && + test_grep "alias.noval.command" error +' + +test_expect_success 'simple syntax is case-insensitive' ' + test_config alias.LegacyCase "!echo ran-legacy" && + git legacycase >output && + test_grep "ran-legacy" output +' + +test_expect_success 'subsection syntax is case-sensitive' ' + test_config alias.SubCase.command "!echo ran-upper" && + test_config alias.subcase.command "!echo ran-lower" && + git SubCase >upper.out && + git subcase >lower.out && + test_grep "ran-upper" upper.out && + test_grep "ran-lower" lower.out +' + +test_expect_success 'UTF-8 alias with Swedish characters' ' + test_config alias."förgrena".command "!echo ran-swedish" && + git förgrena >output && + test_grep "ran-swedish" output +' + +test_expect_success 'UTF-8 alias with CJK characters' ' + test_config alias."分支".command "!echo ran-cjk" && + git 分支 >output && + test_grep "ran-cjk" output +' + +test_expect_success 'alias with spaces in name' ' + test_config alias."test name".command "!echo ran-spaces" && + git "test name" >output && + test_grep "ran-spaces" output +' + +test_expect_success 'subsection aliases listed in help -a' ' + test_config alias."förgrena".command "!echo test" && + git help -a >output && + test_grep "förgrena" output +' + test_done diff --git a/t/t0613-reftable-write-options.sh b/t/t0613-reftable-write-options.sh index e33475175949f5..26b716c75f219d 100755 --- a/t/t0613-reftable-write-options.sh +++ b/t/t0613-reftable-write-options.sh @@ -68,8 +68,8 @@ test_expect_success 'many refs results in multiple blocks' ' ( cd repo && test_commit initial && - test_seq -f "update refs/heads/branch-%d HEAD" 200 >input && - git update-ref --stdin expect <<-EOF && @@ -178,8 +178,8 @@ test_expect_success 'restart interval at every single record' ' ( cd repo && test_commit initial && - test_seq -f "update refs/heads/branch-%d HEAD" 10 >input && - git update-ref --stdin expect <<-EOF && @@ -218,8 +218,8 @@ test_expect_success 'object index gets written by default with ref index' ' ( cd repo && test_commit initial && - test_seq -f "update refs/heads/branch-%d HEAD" 5 >input && - git update-ref --stdin expect <<-EOF && @@ -253,8 +253,8 @@ test_expect_success 'object index can be disabled' ' ( cd repo && test_commit initial && - test_seq -f "update refs/heads/branch-%d HEAD" 5 >input && - git update-ref --stdin expect <<-EOF && diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index db6585b8d828a5..b2858a9061a23d 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -1380,16 +1380,16 @@ test_expect_success 'fails with duplicate ref update via symref' ' test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction creating branches does not burst open file limit' ' ( - test_seq -f "create refs/heads/%d HEAD" 33 >large_input && - run_with_limited_open_files git update-ref --stdin large_input && - run_with_limited_open_files git update-ref --stdin stdin && - git -C repo update-ref --stdin stdin && - git -C repo update-ref --stdin @@ -34,7 +25,7 @@ test_repo_info () { eval "$init_command $repo_name" ' - test_expect_success "keyvalue: $label" ' + test_expect_success "lines: $label" ' echo "$key=$expected_value" > expect && git -C "$repo_name" repo info "$key" >actual && test_cmp expect actual @@ -115,12 +106,12 @@ test_expect_success '-z uses nul-terminated format' ' test_expect_success 'git repo info uses the last requested format' ' echo "layout.bare=false" >expected && - git repo info --format=nul -z --format=keyvalue layout.bare >actual && + git repo info --format=nul -z --format=lines layout.bare >actual && test_cmp expected actual ' -test_expect_success 'git repo info --all returns all key-value pairs' ' - git repo info $REPO_INFO_KEYS >expect && +test_expect_success 'git repo info --all and git repo info $(git repo info --keys) output the same data' ' + git repo info $(git repo info --keys) >expect && git repo info --all >actual && test_cmp expect actual ' @@ -131,4 +122,31 @@ test_expect_success 'git repo info --all aborts' ' test_cmp expect actual ' +test_expect_success 'git repo info --keys --format=nul uses nul-terminated output' ' + git repo info --keys --format=lines >lines && + lf_to_nul expect && + git repo info --keys --format=nul >actual && + test_cmp expect actual +' + +test_expect_success 'git repo info --keys aborts when using --format other than lines or nul' ' + echo "fatal: --keys can only be used with --format=lines or --format=nul" >expect && + test_must_fail git repo info --keys --format=table 2>actual && + test_cmp expect actual +' + +test_expect_success 'git repo info --keys aborts when requesting keys' ' + echo "fatal: --keys cannot be used with a or --all" >expect && + test_must_fail git repo info --keys --all 2>actual_all && + test_must_fail git repo info --keys some.key 2>actual_key && + test_cmp expect actual_all && + test_cmp expect actual_key +' + +test_expect_success 'git repo info --keys uses lines as its default output format' ' + git repo info --keys --format=lines >expect && + git repo info --keys >actual && + test_cmp expect actual +' + test_done diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh index 17ff164b0596c9..a6f2591d9a61ff 100755 --- a/t/t1901-repo-structure.sh +++ b/t/t1901-repo-structure.sh @@ -113,7 +113,7 @@ test_expect_success SHA1 'repository with references and objects' ' ) ' -test_expect_success SHA1 'keyvalue and nul format' ' +test_expect_success SHA1 'lines and nul format' ' test_when_finished "rm -rf repo" && git init repo && ( @@ -140,7 +140,7 @@ test_expect_success SHA1 'keyvalue and nul format' ' objects.tags.disk_size=$(object_type_disk_usage tag) EOF - git repo structure --format=keyvalue >out 2>err && + git repo structure --format=lines >out 2>err && test_cmp expect out && test_line_count = 0 err && diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh index a397790df59d0b..c01f1cd617c72b 100755 --- a/t/t2027-checkout-track.sh +++ b/t/t2027-checkout-track.sh @@ -47,4 +47,22 @@ test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' ' test_cmp_config refs/heads/main branch.b4.merge ' +test_expect_success 'ambiguous tracking info' ' + # Set up a few remote repositories + git init --bare --initial-branch=trunk src1 && + git init --bare --initial-branch=trunk src2 && + git push src1 one:refs/heads/trunk && + git push src2 two:refs/heads/trunk && + + git remote add -f src1 "file://$PWD/src1" && + git remote add -f src2 "file://$PWD/src2" && + + # DWIM + test_must_fail git checkout trunk 2>hint.checkout && + test_grep "hint: *git checkout --track" hint.checkout && + + test_must_fail git switch trunk 2>hint.switch && + test_grep "hint: *git switch --track" hint.switch +' + test_done diff --git a/t/t3451-history-reword.sh b/t/t3451-history-reword.sh index 3594421b681c40..de7b357685db4a 100755 --- a/t/t3451-history-reword.sh +++ b/t/t3451-history-reword.sh @@ -203,7 +203,7 @@ test_expect_success 'can reword a merge commit' ' # It is not possible to replay merge commits embedded in the # history (yet). - test_must_fail git history reword HEAD~ 2>err && + test_must_fail git -c core.editor=false history reword HEAD~ 2>err && test_grep "replaying merge commits is not supported yet" err && # But it is possible to reword a merge commit directly. @@ -221,7 +221,7 @@ test_expect_success 'can reword a merge commit' ' ) ' -test_expect_success '--ref-action=print prints ref updates without modifying repo' ' +test_expect_success '--dry-run prints ref updates without modifying repo' ' test_when_finished "rm -rf repo" && git init repo --initial-branch=main && ( @@ -233,7 +233,15 @@ test_expect_success '--ref-action=print prints ref updates without modifying rep test_commit theirs && git refs list >refs-expect && - reword_with_message --ref-action=print base >updates <<-\EOF && + reword_with_message --dry-run --update-refs=head base >updates <<-\EOF && + reworded commit + EOF + git refs list >refs-actual && + test_cmp refs-expect refs-actual && + test_grep "update refs/heads/branch" updates && + test_grep ! "update refs/heads/main" updates && + + reword_with_message --dry-run base >updates <<-\EOF && reworded commit EOF git refs list >refs-actual && @@ -250,7 +258,7 @@ test_expect_success '--ref-action=print prints ref updates without modifying rep ) ' -test_expect_success '--ref-action=head updates only HEAD' ' +test_expect_success '--update-refs=head updates only HEAD' ' test_when_finished "rm -rf repo" && git init repo --initial-branch=main && ( @@ -263,10 +271,10 @@ test_expect_success '--ref-action=head updates only HEAD' ' # When told to update HEAD, only, the command will refuse to # rewrite commits that are not an ancestor of HEAD. - test_must_fail git history reword --ref-action=head theirs 2>err && + test_must_fail git -c core.editor=false history reword --update-refs=head theirs 2>err && test_grep "rewritten commit must be an ancestor of HEAD" err && - reword_with_message --ref-action=head base >updates <<-\EOF && + reword_with_message --update-refs=head base >updates <<-\EOF && reworded base EOF expect_log HEAD <<-\EOF && diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 21d6d0cd9ef679..2135b65cee5d3b 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -1472,6 +1472,14 @@ test_expect_success '--from uses committer ident' ' test_cmp expect patch.head ' +test_expect_success '--from applies to cover letter' ' + test_when_finished "rm -rf patches" && + git format-patch -1 --cover-letter --from="Foo Bar " -o patches && + echo "From: Foo Bar " >expect && + grep "^From:" patches/0000-cover-letter.patch >patch.head && + test_cmp expect patch.head +' + test_expect_success '--from omits redundant in-body header' ' git format-patch -1 --stdout --from="A U Thor " >patch && cat >expect <<-\EOF && diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 3c8eb02e4f3e64..b691d2947943c8 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -90,6 +90,32 @@ test_expect_success "new incomplete line in post-image" ' git -c core.whitespace=incomplete diff -R --check x ' +test_expect_success SYMLINKS "incomplete-line error is disabled for symlinks" ' + test_when_finished "git reset --hard" && + test_when_finished "rm -f mylink" && + + # a regular file with an incomplete line + printf "%s" one >mylink && + git add mylink && + + # a symbolic link + rm mylink && + ln -s two mylink && + + git -c diff.color=always -c core.whitespace=incomplete \ + diff mylink >forward.raw && + test_decode_color >forward \\\\ No newline at end of file" forward && + + git -c diff.color=always -c core.whitespace=incomplete \ + diff -R mylink >reverse.raw && + test_decode_color >reverse \\\\ No newline at end of file" reverse && + + git -c core.whitespace=incomplete diff --check mylink && + test_must_fail git -c core.whitespace=incomplete diff --check -R mylink +' + test_expect_success "Ray Lehtiniemi's example" ' cat <<-\EOF >x && do { diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh index 4d4aa1650fe84a..4dd4954260cfc1 100755 --- a/t/t4041-diff-submodule-option.sh +++ b/t/t4041-diff-submodule-option.sh @@ -37,8 +37,12 @@ add_file () { test_tick && # "git commit -m" would break MinGW, as Windows refuse to pass # $test_encoding encoded parameter to git. - echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding | - git -c "i18n.commitEncoding=$test_encoding" commit -F - + message="Add $name ($added $name)" && + if test_have_prereq ICONV + then + message=$(echo "$message" | iconv -f utf-8 -t $test_encoding) + fi && + echo "$message" | git -c "i18n.commitEncoding=$test_encoding" commit -F - done >/dev/null && git rev-parse --short --verify HEAD ) diff --git a/t/t4059-diff-submodule-not-initialized.sh b/t/t4059-diff-submodule-not-initialized.sh index 0fe81056d5be68..bb902ce94df6f6 100755 --- a/t/t4059-diff-submodule-not-initialized.sh +++ b/t/t4059-diff-submodule-not-initialized.sh @@ -35,8 +35,12 @@ add_file () { test_tick && # "git commit -m" would break MinGW, as Windows refuse to pass # $test_encoding encoded parameter to git. - echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding | - git -c "i18n.commitEncoding=$test_encoding" commit -F - + message="Add $name ($added $name)" && + if test_have_prereq ICONV + then + message=$(echo "$message" | iconv -f utf-8 -t $test_encoding) + fi && + echo "$message" | git -c "i18n.commitEncoding=$test_encoding" commit -F - done >/dev/null && git rev-parse --short --verify HEAD ) diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh index dbfeb7470bcfad..d8f92132550681 100755 --- a/t/t4060-diff-submodule-option-diff-format.sh +++ b/t/t4060-diff-submodule-option-diff-format.sh @@ -35,8 +35,12 @@ add_file () { test_tick && # "git commit -m" would break MinGW, as Windows refuse to pass # $test_encoding encoded parameter to git. - echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding | - git -c "i18n.commitEncoding=$test_encoding" commit -F - + message="Add $name ($added $name)" && + if test_have_prereq ICONV + then + message=$(echo "$message" | iconv -f utf-8 -t $test_encoding) + fi && + echo "$message" | git -c "i18n.commitEncoding=$test_encoding" commit -F - done >/dev/null && git rev-parse --short --verify HEAD ) diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 115a0f857906d4..29ea7d4268eca4 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -743,4 +743,90 @@ test_expect_success 'incomplete line modified at the end (error)' ' test_cmp sample target ' +test_expect_success "incomplete-line error is disabled for symlinks" ' + test_when_finished "git reset" && + test_when_finished "rm -f patch.txt" && + oneblob=$(printf "one" | git hash-object --stdin -w -t blob) && + twoblob=$(printf "two" | git hash-object --stdin -w -t blob) && + + oneshort=$(git rev-parse --short $oneblob) && + twoshort=$(git rev-parse --short $twoblob) && + + cat >patch0.txt <<-EOF && + diff --git a/mylink b/mylink + index $oneshort..$twoshort 120000 + --- a/mylink + +++ b/mylink + @@ -1 +1 @@ + -one + \ No newline at end of file + +two + \ No newline at end of file + EOF + + # the index has the preimage symlink + git update-index --add --cacheinfo "120000,$oneblob,mylink" && + + # check the patch going forward and reverse + git -c core.whitespace=incomplete apply --cached --check \ + --whitespace=error patch0.txt && + + git update-index --add --cacheinfo "120000,$twoblob,mylink" && + git -c core.whitespace=incomplete apply --cached --check \ + --whitespace=error -R patch0.txt && + + # the patch turns it into the postimage symlink + git update-index --add --cacheinfo "120000,$oneblob,mylink" && + git -c core.whitespace=incomplete apply --cached --whitespace=error \ + patch0.txt && + + # and then back. + git -c core.whitespace=incomplete apply --cached -R --whitespace=error \ + patch0.txt && + + # a text file turns into a symlink + cat >patch1.txt <<-EOF && + diff --git a/mylink b/mylink + deleted file mode 100644 + index $oneshort..0000000 + --- a/mylink + +++ /dev/null + @@ -1 +0,0 @@ + -one + \ No newline at end of file + diff --git a/mylink b/mylink + new file mode 120000 + index 0000000..$twoshort + --- /dev/null + +++ b/mylink + @@ -0,0 +1 @@ + +two + \ No newline at end of file + EOF + + # the index has the preimage text + git update-index --cacheinfo "100644,$oneblob,mylink" && + + # check + git -c core.whitespace=incomplete apply --cached \ + --check --whitespace=error patch1.txt && + + # reverse, leaving an incomplete text file, should error + git update-index --cacheinfo "120000,$twoblob,mylink" && + test_must_fail git -c core.whitespace=incomplete \ + apply --cached --check --whitespace=error -R patch1.txt && + + # apply to create a symbolic link + git update-index --cacheinfo "100644,$oneblob,mylink" && + git -c core.whitespace=incomplete apply --cached --whitespace=error \ + patch1.txt && + + # turning it back into an incomplete text file is an error + test_must_fail git -c core.whitespace=incomplete \ + apply --cached --whitespace=error -R patch1.txt + + + +' + test_done diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index 8f2ba98963feba..3865f6abc70caa 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -9,7 +9,12 @@ test_description='Test pretty formats' . ./test-lib.sh # Tested non-UTF-8 encoding -test_encoding="ISO8859-1" +if test_have_prereq ICONV +then + test_encoding="ISO8859-1" +else + test_encoding="UTF-8" +fi sample_utf8_part=$(printf "f\303\244ng") @@ -18,7 +23,7 @@ commit_msg () { # (translated with Google Translate), # encoded in UTF-8, used as a commit log message below. msg="initial. an${sample_utf8_part}lich\n" - if test -n "$1" + if test -n "$1" && test "$1" != "UTF-8" then printf "$msg" | iconv -f utf-8 -t "$1" else @@ -113,19 +118,19 @@ test_expect_success 'alias loop' ' test_must_fail git log --pretty=test-foo ' -test_expect_success ICONV 'NUL separation' ' +test_expect_success 'NUL separation' ' printf "add bar\0$(commit_msg)" >expected && git log -z --pretty="format:%s" >actual && test_cmp expected actual ' -test_expect_success ICONV 'NUL termination' ' +test_expect_success 'NUL termination' ' printf "add bar\0$(commit_msg)\0" >expected && git log -z --pretty="tformat:%s" >actual && test_cmp expected actual ' -test_expect_success ICONV 'NUL separation with --stat' ' +test_expect_success 'NUL separation with --stat' ' stat0_part=$(git diff --stat HEAD^ HEAD) && stat1_part=$(git diff-tree --no-commit-id --stat --root HEAD^) && printf "add bar\n$stat0_part\n\0$(commit_msg)\n$stat1_part\n" >expected && @@ -180,7 +185,7 @@ test_expect_success 'setup more commits' ' head4=$(git rev-parse --verify --short HEAD~3) ' -test_expect_success ICONV 'left alignment formatting' ' +test_expect_success 'left alignment formatting' ' git log --pretty="tformat:%<(40)%s" >actual && qz_to_tab_space <<-EOF >expected && message two Z @@ -202,7 +207,7 @@ test_expect_success ICONV 'left alignment formatting. i18n.logOutputEncoding' ' test_cmp expected actual ' -test_expect_success ICONV 'left alignment formatting at the nth column' ' +test_expect_success 'left alignment formatting at the nth column' ' git log --pretty="tformat:%h %<|(40)%s" >actual && qz_to_tab_space <<-EOF >expected && $head1 message two Z @@ -213,7 +218,7 @@ test_expect_success ICONV 'left alignment formatting at the nth column' ' test_cmp expected actual ' -test_expect_success ICONV 'left alignment formatting at the nth column' ' +test_expect_success 'left alignment formatting at the nth column' ' COLUMNS=50 git log --pretty="tformat:%h %<|(-10)%s" >actual && qz_to_tab_space <<-EOF >expected && $head1 message two Z @@ -235,7 +240,7 @@ test_expect_success ICONV 'left alignment formatting at the nth column. i18n.log test_cmp expected actual ' -test_expect_success ICONV 'left alignment formatting with no padding' ' +test_expect_success 'left alignment formatting with no padding' ' git log --pretty="tformat:%<(1)%s" >actual && cat <<-EOF >expected && message two @@ -246,7 +251,7 @@ test_expect_success ICONV 'left alignment formatting with no padding' ' test_cmp expected actual ' -test_expect_success 'left alignment formatting with no padding. i18n.logOutputEncoding' ' +test_expect_success ICONV 'left alignment formatting with no padding. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(1)%s" >actual && cat <<-EOF | iconv -f utf-8 -t $test_encoding >expected && message two @@ -257,7 +262,7 @@ test_expect_success 'left alignment formatting with no padding. i18n.logOutputEn test_cmp expected actual ' -test_expect_success ICONV 'left alignment formatting with trunc' ' +test_expect_success 'left alignment formatting with trunc' ' git log --pretty="tformat:%<(10,trunc)%s" >actual && qz_to_tab_space <<-\EOF >expected && message .. @@ -279,7 +284,7 @@ test_expect_success ICONV 'left alignment formatting with trunc. i18n.logOutputE test_cmp expected actual ' -test_expect_success ICONV 'left alignment formatting with ltrunc' ' +test_expect_success 'left alignment formatting with ltrunc' ' git log --pretty="tformat:%<(10,ltrunc)%s" >actual && qz_to_tab_space <<-EOF >expected && ..sage two @@ -301,7 +306,7 @@ test_expect_success ICONV 'left alignment formatting with ltrunc. i18n.logOutput test_cmp expected actual ' -test_expect_success ICONV 'left alignment formatting with mtrunc' ' +test_expect_success 'left alignment formatting with mtrunc' ' git log --pretty="tformat:%<(10,mtrunc)%s" >actual && qz_to_tab_space <<-\EOF >expected && mess.. two @@ -323,7 +328,7 @@ test_expect_success ICONV 'left alignment formatting with mtrunc. i18n.logOutput test_cmp expected actual ' -test_expect_success ICONV 'right alignment formatting' ' +test_expect_success 'right alignment formatting' ' git log --pretty="tformat:%>(40)%s" >actual && qz_to_tab_space <<-EOF >expected && Z message two @@ -345,7 +350,7 @@ test_expect_success ICONV 'right alignment formatting. i18n.logOutputEncoding' ' test_cmp expected actual ' -test_expect_success ICONV 'right alignment formatting at the nth column' ' +test_expect_success 'right alignment formatting at the nth column' ' git log --pretty="tformat:%h %>|(40)%s" >actual && qz_to_tab_space <<-EOF >expected && $head1 message two @@ -356,7 +361,7 @@ test_expect_success ICONV 'right alignment formatting at the nth column' ' test_cmp expected actual ' -test_expect_success ICONV 'right alignment formatting at the nth column' ' +test_expect_success 'right alignment formatting at the nth column' ' COLUMNS=50 git log --pretty="tformat:%h %>|(-10)%s" >actual && qz_to_tab_space <<-EOF >expected && $head1 message two @@ -391,7 +396,7 @@ test_expect_success ICONV 'right alignment formatting at the nth column with --g test_cmp expected actual ' -test_expect_success ICONV 'right alignment formatting with no padding' ' +test_expect_success 'right alignment formatting with no padding' ' git log --pretty="tformat:%>(1)%s" >actual && cat <<-EOF >expected && message two @@ -402,7 +407,7 @@ test_expect_success ICONV 'right alignment formatting with no padding' ' test_cmp expected actual ' -test_expect_success ICONV 'right alignment formatting with no padding and with --graph' ' +test_expect_success 'right alignment formatting with no padding and with --graph' ' git log --graph --pretty="tformat:%>(1)%s" >actual && cat <<-EOF >expected && * message two @@ -424,7 +429,7 @@ test_expect_success ICONV 'right alignment formatting with no padding. i18n.logO test_cmp expected actual ' -test_expect_success ICONV 'center alignment formatting' ' +test_expect_success 'center alignment formatting' ' git log --pretty="tformat:%><(40)%s" >actual && qz_to_tab_space <<-EOF >expected && Z message two Z @@ -445,7 +450,8 @@ test_expect_success ICONV 'center alignment formatting. i18n.logOutputEncoding' EOF test_cmp expected actual ' -test_expect_success ICONV 'center alignment formatting at the nth column' ' + +test_expect_success 'center alignment formatting at the nth column' ' git log --pretty="tformat:%h %><|(40)%s" >actual && qz_to_tab_space <<-EOF >expected && $head1 message two Z @@ -456,7 +462,7 @@ test_expect_success ICONV 'center alignment formatting at the nth column' ' test_cmp expected actual ' -test_expect_success ICONV 'center alignment formatting at the nth column' ' +test_expect_success 'center alignment formatting at the nth column' ' COLUMNS=70 git log --pretty="tformat:%h %><|(-30)%s" >actual && qz_to_tab_space <<-EOF >expected && $head1 message two Z @@ -478,7 +484,7 @@ test_expect_success ICONV 'center alignment formatting at the nth column. i18n.l test_cmp expected actual ' -test_expect_success ICONV 'center alignment formatting with no padding' ' +test_expect_success 'center alignment formatting with no padding' ' git log --pretty="tformat:%><(1)%s" >actual && cat <<-EOF >expected && message two diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index 027dedd9760a18..df513a42694966 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh @@ -176,8 +176,7 @@ test_expect_success EXPENSIVE,UNZIP,UNZIP_ZIP64_SUPPORT \ blob=$(echo $s | git hash-object -w --stdin) && # create tree containing 65500 entries of that blob - test_seq -f "100644 blob $blob\t%d" 1 65500 >tree && - tree=$(git mktree refs && + git update-ref --stdin commits-with-bitmap && + + # Verify that we have at least one commit that did not + # receive a bitmap. + git rev-list HEAD >commits.raw && + sort commits && + comm -13 commits-with-bitmap commits >commits-wo-bitmap && + test_file_not_empty commits-wo-bitmap && + commit_id=$(head commits-wo-bitmap) && + ref_without_bitmap=$(git for-each-ref --points-at="$commit_id" --format="%(refname)") && + + # When passing the full refname we do not expect a + # bitmap to be generated, as it should be interpreted + # as if a slash was appended to the pattern. + git -c pack.preferBitmapTips="$ref_without_bitmap" repack -adb && + test-tool bitmap list-commits >after && + test_grep ! "$commit_id" after && + + # But if we pass the parent directory of the ref we + # should see a bitmap. + ref_namespace=$(dirname "$ref_without_bitmap") && + git -c pack.preferBitmapTips="$ref_namespace" repack -adb && + test-tool bitmap list-commits >after && + test_grep "$commit_id" after + ) + ' + test_expect_success 'complains about multiple pack bitmaps' ' rm -fr repo && git init repo && diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index faae98c7e76a20..449353416ffce4 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -1345,4 +1345,47 @@ test_expect_success 'bitmapped packs are stored via the BTMP chunk' ' ) ' +test_expect_success 'pack.preferBitmapTips interprets patterns as hierarchy' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + # Create enough commits that not all will receive bitmap + # coverage even if they are all at the tip of some reference. + test_commit_bulk --message="%s" 103 && + git log --format="create refs/tags/%s %H" HEAD >refs && + git update-ref --stdin commits-with-bitmap && + + # Verify that we have at least one commit that did not + # receive a bitmap. + git rev-list HEAD >commits.raw && + sort commits && + comm -13 commits-with-bitmap commits >commits-wo-bitmap && + test_file_not_empty commits-wo-bitmap && + commit_id=$(head commits-wo-bitmap) && + ref_without_bitmap=$(git for-each-ref --points-at="$commit_id" --format="%(refname)") && + + # When passing the full refname we do not expect a bitmap to be + # generated, as it should be interpreted as if a slash was + # appended to the pattern. + rm .git/objects/pack/multi-pack-index* && + git -c pack.preferBitmapTips="$ref_without_bitmap" repack -adb --write-midx && + test-tool bitmap list-commits >after && + test_grep ! "$commit_id" after && + + # But if we pass the parent directory of the ref we should see + # a bitmap. + ref_namespace=$(dirname "$ref_without_bitmap") && + rm .git/objects/pack/multi-pack-index* && + git -c pack.preferBitmapTips="$ref_namespace" repack -adb --write-midx && + test-tool bitmap list-commits >after && + test_grep "$commit_id" after + ) +' + test_done diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh index 17a46fd3bae14f..44ec875aef88ae 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -134,8 +134,8 @@ test_expect_success 'pre-receive hook that forgets to read its input' ' EOF rm -f victim.git/hooks/update victim.git/hooks/post-update && - printf "create refs/heads/branch_%d main\n" $(test_seq 100 999) >input && - git update-ref --stdin stderr && - grep "this is the error message" stderr + test_grep "this is the error message" stderr ' -test_expect_success 'git client does not show html errors' ' +test_expect_success ICONV 'git client does not show html errors' ' test_must_fail git clone "$HTTPD_URL/error/html" 2>stderr && - ! grep "this is the error message" stderr + test_grep ! "this is the error message" stderr ' -test_expect_success 'git client shows text/plain with a charset' ' +test_expect_success ICONV 'git client shows text/plain with a charset' ' test_must_fail git clone "$HTTPD_URL/error/charset" 2>stderr && - grep "this is the error message" stderr + test_grep "this is the error message" stderr ' test_expect_success ICONV 'http error messages are reencoded' ' test_must_fail git clone "$HTTPD_URL/error/utf16" 2>stderr && - grep "this is the error message" stderr + test_grep "this is the error message" stderr ' test_expect_success ICONV 'reencoding is robust to whitespace oddities' ' test_must_fail git clone "$HTTPD_URL/error/odd-spacing" 2>stderr && - grep "this is the error message" stderr + test_grep "this is the error message" stderr ' check_language () { @@ -406,7 +406,7 @@ ja;q=0.95, zh;q=0.94, sv;q=0.93, pt;q=0.92, nb;q=0.91, *;q=0.90" \ test_expect_success 'git client send an empty Accept-Language' ' GIT_TRACE_CURL=true LANGUAGE= git ls-remote "$HTTPD_URL/dumb/repo.git" 2>stderr && - ! grep "^=> Send header: Accept-Language:" stderr + test_grep ! "^=> Send header: Accept-Language:" stderr ' test_expect_success 'remote-http complains cleanly about malformed urls' ' diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index eb93d68d7dc319..581984467dfd3f 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -378,15 +378,23 @@ test_expect_success 'rev-list %C(auto,...) respects --color' ' test_cmp expect actual ' -iconv -f utf-8 -t $test_encoding > commit-msg <commit-msg + else + echo "$message" >commit-msg + fi && -test_expect_success 'setup complex body' ' git config i18n.commitencoding $test_encoding && echo change2 >foo && git commit -a -F commit-msg && head3=$(git rev-parse --verify HEAD) && @@ -448,7 +456,12 @@ test_expect_success 'setup expected messages (for test %b)' ' commit $head2 commit $head1 EOF - iconv -f utf-8 -t $test_encoding expected.utf-8 >expected.ISO8859-1 + if test_have_prereq ICONV + then + iconv -f utf-8 -t $test_encoding expected.utf-8 >expected.ISO8859-1 + else + cp expected.utf-8 expected.ISO8859-1 + fi ' test_format complex-body %b