From 7725b8100ffbbff2750ee4d61a0fcc1f53a086e8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 30 Oct 2024 13:26:10 +0100 Subject: credential: sanitize the user prompt When asking the user interactively for credentials, we want to avoid misleading them e.g. via control sequences that pretend that the URL targets a trusted host when it does not. While Git learned, over the course of the preceding commits, to disallow URLs containing URL-encoded control characters by default, credential helpers are still allowed to specify values very freely (apart from Line Feed and NUL characters, anything is allowed), and this would allow, say, a username containing control characters to be specified that would then be displayed in the interactive terminal prompt asking the user for the password, potentially sending those control characters directly to the terminal. This is undesirable because control characters can be used to mislead users to divulge secret information to untrusted sites. To prevent such an attack vector, let's add a `git_prompt()` that forces the displayed text to be sanitized, i.e. displaying question marks instead of control characters. Note: While this commit's diff changes a lot of `user@host` strings to `user%40host`, which may look suspicious on the surface, there is a good reason for that: this string specifies a user name, not a @ combination! In the context of t5541, the actual combination looks like this: `user%40@127.0.0.1:5541`. Therefore, these string replacements document a net improvement introduced by this commit, as `user@host@127.0.0.1` could have left readers wondering where the user name ends and where the host name begins. Hinted-at-by: Jeff King Signed-off-by: Johannes Schindelin --- Documentation/config/credential.txt | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'Documentation/config') diff --git a/Documentation/config/credential.txt b/Documentation/config/credential.txt index 512f31876e..fd8113d6d4 100644 --- a/Documentation/config/credential.txt +++ b/Documentation/config/credential.txt @@ -14,6 +14,12 @@ credential.useHttpPath:: or https URL to be important. Defaults to false. See linkgit:gitcredentials[7] for more information. +credential.sanitizePrompt:: + By default, user names and hosts that are shown as part of the + password prompt are not allowed to contain control characters (they + will be URL-encoded by default). Configure this setting to `false` to + override that behavior. + credential.username:: If no username is set for a network authentication, use this username by default. See credential..* below, and -- cgit v1.3 From b01b9b81d36759cdcd07305e78765199e1bc2060 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 4 Nov 2024 14:48:22 +0100 Subject: credential: disallow Carriage Returns in the protocol by default While Git has documented that the credential protocol is line-based, with newlines as terminators, the exact shape of a newline has not been documented. From Git's perspective, which is firmly rooted in the Linux ecosystem, it is clear that "a newline" means a Line Feed character. However, even Git's credential protocol respects Windows line endings (a Carriage Return character followed by a Line Feed character, "CR/LF") by virtue of using `strbuf_getline()`. There is a third category of line endings that has been used originally by MacOS, and that is respected by the default line readers of .NET and node.js: bare Carriage Returns. Git cannot handle those, and what is worse: Git's remedy against CVE-2020-5260 does not catch when credential helpers are used that interpret bare Carriage Returns as newlines. Git Credential Manager addressed this as CVE-2024-50338, but other credential helpers may still be vulnerable. So let's not only disallow Line Feed characters as part of the values in the credential protocol, but also disallow Carriage Return characters. In the unlikely event that a credential helper relies on Carriage Returns in the protocol, introduce an escape hatch via the `credential.protectProtocol` config setting. This addresses CVE-2024-52006. Signed-off-by: Johannes Schindelin --- Documentation/config/credential.txt | 5 +++++ credential.c | 21 ++++++++++++++------- credential.h | 4 +++- t/t0300-credentials.sh | 16 ++++++++++++++++ 4 files changed, 38 insertions(+), 8 deletions(-) (limited to 'Documentation/config') diff --git a/Documentation/config/credential.txt b/Documentation/config/credential.txt index fd8113d6d4..9cadca7f73 100644 --- a/Documentation/config/credential.txt +++ b/Documentation/config/credential.txt @@ -20,6 +20,11 @@ credential.sanitizePrompt:: will be URL-encoded by default). Configure this setting to `false` to override that behavior. +credential.protectProtocol:: + By default, Carriage Return characters are not allowed in the protocol + that is used when Git talks to a credential helper. This setting allows + users to override this default. + credential.username:: If no username is set for a network authentication, use this username by default. See credential..* below, and diff --git a/credential.c b/credential.c index 1392a54d5c..b76a730901 100644 --- a/credential.c +++ b/credential.c @@ -69,6 +69,8 @@ static int credential_config_callback(const char *var, const char *value, c->use_http_path = git_config_bool(var, value); else if (!strcmp(key, "sanitizeprompt")) c->sanitize_prompt = git_config_bool(var, value); + else if (!strcmp(key, "protectprotocol")) + c->protect_protocol = git_config_bool(var, value); return 0; } @@ -262,7 +264,8 @@ int credential_read(struct credential *c, FILE *fp) return 0; } -static void credential_write_item(FILE *fp, const char *key, const char *value, +static void credential_write_item(const struct credential *c, + FILE *fp, const char *key, const char *value, int required) { if (!value && required) @@ -271,19 +274,23 @@ static void credential_write_item(FILE *fp, const char *key, const char *value, return; if (strchr(value, '\n')) die("credential value for %s contains newline", key); + if (c->protect_protocol && strchr(value, '\r')) + die("credential value for %s contains carriage return\n" + "If this is intended, set `credential.protectProtocol=false`", + key); fprintf(fp, "%s=%s\n", key, value); } void credential_write(const struct credential *c, FILE *fp) { - credential_write_item(fp, "protocol", c->protocol, 1); - credential_write_item(fp, "host", c->host, 1); - credential_write_item(fp, "path", c->path, 0); - credential_write_item(fp, "username", c->username, 0); - credential_write_item(fp, "password", c->password, 0); + credential_write_item(c, fp, "protocol", c->protocol, 1); + credential_write_item(c, fp, "host", c->host, 1); + credential_write_item(c, fp, "path", c->path, 0); + credential_write_item(c, fp, "username", c->username, 0); + credential_write_item(c, fp, "password", c->password, 0); if (c->password_expiry_utc != TIME_MAX) { char *s = xstrfmt("%"PRItime, c->password_expiry_utc); - credential_write_item(fp, "password_expiry_utc", s, 0); + credential_write_item(c, fp, "password_expiry_utc", s, 0); free(s); } } diff --git a/credential.h b/credential.h index 0364d436d2..2c0b39a925 100644 --- a/credential.h +++ b/credential.h @@ -120,7 +120,8 @@ struct credential { quit:1, use_http_path:1, username_from_proto:1, - sanitize_prompt:1; + sanitize_prompt:1, + protect_protocol:1; char *username; char *password; @@ -134,6 +135,7 @@ struct credential { .helpers = STRING_LIST_INIT_DUP, \ .password_expiry_utc = TIME_MAX, \ .sanitize_prompt = 1, \ + .protect_protocol = 1, \ } /* Initialize a credential structure, setting all fields to empty. */ diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index b62c70c193..168ae76550 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -720,6 +720,22 @@ test_expect_success 'url parser rejects embedded newlines' ' test_cmp expect stderr ' +test_expect_success 'url parser rejects embedded carriage returns' ' + test_config credential.helper "!true" && + test_must_fail git credential fill 2>stderr <<-\EOF && + url=https://example%0d.com/ + EOF + cat >expect <<-\EOF && + fatal: credential value for host contains carriage return + If this is intended, set `credential.protectProtocol=false` + EOF + test_cmp expect stderr && + GIT_ASKPASS=true \ + git -c credential.protectProtocol=false credential fill <<-\EOF + url=https://example%0d.com/ + EOF +' + test_expect_success 'host-less URLs are parsed as empty host' ' check fill "verbatim foo bar" <<-\EOF url=cert:///path/to/cert.pem -- cgit v1.3 From 1860ba1a2a96c587fca1c294d8288accf9554096 Mon Sep 17 00:00:00 2001 From: Caleb White Date: Fri, 29 Nov 2024 22:22:34 +0000 Subject: worktree: add `relativeWorktrees` extension A new extension, `relativeWorktrees`, is added to indicate that at least one worktree in the repository has been linked with relative paths. This ensures older Git versions do not attempt to automatically prune worktrees with relative paths, as they would not not recognize the paths as being valid. Suggested-by: Junio C Hamano Signed-off-by: Caleb White Signed-off-by: Junio C Hamano --- Documentation/config/extensions.txt | 6 ++++++ repository.c | 1 + repository.h | 1 + setup.c | 7 +++++++ setup.h | 1 + 5 files changed, 16 insertions(+) (limited to 'Documentation/config') diff --git a/Documentation/config/extensions.txt b/Documentation/config/extensions.txt index 5dc569d1c9..5cb4721a0e 100644 --- a/Documentation/config/extensions.txt +++ b/Documentation/config/extensions.txt @@ -63,6 +63,12 @@ Note that this setting should only be set by linkgit:git-init[1] or linkgit:git-clone[1]. Trying to change it after initialization will not work and will produce hard-to-diagnose issues. +relativeWorktrees:: + If enabled, indicates at least one worktree has been linked with + relative paths. Automatically set if a worktree has been created or + repaired with either the `--relative-paths` option or with the + `worktree.useRelativePaths` config set to `true`. + worktreeConfig:: If enabled, then worktrees will load config settings from the `$GIT_DIR/config.worktree` file in addition to the diff --git a/repository.c b/repository.c index f988b8ae68..1a6a62bbd0 100644 --- a/repository.c +++ b/repository.c @@ -283,6 +283,7 @@ int repo_init(struct repository *repo, repo_set_compat_hash_algo(repo, format.compat_hash_algo); repo_set_ref_storage_format(repo, format.ref_storage_format); repo->repository_format_worktree_config = format.worktree_config; + repo->repository_format_relative_worktrees = format.relative_worktrees; /* take ownership of format.partial_clone */ repo->repository_format_partial_clone = format.partial_clone; diff --git a/repository.h b/repository.h index 24a66a496a..c4c92b2ab9 100644 --- a/repository.h +++ b/repository.h @@ -150,6 +150,7 @@ struct repository { /* Configurations */ int repository_format_worktree_config; + int repository_format_relative_worktrees; /* Indicate if a repository has a different 'commondir' from 'gitdir' */ unsigned different_commondir:1; diff --git a/setup.c b/setup.c index 1e5c2eacb1..39ff48d9dc 100644 --- a/setup.c +++ b/setup.c @@ -683,6 +683,9 @@ static enum extension_result handle_extension(const char *var, "extensions.refstorage", value); data->ref_storage_format = format; return EXTENSION_OK; + } else if (!strcmp(ext, "relativeworktrees")) { + data->relative_worktrees = git_config_bool(var, value); + return EXTENSION_OK; } return EXTENSION_UNKNOWN; } @@ -1854,6 +1857,8 @@ const char *setup_git_directory_gently(int *nongit_ok) repo_fmt.ref_storage_format); the_repository->repository_format_worktree_config = repo_fmt.worktree_config; + the_repository->repository_format_relative_worktrees = + repo_fmt.relative_worktrees; /* take ownership of repo_fmt.partial_clone */ the_repository->repository_format_partial_clone = repo_fmt.partial_clone; @@ -1950,6 +1955,8 @@ void check_repository_format(struct repository_format *fmt) fmt->ref_storage_format); the_repository->repository_format_worktree_config = fmt->worktree_config; + the_repository->repository_format_relative_worktrees = + fmt->relative_worktrees; the_repository->repository_format_partial_clone = xstrdup_or_null(fmt->partial_clone); clear_repository_format(&repo_fmt); diff --git a/setup.h b/setup.h index e496ab3e4d..18dc3b7368 100644 --- a/setup.h +++ b/setup.h @@ -129,6 +129,7 @@ struct repository_format { int precious_objects; char *partial_clone; /* value of extensions.partialclone */ int worktree_config; + int relative_worktrees; int is_bare; int hash_algo; int compat_hash_algo; -- cgit v1.3 From b7016344f1d4ef8457821e0436f335ec388b8dac Mon Sep 17 00:00:00 2001 From: Caleb White Date: Fri, 29 Nov 2024 22:22:55 +0000 Subject: worktree: add relative cli/config options to `add` command This introduces the `--[no-]relative-paths` CLI option and `worktree.useRelativePaths` configuration setting to the `worktree add` command. When enabled these options allow worktrees to be linked using relative paths, enhancing portability across environments where absolute paths may differ (e.g., containerized setups, shared network drives). Git still creates absolute paths by default, but these options allow users to opt-in to relative paths if desired. The t2408 test file is removed and more comprehensive tests are written for the various worktree operations in their own files. Signed-off-by: Caleb White Signed-off-by: Junio C Hamano --- Documentation/config/worktree.txt | 10 +++++++++ Documentation/git-worktree.txt | 5 +++++ builtin/worktree.c | 19 ++++++++-------- t/t2400-worktree-add.sh | 46 +++++++++++++++++++++++++++++++++++++++ t/t2401-worktree-prune.sh | 3 ++- t/t2402-worktree-list.sh | 22 +++++++++++++++++++ t/t2408-worktree-relative.sh | 39 --------------------------------- 7 files changed, 95 insertions(+), 49 deletions(-) delete mode 100755 t/t2408-worktree-relative.sh (limited to 'Documentation/config') diff --git a/Documentation/config/worktree.txt b/Documentation/config/worktree.txt index 048e349482..5e35c7d018 100644 --- a/Documentation/config/worktree.txt +++ b/Documentation/config/worktree.txt @@ -7,3 +7,13 @@ worktree.guessRemote:: such a branch exists, it is checked out and set as "upstream" for the new branch. If no such match can be found, it falls back to creating a new branch from the current HEAD. + +worktree.useRelativePaths:: + Link worktrees using relative paths (when "true") or absolute + paths (when "false"). This is particularly useful for setups + where the repository and worktrees may be moved between + different locations or environments. Defaults to "false". ++ +Note that setting `worktree.useRelativePaths` to "true" implies enabling the +`extension.relativeWorktrees` config (see linkgit:git-config[1]), +thus making it incompatible with older versions of Git. diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 70437c815f..60a671bbc2 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -216,6 +216,11 @@ To remove a locked worktree, specify `--force` twice. This can also be set up as the default behaviour by using the `worktree.guessRemote` config option. +--[no-]relative-paths:: + Link worktrees using relative paths or absolute paths (default). + Overrides the `worktree.useRelativePaths` config option, see + linkgit:git-config[1]. + --[no-]track:: When creating a new branch, if `` is a branch, mark it as "upstream" from the new branch. This is the diff --git a/builtin/worktree.c b/builtin/worktree.c index dae63dedf4..e3b4a71ee0 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -120,12 +120,14 @@ struct add_opts { int quiet; int checkout; int orphan; + int relative_paths; const char *keep_locked; }; static int show_only; static int verbose; static int guess_remote; +static int use_relative_paths; static timestamp_t expire; static int git_worktree_config(const char *var, const char *value, @@ -134,6 +136,9 @@ static int git_worktree_config(const char *var, const char *value, if (!strcmp(var, "worktree.guessremote")) { guess_remote = git_config_bool(var, value); return 0; + } else if (!strcmp(var, "worktree.userelativepaths")) { + use_relative_paths = git_config_bool(var, value); + return 0; } return git_default_config(var, value, ctx, cb); @@ -414,8 +419,7 @@ static int add_worktree(const char *path, const char *refname, const struct add_opts *opts) { struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; - struct strbuf sb = STRBUF_INIT, sb_tmp = STRBUF_INIT; - struct strbuf sb_path_realpath = STRBUF_INIT, sb_repo_realpath = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; const char *name; struct strvec child_env = STRVEC_INIT; unsigned int counter = 0; @@ -491,10 +495,7 @@ static int add_worktree(const char *path, const char *refname, strbuf_reset(&sb); strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); - strbuf_realpath(&sb_path_realpath, path, 1); - strbuf_realpath(&sb_repo_realpath, sb_repo.buf, 1); - write_file(sb.buf, "%s/.git", relative_path(sb_path_realpath.buf, sb_repo_realpath.buf, &sb_tmp)); - write_file(sb_git.buf, "gitdir: %s", relative_path(sb_repo_realpath.buf, sb_path_realpath.buf, &sb_tmp)); + write_worktree_linking_files(sb_git, sb, opts->relative_paths); strbuf_reset(&sb); strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, "../.."); @@ -578,12 +579,9 @@ done: strvec_clear(&child_env); strbuf_release(&sb); - strbuf_release(&sb_tmp); strbuf_release(&symref); strbuf_release(&sb_repo); - strbuf_release(&sb_repo_realpath); strbuf_release(&sb_git); - strbuf_release(&sb_path_realpath); strbuf_release(&sb_name); free_worktree(wt); return ret; @@ -796,12 +794,15 @@ static int add(int ac, const char **av, const char *prefix) PARSE_OPT_NOARG | PARSE_OPT_OPTARG), OPT_BOOL(0, "guess-remote", &guess_remote, N_("try to match the new branch name with a remote-tracking branch")), + OPT_BOOL(0, "relative-paths", &opts.relative_paths, + N_("use relative paths for worktrees")), OPT_END() }; int ret; memset(&opts, 0, sizeof(opts)); opts.checkout = 1; + opts.relative_paths = use_relative_paths; ac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0); if (!!opts.detach + !!new_branch + !!new_branch_force > 1) die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach"); diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index cfc4aeb179..bc4f4e90d6 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -1207,4 +1207,50 @@ test_expect_success '"add" with initialized submodule, with submodule.recurse se git -C project-clone -c submodule.recurse worktree add ../project-5 ' +test_expect_success 'can create worktrees with relative paths' ' + test_when_finished "git worktree remove relative" && + test_config worktree.useRelativePaths false && + git worktree add --relative-paths ./relative && + echo "gitdir: ../.git/worktrees/relative" >expect && + test_cmp expect relative/.git && + echo "../../../relative/.git" >expect && + test_cmp expect .git/worktrees/relative/gitdir +' + +test_expect_success 'can create worktrees with absolute paths' ' + test_config worktree.useRelativePaths true && + git worktree add ./relative && + echo "gitdir: ../.git/worktrees/relative" >expect && + test_cmp expect relative/.git && + git worktree add --no-relative-paths ./absolute && + echo "gitdir: $(pwd)/.git/worktrees/absolute" >expect && + test_cmp expect absolute/.git && + echo "$(pwd)/absolute/.git" >expect && + test_cmp expect .git/worktrees/absolute/gitdir +' + +test_expect_success 'move repo without breaking relative internal links' ' + test_when_finished rm -rf repo moved && + git init repo && + ( + cd repo && + test_commit initial && + git worktree add --relative-paths wt1 && + cd .. && + mv repo moved && + cd moved/wt1 && + git worktree list >out 2>err && + test_must_be_empty err + ) +' + +test_expect_success 'relative worktree sets extension config' ' + test_when_finished "rm -rf repo" && + git init repo && + git -C repo commit --allow-empty -m base && + git -C repo worktree add --relative-paths ./foo && + test_cmp_config -C repo 1 core.repositoryformatversion && + test_cmp_config -C repo true extensions.relativeworktrees +' + test_done diff --git a/t/t2401-worktree-prune.sh b/t/t2401-worktree-prune.sh index 976d048e3e..5eb52b9abb 100755 --- a/t/t2401-worktree-prune.sh +++ b/t/t2401-worktree-prune.sh @@ -120,11 +120,12 @@ test_expect_success 'prune duplicate (main/linked)' ' ! test -d .git/worktrees/wt ' -test_expect_success 'not prune proper worktrees when run inside linked worktree' ' +test_expect_success 'not prune proper worktrees inside linked worktree with relative paths' ' test_when_finished rm -rf repo wt_ext && git init repo && ( cd repo && + git config worktree.useRelativePaths true && echo content >file && git add file && git commit -m msg && diff --git a/t/t2402-worktree-list.sh b/t/t2402-worktree-list.sh index 33ea9cb21b..780daa6cd6 100755 --- a/t/t2402-worktree-list.sh +++ b/t/t2402-worktree-list.sh @@ -261,6 +261,7 @@ test_expect_success 'broken main worktree still at the top' ' ' test_expect_success 'linked worktrees are sorted' ' + test_when_finished "rm -rf sorted" && mkdir sorted && git init sorted/main && ( @@ -280,6 +281,27 @@ test_expect_success 'linked worktrees are sorted' ' test_cmp expected sorted/main/actual ' +test_expect_success 'linked worktrees with relative paths are shown with absolute paths' ' + test_when_finished "rm -rf sorted" && + mkdir sorted && + git init sorted/main && + ( + cd sorted/main && + test_tick && + test_commit new && + git worktree add --relative-paths ../first && + git worktree add ../second && + git worktree list --porcelain >out && + grep ^worktree out >actual + ) && + cat >expected <<-EOF && + worktree $(pwd)/sorted/main + worktree $(pwd)/sorted/first + worktree $(pwd)/sorted/second + EOF + test_cmp expected sorted/main/actual +' + test_expect_success 'worktree path when called in .git directory' ' git worktree list >list1 && git -C .git worktree list >list2 && diff --git a/t/t2408-worktree-relative.sh b/t/t2408-worktree-relative.sh deleted file mode 100755 index a3136db7e2..0000000000 --- a/t/t2408-worktree-relative.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh - -test_description='test worktrees linked with relative paths' - -TEST_PASSES_SANITIZE_LEAK=true -. ./test-lib.sh - -test_expect_success 'links worktrees with relative paths' ' - test_when_finished rm -rf repo && - git init repo && - ( - cd repo && - test_commit initial && - git worktree add wt1 && - echo "../../../wt1/.git" >expected_gitdir && - cat .git/worktrees/wt1/gitdir >actual_gitdir && - echo "gitdir: ../.git/worktrees/wt1" >expected_git && - cat wt1/.git >actual_git && - test_cmp expected_gitdir actual_gitdir && - test_cmp expected_git actual_git - ) -' - -test_expect_success 'move repo without breaking relative internal links' ' - test_when_finished rm -rf repo moved && - git init repo && - ( - cd repo && - test_commit initial && - git worktree add wt1 && - cd .. && - mv repo moved && - cd moved/wt1 && - git status >out 2>err && - test_must_be_empty err - ) -' - -test_done -- cgit v1.3 From b7f7d16562c3c5af31d77254577e4afe9bf3e99a Mon Sep 17 00:00:00 2001 From: Bence Ferdinandy Date: Fri, 29 Nov 2024 00:06:46 +0100 Subject: fetch: add configuration for set_head behaviour In the current implementation, if refs/remotes/$remote/HEAD does not exist, running fetch will create it, but if it does exist it will not do anything, which is a somewhat safe and minimal approach. Unfortunately, for users who wish to NOT have refs/remotes/$remote/HEAD set for any reason (e.g. so that `git rev-parse origin` doesn't accidentally point them somewhere they do not want to), there is no way to remove this behaviour. On the other side of the spectrum, users may want fetch to automatically update HEAD or at least give them a warning if something changed on the remote. Introduce a new setting, remote.$remote.followRemoteHEAD with four options: - "never": do not ever do anything, not even create - "create": the current behaviour, now the default behaviour - "warn": print a message if remote and local HEAD is different - "always": silently update HEAD on every change Signed-off-by: Bence Ferdinandy Signed-off-by: Junio C Hamano --- Documentation/config/remote.txt | 11 +++++ builtin/fetch.c | 46 +++++++++++++++--- remote.c | 9 ++++ remote.h | 9 ++++ t/t5510-fetch.sh | 102 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 171 insertions(+), 6 deletions(-) (limited to 'Documentation/config') diff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt index 6d8b7d6c63..024f92befc 100644 --- a/Documentation/config/remote.txt +++ b/Documentation/config/remote.txt @@ -101,6 +101,17 @@ remote..serverOption:: The default set of server options used when fetching from this remote. These server options can be overridden by the `--server-option=` command line arguments. + +remote..followRemoteHEAD:: + How linkgit:git-fetch[1] should handle updates to `remotes//HEAD`. + The default value is "create", which will create `remotes//HEAD` + if it exists on the remote, but not locally, but will not touch an + already existing local reference. Setting to "warn" will print + a message if the remote has a different value, than the local one and + in case there is no local reference, it behaves like "create". Setting + to "always" will silently update it to the value on the remote. + Finally, setting it to "never" will never change or create the local + reference. + This is a multi-valued variable, and an empty value can be used in a higher priority configuration file (e.g. `.git/config` in a repository) to clear diff --git a/builtin/fetch.c b/builtin/fetch.c index 2f416cf867..88c5c5d781 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1579,10 +1579,35 @@ static const char *strip_refshead(const char *name){ return name; } -static int set_head(const struct ref *remote_refs) +static void report_set_head(const char *remote, const char *head_name, + struct strbuf *buf_prev, int updateres) { + struct strbuf buf_prefix = STRBUF_INIT; + const char *prev_head = NULL; + + strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote); + skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head); + + if (prev_head && strcmp(prev_head, head_name)) { + printf("'HEAD' at '%s' is '%s', but we have '%s' locally.\n", + remote, head_name, prev_head); + printf("Run 'git remote set-head %s %s' to follow the change.\n", + remote, head_name); + } + else if (updateres && buf_prev->len) { + printf("'HEAD' at '%s' is '%s', " + "but we have a detached HEAD pointing to '%s' locally.\n", + remote, head_name, buf_prev->buf); + printf("Run 'git remote set-head %s %s' to follow the change.\n", + remote, head_name); + } + strbuf_release(&buf_prefix); +} + +static int set_head(const struct ref *remote_refs, int follow_remote_head) { - int result = 0, is_bare; - struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT; + int result = 0, create_only, is_bare, was_detached; + struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT, + b_local_head = STRBUF_INIT; const char *remote = gtransport->remote->name; char *head_name = NULL; struct ref *ref, *matches; @@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs) string_list_append(&heads, strip_refshead(ref->name)); } + if (follow_remote_head == FOLLOW_REMOTE_NEVER) + goto cleanup; if (!heads.nr) result = 1; @@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs) if (!head_name) goto cleanup; is_bare = is_bare_repository(); + create_only = follow_remote_head == FOLLOW_REMOTE_ALWAYS ? 0 : !is_bare; if (is_bare) { strbuf_addstr(&b_head, "HEAD"); strbuf_addf(&b_remote_head, "refs/heads/%s", head_name); @@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs) result = 1; goto cleanup; } - if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf, - "fetch", NULL, !is_bare)) + was_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf, + "fetch", &b_local_head, create_only); + if (was_detached == -1) { result = 1; + goto cleanup; + } + if (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0) + report_set_head(remote, head_name, &b_local_head, was_detached); cleanup: free(head_name); @@ -1636,6 +1669,7 @@ cleanup: free_refs(matches); string_list_clear(&heads, 0); strbuf_release(&b_head); + strbuf_release(&b_local_head); strbuf_release(&b_remote_head); return result; } @@ -1855,7 +1889,7 @@ static int do_fetch(struct transport *transport, "you need to specify exactly one branch with the --set-upstream option")); } } - if (set_head(remote_refs)) + if (set_head(remote_refs, transport->remote->follow_remote_head)) ; /* * Way too many cases where this can go wrong diff --git a/remote.c b/remote.c index 10104d11e3..0b18840d43 100644 --- a/remote.c +++ b/remote.c @@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value, } else if (!strcmp(subkey, "serveroption")) { return parse_transport_option(key, value, &remote->server_options); + } else if (!strcmp(subkey, "followremotehead")) { + if (!strcmp(value, "never")) + remote->follow_remote_head = FOLLOW_REMOTE_NEVER; + else if (!strcmp(value, "create")) + remote->follow_remote_head = FOLLOW_REMOTE_CREATE; + else if (!strcmp(value, "warn")) + remote->follow_remote_head = FOLLOW_REMOTE_WARN; + else if (!strcmp(value, "always")) + remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS; } return 0; } diff --git a/remote.h b/remote.h index a7e5c4e07c..184b35653d 100644 --- a/remote.h +++ b/remote.h @@ -59,6 +59,13 @@ struct remote_state { void remote_state_clear(struct remote_state *remote_state); struct remote_state *remote_state_new(void); + enum follow_remote_head_settings { + FOLLOW_REMOTE_NEVER = -1, + FOLLOW_REMOTE_CREATE = 0, + FOLLOW_REMOTE_WARN = 1, + FOLLOW_REMOTE_ALWAYS = 2, + }; + struct remote { struct hashmap_entry ent; @@ -107,6 +114,8 @@ struct remote { char *http_proxy_authmethod; struct string_list server_options; + + enum follow_remote_head_settings follow_remote_head; }; /** diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 87698341f5..2467027d34 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -99,6 +99,108 @@ test_expect_success "fetch test remote HEAD change" ' branch=$(git rev-parse refs/remotes/origin/other) && test "z$head" = "z$branch"' +test_expect_success "fetch test followRemoteHEAD never" ' + test_when_finished "git config unset remote.origin.followRemoteHEAD" && + ( + cd "$D" && + cd two && + git update-ref --no-deref -d refs/remotes/origin/HEAD && + git config set remote.origin.followRemoteHEAD "never" && + git fetch && + test_must_fail git rev-parse --verify refs/remotes/origin/HEAD + ) +' + +test_expect_success "fetch test followRemoteHEAD warn no change" ' + test_when_finished "git config unset remote.origin.followRemoteHEAD" && + ( + cd "$D" && + cd two && + git rev-parse --verify refs/remotes/origin/other && + git remote set-head origin other && + git rev-parse --verify refs/remotes/origin/HEAD && + git rev-parse --verify refs/remotes/origin/main && + git config set remote.origin.followRemoteHEAD "warn" && + git fetch >output && + echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \ + "but we have ${SQ}other${SQ} locally." >expect && + echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect && + test_cmp expect output && + head=$(git rev-parse refs/remotes/origin/HEAD) && + branch=$(git rev-parse refs/remotes/origin/other) && + test "z$head" = "z$branch" + ) +' + +test_expect_success "fetch test followRemoteHEAD warn create" ' + test_when_finished "git config unset remote.origin.followRemoteHEAD" && + ( + cd "$D" && + cd two && + git update-ref --no-deref -d refs/remotes/origin/HEAD && + git config set remote.origin.followRemoteHEAD "warn" && + git rev-parse --verify refs/remotes/origin/main && + output=$(git fetch) && + test "z" = "z$output" && + head=$(git rev-parse refs/remotes/origin/HEAD) && + branch=$(git rev-parse refs/remotes/origin/main) && + test "z$head" = "z$branch" + ) +' + +test_expect_success "fetch test followRemoteHEAD warn detached" ' + test_when_finished "git config unset remote.origin.followRemoteHEAD" && + ( + cd "$D" && + cd two && + git update-ref --no-deref -d refs/remotes/origin/HEAD && + git update-ref refs/remotes/origin/HEAD HEAD && + HEAD=$(git log --pretty="%H") && + git config set remote.origin.followRemoteHEAD "warn" && + git fetch >output && + echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \ + "but we have a detached HEAD pointing to" \ + "${SQ}${HEAD}${SQ} locally." >expect && + echo "Run ${SQ}git remote set-head origin main${SQ} to follow the change." >>expect && + test_cmp expect output + ) +' + +test_expect_success "fetch test followRemoteHEAD warn quiet" ' + test_when_finished "git config unset remote.origin.followRemoteHEAD" && + ( + cd "$D" && + cd two && + git rev-parse --verify refs/remotes/origin/other && + git remote set-head origin other && + git rev-parse --verify refs/remotes/origin/HEAD && + git rev-parse --verify refs/remotes/origin/main && + git config set remote.origin.followRemoteHEAD "warn" && + output=$(git fetch --quiet) && + test "z" = "z$output" && + head=$(git rev-parse refs/remotes/origin/HEAD) && + branch=$(git rev-parse refs/remotes/origin/other) && + test "z$head" = "z$branch" + ) +' + +test_expect_success "fetch test followRemoteHEAD always" ' + test_when_finished "git config unset remote.origin.followRemoteHEAD" && + ( + cd "$D" && + cd two && + git rev-parse --verify refs/remotes/origin/other && + git remote set-head origin other && + git rev-parse --verify refs/remotes/origin/HEAD && + git rev-parse --verify refs/remotes/origin/main && + git config set remote.origin.followRemoteHEAD "always" && + git fetch && + head=$(git rev-parse refs/remotes/origin/HEAD) && + branch=$(git rev-parse refs/remotes/origin/main) && + test "z$head" = "z$branch" + ) +' + test_expect_success 'fetch --prune on its own works as expected' ' cd "$D" && git clone . prune && -- cgit v1.3 From 9e2b7005becaf730ff75f6efbef4542cc4454107 Mon Sep 17 00:00:00 2001 From: Bence Ferdinandy Date: Thu, 5 Dec 2024 13:16:21 +0100 Subject: fetch set_head: add warn-if-not-$branch option Currently if we want to have a remote/HEAD locally that is different from the one on the remote, but we still want to get a warning if remote changes HEAD, our only option is to have an indiscriminate warning with "follow_remote_head" set to "warn". Add a new option "warn-if-not-$branch", where $branch is a branch name we do not wish to get a warning about. If the remote HEAD is $branch do not warn, otherwise, behave as "warn". E.g. let's assume, that our remote origin has HEAD set to "master", but locally we have "git remote set-head origin seen". Setting 'remote.origin.followRemoteHEAD = "warn"' will always print a warning, even though the remote has not changed HEAD from "master". Setting 'remote.origin.followRemoteHEAD = "warn-if-not-master" will squelch the warning message, unless the remote changes HEAD from "master". Note, that should the remote change HEAD to "seen" (which we have locally), there will still be no warning. Improve the advice message in report_set_head to also include silencing the warning message with "warn-if-not-$branch". Signed-off-by: Bence Ferdinandy Signed-off-by: Junio C Hamano --- Documentation/config/remote.txt | 8 +++++--- builtin/fetch.c | 16 +++++++++++----- remote.c | 13 +++++++++++-- remote.h | 1 + t/t5510-fetch.sh | 38 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 66 insertions(+), 10 deletions(-) (limited to 'Documentation/config') diff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt index 024f92befc..4118c219c1 100644 --- a/Documentation/config/remote.txt +++ b/Documentation/config/remote.txt @@ -106,10 +106,12 @@ remote..followRemoteHEAD:: How linkgit:git-fetch[1] should handle updates to `remotes//HEAD`. The default value is "create", which will create `remotes//HEAD` if it exists on the remote, but not locally, but will not touch an - already existing local reference. Setting to "warn" will print + already existing local reference. Setting to "warn" will print a message if the remote has a different value, than the local one and - in case there is no local reference, it behaves like "create". Setting - to "always" will silently update it to the value on the remote. + in case there is no local reference, it behaves like "create". + A variant on "warn" is "warn-if-not-$branch", which behaves like + "warn", but if `HEAD` on the remote is `$branch` it will be silent. + Setting to "always" will silently update it to the value on the remote. Finally, setting it to "never" will never change or create the local reference. + diff --git a/builtin/fetch.c b/builtin/fetch.c index 897e71325f..c4257a7ead 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1584,10 +1584,12 @@ static void set_head_advice_msg(const char *remote, const char *head_name) const char message_advice_set_head[] = N_("Run 'git remote set-head %s %s' to follow the change, or set\n" "'remote.%s.followRemoteHEAD' configuration option to a different value\n" - "if you do not want to see this message."); + "if you do not want to see this message. Specifically running\n" + "'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n" + "until the remote changes HEAD to something else."); advise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head), - remote, head_name, remote); + remote, head_name, remote, remote, head_name); } static void report_set_head(const char *remote, const char *head_name, @@ -1612,7 +1614,8 @@ static void report_set_head(const char *remote, const char *head_name, strbuf_release(&buf_prefix); } -static int set_head(const struct ref *remote_refs, int follow_remote_head) +static int set_head(const struct ref *remote_refs, int follow_remote_head, + const char *no_warn_branch) { int result = 0, create_only, is_bare, was_detached; struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT, @@ -1669,7 +1672,9 @@ static int set_head(const struct ref *remote_refs, int follow_remote_head) result = 1; goto cleanup; } - if (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0) + if (verbosity >= 0 && + follow_remote_head == FOLLOW_REMOTE_WARN && + (!no_warn_branch || strcmp(no_warn_branch, head_name))) report_set_head(remote, head_name, &b_local_head, was_detached); cleanup: @@ -1898,7 +1903,8 @@ static int do_fetch(struct transport *transport, "you need to specify exactly one branch with the --set-upstream option")); } } - if (set_head(remote_refs, transport->remote->follow_remote_head)) + if (set_head(remote_refs, transport->remote->follow_remote_head, + transport->remote->no_warn_branch)) ; /* * Way too many cases where this can go wrong diff --git a/remote.c b/remote.c index 0b18840d43..4ec5d3f47b 100644 --- a/remote.c +++ b/remote.c @@ -515,14 +515,23 @@ static int handle_config(const char *key, const char *value, return parse_transport_option(key, value, &remote->server_options); } else if (!strcmp(subkey, "followremotehead")) { + const char *no_warn_branch; if (!strcmp(value, "never")) remote->follow_remote_head = FOLLOW_REMOTE_NEVER; else if (!strcmp(value, "create")) remote->follow_remote_head = FOLLOW_REMOTE_CREATE; - else if (!strcmp(value, "warn")) + else if (!strcmp(value, "warn")) { remote->follow_remote_head = FOLLOW_REMOTE_WARN; - else if (!strcmp(value, "always")) + remote->no_warn_branch = NULL; + } else if (skip_prefix(value, "warn-if-not-", &no_warn_branch)) { + remote->follow_remote_head = FOLLOW_REMOTE_WARN; + remote->no_warn_branch = no_warn_branch; + } else if (!strcmp(value, "always")) { remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS; + } else { + warning(_("unrecognized followRemoteHEAD value '%s' ignored"), + value); + } } return 0; } diff --git a/remote.h b/remote.h index 184b35653d..bda10dd5c8 100644 --- a/remote.h +++ b/remote.h @@ -116,6 +116,7 @@ struct remote { struct string_list server_options; enum follow_remote_head_settings follow_remote_head; + const char *no_warn_branch; }; /** diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 5c96591b9e..13881603f6 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -182,6 +182,44 @@ test_expect_success "fetch test followRemoteHEAD warn quiet" ' ) ' +test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is same" ' + test_when_finished "git config unset remote.origin.followRemoteHEAD" && + ( + cd "$D" && + cd two && + git rev-parse --verify refs/remotes/origin/other && + git remote set-head origin other && + git rev-parse --verify refs/remotes/origin/HEAD && + git rev-parse --verify refs/remotes/origin/main && + git config set remote.origin.followRemoteHEAD "warn-if-not-main" && + actual=$(git fetch) && + test "z" = "z$actual" && + head=$(git rev-parse refs/remotes/origin/HEAD) && + branch=$(git rev-parse refs/remotes/origin/other) && + test "z$head" = "z$branch" + ) +' + +test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is different" ' + test_when_finished "git config unset remote.origin.followRemoteHEAD" && + ( + cd "$D" && + cd two && + git rev-parse --verify refs/remotes/origin/other && + git remote set-head origin other && + git rev-parse --verify refs/remotes/origin/HEAD && + git rev-parse --verify refs/remotes/origin/main && + git config set remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" && + git fetch >actual && + echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \ + "but we have ${SQ}other${SQ} locally." >expect && + test_cmp expect actual && + head=$(git rev-parse refs/remotes/origin/HEAD) && + branch=$(git rev-parse refs/remotes/origin/other) && + test "z$head" = "z$branch" + ) +' + test_expect_success "fetch test followRemoteHEAD always" ' test_when_finished "git config unset remote.origin.followRemoteHEAD" && ( -- cgit v1.3 From 9219325be74c35c493a61ab3107d215cad91cf38 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 6 Dec 2024 14:24:53 +0100 Subject: Documentation: allow sourcing generated includes from separate dir Our documentation uses "include::" directives to include parts that are either reused across multiple documents or parts that we generate at build time. Unfortunately, top-level includes are only ever resolved relative to the base directory, which is typically the directory of the including document. Most importantly, it is not possible to have either asciidoc or asciidoctor search multiple directories. It follows that both kinds of includes must live in the same directory. This is of course a bummer for out-of-tree builds, because here the dynamically-built includes live in the build directory whereas the static includes live in the source directory. Introduce a `build_dir` attribute and prepend it to all of our includes for dynamically-built files. This attribute gets set to the build directory and thus converts the include path to an absolute path, which asciidoc and asciidoctor know how to resolve. Note that this change also requires us to update "build-docdep.perl", which tries to figure out included files such our Makefile can set up proper build-time dependencies. This script simply scans through the source files for any lines that match "^include::" and treats the remainder of the line as included file path. But given that those may now contain the "{build_dir}" variable we have to teach the script to replace that attribute with the actual build directory. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- Documentation/Makefile | 3 ++- Documentation/build-docdep.perl | 2 ++ Documentation/config/diff.txt | 2 +- Documentation/config/merge.txt | 2 +- Documentation/git.txt | 24 ++++++++++++------------ 5 files changed, 18 insertions(+), 15 deletions(-) (limited to 'Documentation/config') diff --git a/Documentation/Makefile b/Documentation/Makefile index 9371f29425..a74fc0ff3d 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -224,6 +224,7 @@ SHELL_PATH ?= $(SHELL) # Shell quote; SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +ASCIIDOC_EXTRA += -abuild_dir='$(shell pwd)' ifdef DEFAULT_PAGER DEFAULT_PAGER_SQ = $(subst ','\'',$(DEFAULT_PAGER)) ASCIIDOC_EXTRA += -a 'git-default-pager=$(DEFAULT_PAGER_SQ)' @@ -289,7 +290,7 @@ docdep_prereqs = \ cmd-list.made $(cmds_txt) doc.dep : $(docdep_prereqs) $(DOC_DEP_TXT) build-docdep.perl - $(QUIET_GEN)$(PERL_PATH) ./build-docdep.perl >$@ $(QUIET_STDERR) + $(QUIET_GEN)$(PERL_PATH) ./build-docdep.perl "$(shell pwd)" >$@ $(QUIET_STDERR) ifneq ($(MAKECMDGOALS),clean) -include doc.dep diff --git a/Documentation/build-docdep.perl b/Documentation/build-docdep.perl index 1b3ac8fdd9..315efaa2fa 100755 --- a/Documentation/build-docdep.perl +++ b/Documentation/build-docdep.perl @@ -1,5 +1,6 @@ #!/usr/bin/perl +my ($build_dir) = @ARGV; my %include = (); my %included = (); @@ -10,6 +11,7 @@ for my $text (<*.txt>) { chomp; s/^include::\s*//; s/\[\]//; + s/{build_dir}/${build_dir}/; $include{$text}{$_} = 1; $included{$_} = 1; } diff --git a/Documentation/config/diff.txt b/Documentation/config/diff.txt index 45f3fe855c..fdae13a212 100644 --- a/Documentation/config/diff.txt +++ b/Documentation/config/diff.txt @@ -218,7 +218,7 @@ endif::git-diff[] Set this option to `true` to make the diff driver cache the text conversion outputs. See linkgit:gitattributes[5] for details. -include::../mergetools-diff.txt[] +include::{build_dir}/mergetools-diff.txt[] `diff.indentHeuristic`:: Set this option to `false` to disable the default heuristics diff --git a/Documentation/config/merge.txt b/Documentation/config/merge.txt index 8851b6cede..82554d65a0 100644 --- a/Documentation/config/merge.txt +++ b/Documentation/config/merge.txt @@ -101,7 +101,7 @@ merge.guitool:: Any other value is treated as a custom merge tool and requires that a corresponding mergetool..cmd variable is defined. -include::../mergetools-merge.txt[] +include::{build_dir}/mergetools-merge.txt[] merge.verbosity:: Controls the amount of output shown by the recursive merge diff --git a/Documentation/git.txt b/Documentation/git.txt index d15a869762..44f0797ccf 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -245,17 +245,17 @@ ancillary user utilities. Main porcelain commands ~~~~~~~~~~~~~~~~~~~~~~~ -include::cmds-mainporcelain.txt[] +include::{build_dir}/cmds-mainporcelain.txt[] Ancillary Commands ~~~~~~~~~~~~~~~~~~ Manipulators: -include::cmds-ancillarymanipulators.txt[] +include::{build_dir}/cmds-ancillarymanipulators.txt[] Interrogators: -include::cmds-ancillaryinterrogators.txt[] +include::{build_dir}/cmds-ancillaryinterrogators.txt[] Interacting with Others @@ -264,7 +264,7 @@ Interacting with Others These commands are to interact with foreign SCM and with other people via patch over e-mail. -include::cmds-foreignscminterface.txt[] +include::{build_dir}/cmds-foreignscminterface.txt[] Reset, restore and revert ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -313,13 +313,13 @@ repositories. Manipulation commands ~~~~~~~~~~~~~~~~~~~~~ -include::cmds-plumbingmanipulators.txt[] +include::{build_dir}/cmds-plumbingmanipulators.txt[] Interrogation commands ~~~~~~~~~~~~~~~~~~~~~~ -include::cmds-plumbinginterrogators.txt[] +include::{build_dir}/cmds-plumbinginterrogators.txt[] In general, the interrogate commands do not touch the files in the working tree. @@ -328,12 +328,12 @@ the working tree. Syncing repositories ~~~~~~~~~~~~~~~~~~~~ -include::cmds-synchingrepositories.txt[] +include::{build_dir}/cmds-synchingrepositories.txt[] The following are helper commands used by the above; end users typically do not use them directly. -include::cmds-synchelpers.txt[] +include::{build_dir}/cmds-synchelpers.txt[] Internal helper commands @@ -342,14 +342,14 @@ Internal helper commands These are internal helper commands used by other commands; end users typically do not use them directly. -include::cmds-purehelpers.txt[] +include::{build_dir}/cmds-purehelpers.txt[] Guides ------ The following documentation pages are guides about Git concepts. -include::cmds-guide.txt[] +include::{build_dir}/cmds-guide.txt[] Repository, command and file interfaces --------------------------------------- @@ -358,7 +358,7 @@ This documentation discusses repository and command interfaces which users are expected to interact with directly. See `--user-formats` in linkgit:git-help[1] for more details on the criteria. -include::cmds-userinterfaces.txt[] +include::{build_dir}/cmds-userinterfaces.txt[] File formats, protocols and other developer interfaces ------------------------------------------------------ @@ -367,7 +367,7 @@ This documentation discusses file formats, over-the-wire protocols and other git developer interfaces. See `--developer-interfaces` in linkgit:git-help[1]. -include::cmds-developerinterfaces.txt[] +include::{build_dir}/cmds-developerinterfaces.txt[] Configuration Mechanism ----------------------- -- cgit v1.3 From 21e1b4486586d3a15d2d7bf0479e77636359b816 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 8 Jan 2025 23:35:23 +0000 Subject: difftool docs: restore correct position of tool list 2a9dfdf260 (difftool docs: de-duplicate configuration sections, 2022-09-07) moved the difftool documentation, but missed moving this "include" line that includes the generated list of diff tools, as referenced in the moved text. Restore the correct position of the included list. Signed-off-by: Adam Johnson Signed-off-by: Junio C Hamano --- Documentation/config/diff.txt | 2 -- Documentation/config/difftool.txt | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'Documentation/config') diff --git a/Documentation/config/diff.txt b/Documentation/config/diff.txt index fdae13a212..1135a62a0a 100644 --- a/Documentation/config/diff.txt +++ b/Documentation/config/diff.txt @@ -218,8 +218,6 @@ endif::git-diff[] Set this option to `true` to make the diff driver cache the text conversion outputs. See linkgit:gitattributes[5] for details. -include::{build_dir}/mergetools-diff.txt[] - `diff.indentHeuristic`:: Set this option to `false` to disable the default heuristics that shift diff hunk boundaries to make patches easier to read. diff --git a/Documentation/config/difftool.txt b/Documentation/config/difftool.txt index 447c40d85a..6cd47331a9 100644 --- a/Documentation/config/difftool.txt +++ b/Documentation/config/difftool.txt @@ -13,6 +13,8 @@ diff.guitool:: and requires that a corresponding difftool..cmd variable is defined. +include::{build_dir}/mergetools-diff.txt[] + difftool..cmd:: Specify the command to invoke the specified diff tool. The specified command is evaluated in shell with the following -- cgit v1.3 From 77b2d29e91c568dc3b08adbfc48fea641d4e39f7 Mon Sep 17 00:00:00 2001 From: Jean-Noël Avila Date: Fri, 10 Jan 2025 10:08:23 +0000 Subject: doc: convert git-notes to new documentation format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Switch the synopsis to a synopsis block which will automatically format placeholders in italics and keywords in monospace - Use __ instead of in the description - Use `backticks` for keywords and more complex option descriptions. The new rendering engine will apply synopsis rules to these spans. Signed-off-by: Jean-Noël Avila Signed-off-by: Junio C Hamano --- Documentation/config/notes.txt | 32 +++---- Documentation/git-notes.txt | 191 +++++++++++++++++++++-------------------- 2 files changed, 112 insertions(+), 111 deletions(-) (limited to 'Documentation/config') diff --git a/Documentation/config/notes.txt b/Documentation/config/notes.txt index 43db8e808d..b7e536496f 100644 --- a/Documentation/config/notes.txt +++ b/Documentation/config/notes.txt @@ -1,4 +1,4 @@ -notes.mergeStrategy:: +`notes.mergeStrategy`:: Which merge strategy to choose by default when resolving notes conflicts. Must be one of `manual`, `ours`, `theirs`, `union`, or `cat_sort_uniq`. Defaults to `manual`. See the "NOTES MERGE STRATEGIES" @@ -7,17 +7,17 @@ notes.mergeStrategy:: This setting can be overridden by passing the `--strategy` option to linkgit:git-notes[1]. -notes..mergeStrategy:: +`notes..mergeStrategy`:: Which merge strategy to choose when doing a notes merge into - refs/notes/. This overrides the more general - "notes.mergeStrategy". See the "NOTES MERGE STRATEGIES" section in + `refs/notes/`. This overrides the more general + `notes.mergeStrategy`. See the "NOTES MERGE STRATEGIES" section in linkgit:git-notes[1] for more information on the available strategies. -notes.displayRef:: +`notes.displayRef`:: Which ref (or refs, if a glob or specified more than once), in addition to the default set by `core.notesRef` or `GIT_NOTES_REF`, to read notes from when showing commit - messages with the 'git log' family of commands. + messages with the `git log` family of commands. + This setting can be overridden with the `GIT_NOTES_DISPLAY_REF` environment variable, which must be a colon separated list of refs or @@ -26,27 +26,27 @@ globs. A warning will be issued for refs that do not exist, but a glob that does not match any refs is silently ignored. + -This setting can be disabled by the `--no-notes` option to the 'git -log' family of commands, or by the `--notes=` option accepted by +This setting can be disabled by the `--no-notes` option to the linkgit:git-log[1] +family of commands, or by the `--notes=` option accepted by those commands. + -The effective value of "core.notesRef" (possibly overridden by -GIT_NOTES_REF) is also implicitly added to the list of refs to be +The effective value of `core.notesRef` (possibly overridden by +`GIT_NOTES_REF`) is also implicitly added to the list of refs to be displayed. -notes.rewrite.:: - When rewriting commits with (currently `amend` or +`notes.rewrite.`:: + When rewriting commits with __ (currently `amend` or `rebase`), if this variable is `false`, git will not copy notes from the original to the rewritten commit. Defaults to - `true`. See also "`notes.rewriteRef`" below. + `true`. See also `notes.rewriteRef` below. + This setting can be overridden with the `GIT_NOTES_REWRITE_REF` environment variable, which must be a colon separated list of refs or globs. -notes.rewriteMode:: +`notes.rewriteMode`:: When copying notes during a rewrite (see the - "notes.rewrite." option), determines what to do if + `notes.rewrite.` option), determines what to do if the target commit already has a note. Must be one of `overwrite`, `concatenate`, `cat_sort_uniq`, or `ignore`. Defaults to `concatenate`. @@ -54,7 +54,7 @@ notes.rewriteMode:: This setting can be overridden with the `GIT_NOTES_REWRITE_MODE` environment variable. -notes.rewriteRef:: +`notes.rewriteRef`:: When copying notes during a rewrite, specifies the (fully qualified) ref whose notes should be copied. May be a glob, in which case notes in all matching refs will be copied. You diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 84022f99d7..5c8e6ff566 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -7,19 +7,19 @@ git-notes - Add or inspect object notes SYNOPSIS -------- -[verse] -'git notes' [list []] -'git notes' add [-f] [--allow-empty] [--[no-]separator | --separator=] [--[no-]stripspace] [-F | -m | (-c | -C) ] [-e] [] -'git notes' copy [-f] ( --stdin | [] ) -'git notes' append [--allow-empty] [--[no-]separator | --separator=] [--[no-]stripspace] [-F | -m | (-c | -C) ] [-e] [] -'git notes' edit [--allow-empty] [] [--[no-]stripspace] -'git notes' show [] -'git notes' merge [-v | -q] [-s ] -'git notes' merge --commit [-v | -q] -'git notes' merge --abort [-v | -q] -'git notes' remove [--ignore-missing] [--stdin] [...] -'git notes' prune [-n] [-v] -'git notes' get-ref +[synopsis] +git notes [list []] +git notes add [-f] [--allow-empty] [--[no-]separator | --separator=] [--[no-]stripspace] [-F | -m | (-c | -C) ] [-e] [] +git notes copy [-f] ( --stdin | [] ) +git notes append [--allow-empty] [--[no-]separator | --separator=] [--[no-]stripspace] [-F | -m | (-c | -C) ] [-e] [] +git notes edit [--allow-empty] [] [--[no-]stripspace] +git notes show [] +git notes merge [-v | -q] [-s ] +git notes merge --commit [-v | -q] +git notes merge --abort [-v | -q] +git notes remove [--ignore-missing] [--stdin] [...] +git notes prune [-n] [-v] +git notes get-ref DESCRIPTION @@ -33,34 +33,34 @@ ENVIRONMENT sections below. If this ref does not exist, it will be quietly created when it is first needed to store a note. A typical use of notes is to supplement a commit message without -changing the commit itself. Notes can be shown by 'git log' along with +changing the commit itself. Notes can be shown by `git log` along with the original commit message. To distinguish these notes from the message stored in the commit object, the notes are indented like the -message, after an unindented line saying "Notes ():" (or +message, after an unindented line saying "Notes (__):" (or "Notes:" for `refs/notes/commits`). Notes can also be added to patches prepared with `git format-patch` by using the `--notes` option. Such notes are added as a patch commentary after a three dash separator line. -To change which notes are shown by 'git log', see the -"notes.displayRef" discussion in <>. +To change which notes are shown by `git log`, see the +`notes.displayRef` discussion in <>. -See the "notes.rewrite." configuration for a way to carry +See the `notes.rewrite.` configuration for a way to carry notes across commands that rewrite commits. SUBCOMMANDS ----------- -list:: +`list`:: List the notes object for a given object. If no object is given, show a list of all note objects and the objects they - annotate (in the format " "). + annotate (in the format "` `"). This is the default subcommand if no subcommand is given. -add:: - Add notes for a given object (defaults to HEAD). Abort if the +`add`:: + Add notes for a given object (defaults to `HEAD`). Abort if the object already has notes (use `-f` to overwrite existing notes). However, if you're using `add` interactively (using an editor to supply the notes contents), then - instead of aborting - @@ -71,10 +71,10 @@ add:: fine-tune the message(s) supplied from `-m` and `-F` options interactively (using an editor) before adding the note. -copy:: +`copy`:: Copy the notes for the first object onto the second object (defaults to - HEAD). Abort if the second object already has notes, or if the first - object has none (use -f to overwrite existing notes to the + `HEAD`). Abort if the second object already has notes, or if the first + object has none (use `-f` to overwrite existing notes to the second object). This subcommand is equivalent to: `git notes add [-f] -C $(git notes list ) ` + @@ -84,27 +84,27 @@ In `--stdin` mode, take lines in the format SP [ SP ] LF ---------- + -on standard input, and copy the notes from each to its -corresponding . (The optional `` is ignored so that +on standard input, and copy the notes from each __ to its +corresponding __. (The optional __ is ignored so that the command can read the input given to the `post-rewrite` hook.) -append:: +`append`:: Append new message(s) given by `-m` or `-F` options to an existing note, or add them as a new note if one does not - exist, for the object (defaults to HEAD). When appending to + exist, for the object (defaults to `HEAD`). When appending to an existing note, a blank line is added before each new message as an inter-paragraph separator. The separator can be customized with the `--separator` option. Edit the notes to be appended given by `-m` and `-F` options with `-e` interactively (using an editor) before appending the note. -edit:: - Edit the notes for a given object (defaults to HEAD). +`edit`:: + Edit the notes for a given object (defaults to `HEAD`). -show:: - Show the notes for a given object (defaults to HEAD). +`show`:: + Show the notes for a given object (defaults to `HEAD`). -merge:: +`merge`:: Merge the given notes ref into the current notes ref. This will try to merge the changes made by the given notes ref (called "remote") since the merge-base (if @@ -112,35 +112,35 @@ merge:: + If conflicts arise and a strategy for automatically resolving conflicting notes (see the "NOTES MERGE STRATEGIES" section) is not given, -the "manual" resolver is used. This resolver checks out the +the `manual` resolver is used. This resolver checks out the conflicting notes in a special worktree (`.git/NOTES_MERGE_WORKTREE`), and instructs the user to manually resolve the conflicts there. When done, the user can either finalize the merge with -'git notes merge --commit', or abort the merge with -'git notes merge --abort'. +`git notes merge --commit`, or abort the merge with +`git notes merge --abort`. -remove:: - Remove the notes for given objects (defaults to HEAD). When +`remove`:: + Remove the notes for given objects (defaults to `HEAD`). When giving zero or one object from the command line, this is equivalent to specifying an empty note message to the `edit` subcommand. -prune:: +`prune`:: Remove all notes for non-existing/unreachable objects. -get-ref:: +`get-ref`:: Print the current notes ref. This provides an easy way to retrieve the current notes ref (e.g. from scripts). OPTIONS ------- --f:: ---force:: +`-f`:: +`--force`:: When adding notes to an object that already has notes, overwrite the existing notes (instead of aborting). --m :: ---message=:: +`-m `:: +`--message=`:: Use the given note message (instead of prompting). If multiple `-m` options are given, their values are concatenated as separate paragraphs. @@ -148,95 +148,96 @@ OPTIONS single line between paragraphs will be stripped out. If you wish to keep them verbatim, use `--no-stripspace`. --F :: ---file=:: - Take the note message from the given file. Use '-' to +`-F `:: +`--file=`:: + Take the note message from the given file. Use `-` to read the note message from the standard input. Lines starting with `#` and empty lines other than a single line between paragraphs will be stripped out. If you wish to keep them verbatim, use `--no-stripspace`. --C :: ---reuse-message=:: +`-C `:: +`--reuse-message=`:: Take the given blob object (for example, another note) as the note message. (Use `git notes copy ` instead to copy notes between objects.). By default, message will be copied verbatim, but if you wish to strip out the lines starting with `#` and empty lines other than a single line - between paragraphs, use with`--stripspace` option. + between paragraphs, use with `--stripspace` option. --c :: ---reedit-message=:: - Like '-C', but with `-c` the editor is invoked, so that +`-c `:: +`--reedit-message=`:: + Like `-C`, but with `-c` the editor is invoked, so that the user can further edit the note message. ---allow-empty:: +`--allow-empty`:: Allow an empty note object to be stored. The default behavior is to automatically remove empty notes. ---[no-]separator, --separator=:: +`--[no-]separator`:: +`--separator=`:: Specify a string used as a custom inter-paragraph separator (a newline is added at the end as needed). If `--no-separator`, no separators will be added between paragraphs. Defaults to a blank line. ---[no-]stripspace:: +`--[no-]stripspace`:: Strip leading and trailing whitespace from the note message. Also strip out empty lines other than a single line between paragraphs. Lines starting with `#` will be stripped out in non-editor cases like `-m`, `-F` and `-C`, but not in editor case like `git notes edit`, `-c`, etc. ---ref :: - Manipulate the notes tree in . This overrides - `GIT_NOTES_REF` and the "core.notesRef" configuration. The ref +`--ref `:: + Manipulate the notes tree in __. This overrides + `GIT_NOTES_REF` and the `core.notesRef` configuration. The ref specifies the full refname when it begins with `refs/notes/`; when it begins with `notes/`, `refs/` and otherwise `refs/notes/` is prefixed to form a full name of the ref. ---ignore-missing:: +`--ignore-missing`:: Do not consider it an error to request removing notes from an object that does not have notes attached to it. ---stdin:: +`--stdin`:: Also read the object names to remove notes from the standard input (there is no reason you cannot combine this with object names from the command line). --n:: ---dry-run:: +`-n`:: +`--dry-run`:: Do not remove anything; just report the object names whose notes would be removed. --s :: ---strategy=:: +`-s `:: +`--strategy=`:: When merging notes, resolve notes conflicts using the given - strategy. The following strategies are recognized: "manual" - (default), "ours", "theirs", "union" and "cat_sort_uniq". - This option overrides the "notes.mergeStrategy" configuration setting. + strategy. The following strategies are recognized: `manual` + (default), `ours`, `theirs`, `union` and `cat_sort_uniq`. + This option overrides the `notes.mergeStrategy` configuration setting. See the "NOTES MERGE STRATEGIES" section below for more information on each notes merge strategy. ---commit:: - Finalize an in-progress 'git notes merge'. Use this option - when you have resolved the conflicts that 'git notes merge' - stored in .git/NOTES_MERGE_WORKTREE. This amends the partial - merge commit created by 'git notes merge' (stored in - .git/NOTES_MERGE_PARTIAL) by adding the notes in - .git/NOTES_MERGE_WORKTREE. The notes ref stored in the - .git/NOTES_MERGE_REF symref is updated to the resulting commit. - ---abort:: - Abort/reset an in-progress 'git notes merge', i.e. a notes merge +`--commit`:: + Finalize an in-progress `git notes merge`. Use this option + when you have resolved the conflicts that `git notes merge` + stored in `.git/NOTES_MERGE_WORKTREE`. This amends the partial + merge commit created by `git notes merge` (stored in + `.git/NOTES_MERGE_PARTIAL`) by adding the notes in + `.git/NOTES_MERGE_WORKTREE`. The notes ref stored in the + `.git/NOTES_MERGE_REF` symref is updated to the resulting commit. + +`--abort`:: + Abort/reset an in-progress `git notes merge`, i.e. a notes merge with conflicts. This simply removes all files related to the notes merge. --q:: ---quiet:: +`-q`:: +`--quiet`:: When merging notes, operate quietly. --v:: ---verbose:: +`-v`:: +`--verbose`:: When merging notes, be more verbose. When pruning notes, report all object names whose notes are removed. @@ -270,28 +271,28 @@ object, in which case the history of the notes can be read with NOTES MERGE STRATEGIES ---------------------- -The default notes merge strategy is "manual", which checks out +The default notes merge strategy is `manual`, which checks out conflicting notes in a special work tree for resolving notes conflicts (`.git/NOTES_MERGE_WORKTREE`), and instructs the user to resolve the conflicts in that work tree. When done, the user can either finalize the merge with -'git notes merge --commit', or abort the merge with -'git notes merge --abort'. +`git notes merge --commit`, or abort the merge with +`git notes merge --abort`. Users may select an automated merge strategy from among the following using -either -s/--strategy option or configuring notes.mergeStrategy accordingly: +either `-s`/`--strategy` option or configuring `notes.mergeStrategy` accordingly: -"ours" automatically resolves conflicting notes in favor of the local +`ours` automatically resolves conflicting notes in favor of the local version (i.e. the current notes ref). -"theirs" automatically resolves notes conflicts in favor of the remote +`theirs` automatically resolves notes conflicts in favor of the remote version (i.e. the given notes ref being merged into the current notes ref). -"union" automatically resolves notes conflicts by concatenating the +`union` automatically resolves notes conflicts by concatenating the local and remote versions. -"cat_sort_uniq" is similar to "union", but in addition to concatenating +`cat_sort_uniq` is similar to `union`, but in addition to concatenating the local and remote versions, this strategy also sorts the resulting lines, and removes duplicate lines from the result. This is equivalent to applying the "cat | sort | uniq" shell pipeline to the local and @@ -320,7 +321,7 @@ Notes: In principle, a note is a regular Git blob, and any kind of (non-)format is accepted. You can binary-safely create notes from -arbitrary files using 'git hash-object': +arbitrary files using `git hash-object`: ------------ $ cc *.c @@ -331,7 +332,7 @@ $ git notes --ref=built add --allow-empty -C "$blob" HEAD (You cannot simply use `git notes --ref=built add -F a.out HEAD` because that is not binary-safe.) Of course, it doesn't make much sense to display non-text-format notes -with 'git log', so if you use such notes, you'll probably need to write +with `git log`, so if you use such notes, you'll probably need to write some special-purpose tools to do something useful with them. @@ -339,7 +340,7 @@ some special-purpose tools to do something useful with them. CONFIGURATION ------------- -core.notesRef:: +`core.notesRef`:: Notes ref to read and manipulate instead of `refs/notes/commits`. Must be an unabbreviated ref name. This setting can be overridden through the environment and -- cgit v1.3 From 819fdd6e76d6dbd3410e9614be843cd50f8a6c75 Mon Sep 17 00:00:00 2001 From: Jean-Noël Avila Date: Wed, 15 Jan 2025 20:23:47 +0000 Subject: doc: convert git commit config to new format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also prevent git-commit manpage to refer to itself in the config description by using a variable. Signed-off-by: Jean-Noël Avila Signed-off-by: Junio C Hamano --- Documentation/config/commit.txt | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) (limited to 'Documentation/config') diff --git a/Documentation/config/commit.txt b/Documentation/config/commit.txt index 62f0d92fda..d3f4624fd2 100644 --- a/Documentation/config/commit.txt +++ b/Documentation/config/commit.txt @@ -1,29 +1,34 @@ -commit.cleanup:: +ifdef::git-commit[] +:see-git-commit: +endif::git-commit[] +ifndef::git-commit[] +:see-git-commit: See linkgit:git-commit[1] for details. +endif::git-commit[] +`commit.cleanup`:: This setting overrides the default of the `--cleanup` option in - `git commit`. See linkgit:git-commit[1] for details. Changing the - default can be useful when you always want to keep lines that begin + `git commit`. {see-git-commit} Changing the default can be useful + when you always want to keep lines that begin with the comment character `#` in your log message, in which case you would do `git config commit.cleanup whitespace` (note that you will have to remove the help lines that begin with `#` in the commit log template yourself, if you do this). -commit.gpgSign:: - +`commit.gpgSign`:: A boolean to specify whether all commits should be GPG signed. Use of this option when doing operations such as rebase can result in a large number of commits being signed. It may be convenient to use an agent to avoid typing your GPG passphrase several times. -commit.status:: +`commit.status`:: A boolean to enable/disable inclusion of status information in the commit message template when using an editor to prepare the commit - message. Defaults to true. + message. Defaults to `true`. -commit.template:: +`commit.template`:: Specify the pathname of a file to use as the template for new commit messages. -commit.verbose:: +`commit.verbose`:: A boolean or int to specify the level of verbosity with `git commit`. - See linkgit:git-commit[1]. + {see-git-commit} -- cgit v1.3