{"thread":{"id":"54382","subject":"[PATCH v2 0/2] rev-parse options for absolute or relative paths","startedAt":"2020-10-09T19:15:33Z","lastAt":"2020-11-16T02:22:20Z","messageCount":13,"participants":["brian m. carlson","Junio C Hamano","Emily Shaffer","Johannes Schindelin"],"isPatch":true,"patchVersion":2,"patchTotal":2},"messages":[{"id":"407238","messageId":"20201009191511.267461-3-sandals@crustytoothpaste.net","threadId":"54382","inReplyTo":"20201009191511.267461-1-sandals@crustytoothpaste.net","subject":"[PATCH v2 2/2] rev-parse: add option for absolute or relative path formatting","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-10-09T19:15:11Z","receivedAt":"2020-10-09T19:15:33Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"git rev-parse has several options which print various paths.  Some of\nthese paths are printed relative to the current working directory, and\nsome are absolute.\n\nNormally, this is not a problem, but there are times when one wants\npaths entirely in one format or another.  This can be done trivially if\nthe paths are canonical, but canonicalizing paths is not possible on\nsome shell scripting environments which lack realpath(1) and also in Go,\nwhich lacks functions that properly canonicalize paths on Windows.\n\nTo help out the scripter, let's provide an option which turns most of\nthe paths printed by git rev-parse to be either relative to the current\nworking directory or absolute and canonical.  Document which options are\naffected and which are not so that users are not confused.\n\nThis approach is cleaner and tidier than providing duplicates of\nexisting options which are either relative or absolute.\n\nNote that if the user needs both forms, it is possible to pass an\nadditional option in the middle of the command line which changes the\nbehavior of subsequent operations.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n Documentation/git-rev-parse.txt |  71 ++++++++++++---------\n builtin/rev-parse.c             | 105 ++++++++++++++++++++++++++++----\n t/t1500-rev-parse.sh            |  57 ++++++++++++++++-\n 3 files changed, 190 insertions(+), 43 deletions(-)\n\ndiff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\nindex 19b12b6d43..6b95292559 100644\n--- a/Documentation/git-rev-parse.txt\n+++ b/Documentation/git-rev-parse.txt\n@@ -208,6 +208,15 @@ Options for Files\n \tOnly the names of the variables are listed, not their value,\n \teven if they are set.\n \n+--path-format=(absolute|relative)::\n+\tControls the behavior of certain other options following it on the command\n+\tline. If specified as absolute, the paths printed by those options will be\n+\tabsolute and canonical. If specified as relative, the paths will be relative\n+\tto the current working directory if that is possible.  The default is option\n+\tspecific.\n+\n+The following options are modified by `--path-format`:\n+\n --git-dir::\n \tShow `$GIT_DIR` if defined. Otherwise show the path to\n \tthe .git directory. The path shown, when relative, is\n@@ -217,13 +226,42 @@ If `$GIT_DIR` is not defined and the current directory\n is not detected to lie in a Git repository or work tree\n print a message to stderr and exit with nonzero status.\n \n+--git-common-dir::\n+\tShow `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`.\n+\n+--resolve-git-dir <path>::\n+\tCheck if <path> is a valid repository or a gitfile that\n+\tpoints at a valid repository, and print the location of the\n+\trepository.  If <path> is a gitfile then the resolved path\n+\tto the real repository is printed.\n+\n+--git-path <path>::\n+\tResolve \"$GIT_DIR/<path>\" and takes other path relocation\n+\tvariables such as $GIT_OBJECT_DIRECTORY,\n+\t$GIT_INDEX_FILE... into account. For example, if\n+\t$GIT_OBJECT_DIRECTORY is set to /foo/bar then \"git rev-parse\n+\t--git-path objects/abc\" returns /foo/bar/abc.\n+\n+--show-toplevel::\n+\tShow the (by default, absolute) path of the top-level directory\n+\tof the working tree. If there is no working tree, report an error.\n+\n+--show-superproject-working-tree::\n+\tShow the absolute path of the root of the superproject's\n+\tworking tree (if exists) that uses the current repository as\n+\tits submodule.  Outputs nothing if the current repository is\n+\tnot used as a submodule by any project.\n+\n+--shared-index-path::\n+\tShow the path to the shared index file in split index mode, or\n+\tempty if not in split-index mode.\n+\n+The following options are unaffected by `--path-format`:\n+\n --absolute-git-dir::\n \tLike `--git-dir`, but its output is always the canonicalized\n \tabsolute path.\n \n---git-common-dir::\n-\tShow `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`.\n-\n --is-inside-git-dir::\n \tWhen the current working directory is below the repository\n \tdirectory print \"true\", otherwise \"false\".\n@@ -238,19 +276,6 @@ print a message to stderr and exit with nonzero status.\n --is-shallow-repository::\n \tWhen the repository is shallow print \"true\", otherwise \"false\".\n \n---resolve-git-dir <path>::\n-\tCheck if <path> is a valid repository or a gitfile that\n-\tpoints at a valid repository, and print the location of the\n-\trepository.  If <path> is a gitfile then the resolved path\n-\tto the real repository is printed.\n-\n---git-path <path>::\n-\tResolve \"$GIT_DIR/<path>\" and takes other path relocation\n-\tvariables such as $GIT_OBJECT_DIRECTORY,\n-\t$GIT_INDEX_FILE... into account. For example, if\n-\t$GIT_OBJECT_DIRECTORY is set to /foo/bar then \"git rev-parse\n-\t--git-path objects/abc\" returns /foo/bar/abc.\n-\n --show-cdup::\n \tWhen the command is invoked from a subdirectory, show the\n \tpath of the top-level directory relative to the current\n@@ -261,20 +286,6 @@ print a message to stderr and exit with nonzero status.\n \tpath of the current directory relative to the top-level\n \tdirectory.\n \n---show-toplevel::\n-\tShow the absolute path of the top-level directory of the working\n-\ttree. If there is no working tree, report an error.\n-\n---show-superproject-working-tree::\n-\tShow the absolute path of the root of the superproject's\n-\tworking tree (if exists) that uses the current repository as\n-\tits submodule.  Outputs nothing if the current repository is\n-\tnot used as a submodule by any project.\n-\n---shared-index-path::\n-\tShow the path to the shared index file in split index mode, or\n-\tempty if not in split-index mode.\n-\n --show-object-format[=(storage|input|output)]::\n \tShow the object format (hash algorithm) used for the repository\n \tfor storage inside the `.git` directory, input, or output. For\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex ed200c8af1..ec62b4cd16 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -583,6 +583,76 @@ static void handle_ref_opt(const char *pattern, const char *prefix)\n \tclear_ref_exclusion(&ref_excludes);\n }\n \n+enum format_type {\n+\t/* We would like a relative path. */\n+\tFORMAT_RELATIVE,\n+\t/* We would like a canonical absolute path. */\n+\tFORMAT_CANONICAL,\n+\t/* We would like the default behavior. */\n+\tFORMAT_DEFAULT,\n+};\n+\n+enum default_type {\n+\t/* Our default is a relative path. */\n+\tDEFAULT_RELATIVE,\n+\t/* Our default is a relative path if there's a shared root. */\n+\tDEFAULT_RELATIVE_IF_SHARED,\n+\t/* Our default is a canonical absolute path. */\n+\tDEFAULT_CANONICAL,\n+\t/* Our default is not to modify the item. */\n+\tDEFAULT_UNMODIFIED,\n+};\n+\n+static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n+{\n+\tchar *cwd = NULL;\n+\t/*\n+\t * We don't ever produce a relative path if prefix is NULL, so set the\n+\t * prefix to the current directory so that we can produce a relative\n+\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n+\t * we want an absolute path unless the two share a common prefix, so don't\n+\t * set it in that case, since doing so causes a relative path to always\n+\t * be produced if possible.\n+\t */\n+\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n+\t\tprefix = cwd = xgetcwd();\n+\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n+\t\tputs(path);\n+\t} else if (format == FORMAT_RELATIVE ||\n+\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n+\t\t/*\n+\t\t * In order for relative_path to work as expected, we need to\n+\t\t * make sure that both paths are absolute paths.  If we don't,\n+\t\t * we can end up with an unexpected absolute path that the user\n+\t\t * didn't want.\n+\t\t */\n+\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n+\t\tif (!is_absolute_path(path)) {\n+\t\t\tif (!strbuf_realpath_missing(&realbuf, path))\n+\t\t\t\tdie(_(\"Unable to resolve path '%s'\"), path);\n+\t\t\tpath = realbuf.buf;\n+\t\t}\n+\t\tif (!is_absolute_path(prefix)) {\n+\t\t\tif (!strbuf_realpath_missing(&prefixbuf, prefix))\n+\t\t\t\tdie(_(\"Unable to resolve path '%s'\"), path);\n+\t\t\tprefix = prefixbuf.buf;\n+\t\t}\n+\t\tputs(relative_path(path, prefix, &buf));\n+\t\tstrbuf_release(&buf);\n+\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tputs(relative_path(path, prefix, &buf));\n+\t\tstrbuf_release(&buf);\n+\t} else {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tif (!strbuf_realpath_missing(&buf, path))\n+\t\t\tdie(_(\"Unable to resolve path '%s'\"), path);\n+\t\tputs(buf.buf);\n+\t\tstrbuf_release(&buf);\n+\t}\n+\tfree(cwd);\n+}\n+\n int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n {\n \tint i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;\n@@ -595,6 +665,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \tstruct object_context unused;\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst int hexsz = the_hash_algo->hexsz;\n+\tenum format_type format = FORMAT_DEFAULT;\n \n \tif (argc > 1 && !strcmp(\"--parseopt\", argv[1]))\n \t\treturn cmd_parseopt(argc - 1, argv + 1, prefix);\n@@ -650,8 +721,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t\tif (!argv[i + 1])\n \t\t\t\tdie(\"--git-path requires an argument\");\n \t\t\tstrbuf_reset(&buf);\n-\t\t\tputs(relative_path(git_path(\"%s\", argv[i + 1]),\n-\t\t\t\t\t   prefix, &buf));\n+\t\t\tprint_path(git_path(\"%s\", argv[i + 1]), prefix, format, DEFAULT_RELATIVE_IF_SHARED);\n \t\t\ti++;\n \t\t\tcontinue;\n \t\t}\n@@ -683,6 +753,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t\t\t\tshow_file(arg, 0);\n \t\t\t\tcontinue;\n \t\t\t}\n+\t\t\tif (opt_with_value(arg, \"--path-format\", &arg)) {\n+\t\t\t\tif (!strcmp(arg, \"absolute\")) {\n+\t\t\t\t\tformat = FORMAT_CANONICAL;\n+\t\t\t\t} else if (!strcmp(arg, \"relative\")) {\n+\t\t\t\t\tformat = FORMAT_RELATIVE;\n+\t\t\t\t} else {\n+\t\t\t\t\tdie(\"unknown argument to --path-format: %s\", arg);\n+\t\t\t\t}\n+\t\t\t\tcontinue;\n+\t\t\t}\n \t\t\tif (!strcmp(arg, \"--default\")) {\n \t\t\t\tdef = argv[++i];\n \t\t\t\tif (!def)\n@@ -803,7 +883,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t\tif (!strcmp(arg, \"--show-toplevel\")) {\n \t\t\t\tconst char *work_tree = get_git_work_tree();\n \t\t\t\tif (work_tree)\n-\t\t\t\t\tputs(work_tree);\n+\t\t\t\t\tprint_path(work_tree, prefix, format, DEFAULT_CANONICAL);\n \t\t\t\telse\n \t\t\t\t\tdie(\"this operation must be run in a work tree\");\n \t\t\t\tcontinue;\n@@ -811,7 +891,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t\tif (!strcmp(arg, \"--show-superproject-working-tree\")) {\n \t\t\t\tstruct strbuf superproject = STRBUF_INIT;\n \t\t\t\tif (get_superproject_working_tree(&superproject))\n-\t\t\t\t\tputs(superproject.buf);\n+\t\t\t\t\tprint_path(superproject.buf, prefix, format, DEFAULT_CANONICAL);\n \t\t\t\tstrbuf_release(&superproject);\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -846,16 +926,18 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n \t\t\t\tchar *cwd;\n \t\t\t\tint len;\n+\t\t\t\tenum format_type wanted = format;\n \t\t\t\tif (arg[2] == 'g') {\t/* --git-dir */\n \t\t\t\t\tif (gitdir) {\n-\t\t\t\t\t\tputs(gitdir);\n+\t\t\t\t\t\tprint_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\t}\n \t\t\t\t\tif (!prefix) {\n-\t\t\t\t\t\tputs(\".git\");\n+\t\t\t\t\t\tprint_path(\".git\", prefix, format, DEFAULT_UNMODIFIED);\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\t}\n \t\t\t\t} else {\t\t/* --absolute-git-dir */\n+\t\t\t\t\twanted = FORMAT_CANONICAL;\n \t\t\t\t\tif (!gitdir && !prefix)\n \t\t\t\t\t\tgitdir = \".git\";\n \t\t\t\t\tif (gitdir) {\n@@ -868,14 +950,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t\t\t}\n \t\t\t\tcwd = xgetcwd();\n \t\t\t\tlen = strlen(cwd);\n-\t\t\t\tprintf(\"%s%s.git\\n\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n+\t\t\t\tstrbuf_reset(&buf);\n+\t\t\t\tstrbuf_addf(&buf, \"%s%s.git\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n \t\t\t\tfree(cwd);\n+\t\t\t\tprint_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--git-common-dir\")) {\n-\t\t\t\tstrbuf_reset(&buf);\n-\t\t\t\tputs(relative_path(get_git_common_dir(),\n-\t\t\t\t\t\t   prefix, &buf));\n+\t\t\t\tprint_path(get_git_common_dir(), prefix, format, DEFAULT_RELATIVE_IF_SHARED);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--is-inside-git-dir\")) {\n@@ -905,8 +987,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t\t\tif (the_index.split_index) {\n \t\t\t\t\tconst struct object_id *oid = &the_index.split_index->base_oid;\n \t\t\t\t\tconst char *path = git_path(\"sharedindex.%s\", oid_to_hex(oid));\n-\t\t\t\t\tstrbuf_reset(&buf);\n-\t\t\t\t\tputs(relative_path(path, prefix, &buf));\n+\t\t\t\t\tprint_path(path, prefix, format, DEFAULT_RELATIVE);\n \t\t\t\t}\n \t\t\t\tcontinue;\n \t\t\t}\ndiff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh\nindex 408b97d5af..51d7d40ec1 100755\n--- a/t/t1500-rev-parse.sh\n+++ b/t/t1500-rev-parse.sh\n@@ -3,6 +3,16 @@\n test_description='test git rev-parse'\n . ./test-lib.sh\n \n+test_one () {\n+\tdir=\"$1\" &&\n+\texpect=\"$2\" &&\n+\tshift &&\n+\tshift &&\n+\techo \"$expect\" >expect &&\n+\tgit -C \"$dir\" rev-parse \"$@\" >actual &&\n+\ttest_cmp expect actual\n+}\n+\n # usage: [options] label is-bare is-inside-git is-inside-work prefix git-dir absolute-git-dir\n test_rev_parse () {\n \td=\n@@ -60,7 +70,13 @@ ROOT=$(pwd)\n \n test_expect_success 'setup' '\n \tmkdir -p sub/dir work &&\n-\tcp -R .git repo.git\n+\tcp -R .git repo.git &&\n+\tgit checkout -B main &&\n+\ttest_commit abc &&\n+\tgit checkout -b side &&\n+\ttest_commit def &&\n+\tgit checkout main &&\n+\tgit worktree add worktree side\n '\n \n test_rev_parse toplevel false false true '' .git \"$ROOT/.git\"\n@@ -88,6 +104,45 @@ test_rev_parse -C work -g ../repo.git -b t 'GIT_DIR=../repo.git, core.bare = tru\n \n test_rev_parse -C work -g ../repo.git -b u 'GIT_DIR=../repo.git, core.bare undefined' false false true ''\n \n+test_expect_success 'rev-parse --path-format=absolute' '\n+\ttest_one \".\" \"$ROOT/.git\" --path-format=absolute --git-dir &&\n+\ttest_one \".\" \"$ROOT/.git\" --path-format=absolute --git-common-dir &&\n+\ttest_one \"sub/dir\" \"$ROOT/.git\" --path-format=absolute --git-dir &&\n+\ttest_one \"sub/dir\" \"$ROOT/.git\" --path-format=absolute --git-common-dir &&\n+\ttest_one \"worktree\" \"$ROOT/.git/worktrees/worktree\" --path-format=absolute --git-dir &&\n+\ttest_one \"worktree\" \"$ROOT/.git\" --path-format=absolute --git-common-dir &&\n+\ttest_one \".\" \"$ROOT\" --path-format=absolute --show-toplevel &&\n+\ttest_one \".\" \"$ROOT/.git/objects\" --path-format=absolute --git-path objects &&\n+\ttest_one \".\" \"$ROOT/.git/objects/foo/bar/baz\" --path-format=absolute --git-path objects/foo/bar/baz\n+'\n+\n+test_expect_success 'rev-parse --path-format=relative' '\n+\ttest_one \".\" \".git\" --path-format=relative --git-dir &&\n+\ttest_one \".\" \".git\" --path-format=relative --git-common-dir &&\n+\ttest_one \"sub/dir\" \"../../.git\" --path-format=relative --git-dir &&\n+\ttest_one \"sub/dir\" \"../../.git\" --path-format=relative --git-common-dir &&\n+\ttest_one \"worktree\" \"../.git/worktrees/worktree\" --path-format=relative --git-dir &&\n+\ttest_one \"worktree\" \"../.git\" --path-format=relative --git-common-dir &&\n+\ttest_one \".\" \"./\" --path-format=relative --show-toplevel &&\n+\ttest_one \".\" \".git/objects\" --path-format=relative --git-path objects &&\n+\ttest_one \".\" \".git/objects/foo/bar/baz\" --path-format=relative --git-path objects/foo/bar/baz\n+'\n+\n+test_expect_success '--path-format=relative does not affect --absolute-git-dir' '\n+\tgit rev-parse --path-format=relative --absolute-git-dir >actual &&\n+\techo \"$ROOT/.git\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--path-format can change in the middle of the command line' '\n+\tgit rev-parse --path-format=absolute --git-dir --path-format=relative --git-path objects/foo/bar >actual &&\n+\tcat >expect <<-EOF &&\n+\t$ROOT/.git\n+\t.git/objects/foo/bar\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git-common-dir from worktree root' '\n \techo .git >expect &&\n \tgit rev-parse --git-common-dir >actual &&\n"},{"id":"407237","messageId":"20201009191511.267461-1-sandals@crustytoothpaste.net","threadId":"54382","inReplyTo":null,"subject":"[PATCH v2 0/2] rev-parse options for absolute or relative paths","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-10-09T19:15:09Z","receivedAt":"2020-10-09T19:15:34Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"There are a bunch of different situations in which one would like to\nhave an absolute and canonical or a relative path from Git.  In many of\nthese cases, these values are already available from git rev-parse, but\nsome values only come in one form or another.\n\nMany operating systems, such as macOS, lack a built-in realpath command\nthat can canonicalize paths properly, and additionally some programming\nlanguages, like Go, currently do as well.  It's therefore helpful for us\nto provide a generic way to request that a path is fully canonicalized\nbefore using it.  Since users may wish for a relative path, we can\nprovide one of those as well.\n\nChanges from v1:\n\n* Add a function to handle missing trailing components when\n  canonicalizing paths and use it.\n* Improve commit messages.\n* Fix broken && chain.\n* Fix situation where relative paths are not relative.\n\nbrian m. carlson (2):\n  abspath: add a function to resolve paths with missing components\n  rev-parse: add option for absolute or relative path formatting\n\n Documentation/git-rev-parse.txt |  71 ++++++++++++---------\n abspath.c                       |  50 +++++++++++++--\n builtin/rev-parse.c             | 105 ++++++++++++++++++++++++++++----\n cache.h                         |   1 +\n t/t1500-rev-parse.sh            |  57 ++++++++++++++++-\n 5 files changed, 237 insertions(+), 47 deletions(-)\n\n"},{"id":"407239","messageId":"20201009191511.267461-2-sandals@crustytoothpaste.net","threadId":"54382","inReplyTo":"20201009191511.267461-1-sandals@crustytoothpaste.net","subject":"[PATCH v2 1/2] abspath: add a function to resolve paths with missing components","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-10-09T19:15:10Z","receivedAt":"2020-10-09T19:16:00Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"We'd like to canonicalize paths such that we can preserve any number of\ntrailing components that may be missing.  Let's add a function to do\nthat that calls strbuf_realpath to find the canonical path for the\nportion we do have and then append the missing part.  We adjust\nstrip_last_component to return us the component it has stripped and use\nthat to help us accumulate the missing part.\n\nNote that it is intentional that we invoke strbuf_realpath here,\nrepeatedly if necessary, because on Windows that function is replaced\nwith a version that uses the proper system semantics for\ncanonicalization.  Trying to adjust strbuf_realpath to perform this kind\nof canonicalization with an additional option would fail to work\nproperly on Windows.  The present approach is equivalent to\nstrbuf_realpath for cases where the path exists, and the only other\ncases where we will use this function the additional overhead of\nmultiple invocations is not significant.\n\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\n abspath.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++----\n cache.h   |  1 +\n 2 files changed, 47 insertions(+), 4 deletions(-)\n\ndiff --git a/abspath.c b/abspath.c\nindex 6f15a418bb..092bb33b64 100644\n--- a/abspath.c\n+++ b/abspath.c\n@@ -11,8 +11,12 @@ int is_directory(const char *path)\n \treturn (!stat(path, &st) && S_ISDIR(st.st_mode));\n }\n \n-/* removes the last path component from 'path' except if 'path' is root */\n-static void strip_last_component(struct strbuf *path)\n+/*\n+ * Removes the last path component from 'path' except if 'path' is root.\n+ *\n+ * If last is not NULL, the last path component is copied to last.\n+ */\n+static void strip_last_component(struct strbuf *path, struct strbuf *last)\n {\n \tsize_t offset = offset_1st_component(path->buf);\n \tsize_t len = path->len;\n@@ -20,6 +24,10 @@ static void strip_last_component(struct strbuf *path)\n \t/* Find start of the last component */\n \twhile (offset < len && !is_dir_sep(path->buf[len - 1]))\n \t\tlen--;\n+\n+\tif (last)\n+\t\tstrbuf_addstr(last, path->buf + len);\n+\n \t/* Skip sequences of multiple path-separators */\n \twhile (offset < len && is_dir_sep(path->buf[len - 1]))\n \t\tlen--;\n@@ -118,7 +126,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,\n \t\t\tcontinue; /* '.' component */\n \t\t} else if (next.len == 2 && !strcmp(next.buf, \"..\")) {\n \t\t\t/* '..' component; strip the last path component */\n-\t\t\tstrip_last_component(resolved);\n+\t\t\tstrip_last_component(resolved, NULL);\n \t\t\tcontinue;\n \t\t}\n \n@@ -169,7 +177,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,\n \t\t\t\t * strip off the last component since it will\n \t\t\t\t * be replaced with the contents of the symlink\n \t\t\t\t */\n-\t\t\t\tstrip_last_component(resolved);\n+\t\t\t\tstrip_last_component(resolved, NULL);\n \t\t\t}\n \n \t\t\t/*\n@@ -202,6 +210,40 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,\n \treturn retval;\n }\n \n+/*\n+ * Like strbuf_realpath, but trailing components which do not exist are copied\n+ * through.\n+ */\n+char *strbuf_realpath_missing(struct strbuf *resolved, const char *path)\n+{\n+\tstruct strbuf remaining = STRBUF_INIT;\n+\tstruct strbuf trailing = STRBUF_INIT;\n+\tstruct strbuf component = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&remaining, path);\n+\n+\twhile (remaining.len) {\n+\t\tif (strbuf_realpath(resolved, remaining.buf, 0)) {\n+\t\t\tstrbuf_addbuf(resolved, &trailing);\n+\n+\t\t\tstrbuf_release(&component);\n+\t\t\tstrbuf_release(&remaining);\n+\t\t\tstrbuf_release(&trailing);\n+\n+\t\t\treturn resolved->buf;\n+\t\t}\n+\t\tstrip_last_component(&remaining, &component);\n+\t\tstrbuf_insertstr(&trailing, 0, \"/\");\n+\t\tstrbuf_insertstr(&trailing, 1, component.buf);\n+\t\tstrbuf_reset(&component);\n+\t}\n+\n+\tstrbuf_release(&component);\n+\tstrbuf_release(&remaining);\n+\tstrbuf_release(&trailing);\n+\treturn NULL;\n+}\n+\n char *real_pathdup(const char *path, int die_on_error)\n {\n \tstruct strbuf realpath = STRBUF_INIT;\ndiff --git a/cache.h b/cache.h\nindex c0072d43b1..e1e17e108e 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1320,6 +1320,7 @@ static inline int is_absolute_path(const char *path)\n int is_directory(const char *);\n char *strbuf_realpath(struct strbuf *resolved, const char *path,\n \t\t      int die_on_error);\n+char *strbuf_realpath_missing(struct strbuf *resolved, const char *path);\n char *real_pathdup(const char *path, int die_on_error);\n const char *absolute_path(const char *path);\n char *absolute_pathdup(const char *path);\n"},{"id":"407249","messageId":"xmqqk0vzrtqr.fsf@gitster.c.googlers.com","threadId":"54382","inReplyTo":"20201009191511.267461-2-sandals@crustytoothpaste.net","subject":"Re: [PATCH v2 1/2] abspath: add a function to resolve paths with missing components","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-09T21:10:04Z","receivedAt":"2020-10-09T21:10:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> We'd like to canonicalize paths such that we can preserve any number of\n> trailing components that may be missing.\n\nSorry, but at least to me, the above gives no clue what kind of\noperation is desired to be done on paths.  How would one preserve\nwhat does not exist (i.e. are missing)?\n\nDo you mean some leading components in a path point at existing\ndirectories and after some point a component names a directory\nthat does not exist, so everything after that does not yet exist\nuntil you \"mkdir -p\" them?\n\nI guess my confusion comes primarily from the fuzziness of the verb\n\"canonicalize\" in the sentence.  We want to handle a/b/../c/d and\nthere are various combinations of missng and existing directories,\ne.g. a/b may not exist or a/b may but a/c may not, etc.  Is that\nwhat is going on?  Makes me wonder if it makes sense to canonicalize\na/b/../c/d into a/c/d when a/b does not exist in the first place,\nthough.\n\n> Let's add a function to do\n> that that calls strbuf_realpath to find the canonical path for the\n> portion we do have and then append the missing part.  We adjust\n> strip_last_component to return us the component it has stripped and use\n> that to help us accumulate the missing part.\n\nOK, so if we have a/b/c/d and know a/b/c/d does not exist on the\nfilesystem, we start by splitting it to a/b/c and d, see if a/b/c\nexists, and if not, do the same recursively to a/b/c to split it\ninto a/b and c, and prefix the latter to 'd' that we split earlier\n(i.e. now we have a/b and c/d), until we have an existing directory\non the first half?\n\n> Note that it is intentional that we invoke strbuf_realpath here,\n> repeatedly if necessary, because on Windows that function is replaced\n> with a version that uses the proper system semantics for\n> canonicalization.  Trying to adjust strbuf_realpath to perform this kind\n> of canonicalization with an additional option would fail to work\n> properly on Windows.  The present approach is equivalent to\n> strbuf_realpath for cases where the path exists, and the only other\n> cases where we will use this function the additional overhead of\n> multiple invocations is not significant.\n>\n> Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n> ---\n>  abspath.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++----\n>  cache.h   |  1 +\n>  2 files changed, 47 insertions(+), 4 deletions(-)\n>\n> diff --git a/abspath.c b/abspath.c\n> index 6f15a418bb..092bb33b64 100644\n> --- a/abspath.c\n> +++ b/abspath.c\n> @@ -11,8 +11,12 @@ int is_directory(const char *path)\n>  \treturn (!stat(path, &st) && S_ISDIR(st.st_mode));\n>  }\n>  \n> -/* removes the last path component from 'path' except if 'path' is root */\n> -static void strip_last_component(struct strbuf *path)\n> +/*\n> + * Removes the last path component from 'path' except if 'path' is root.\n> + *\n> + * If last is not NULL, the last path component is copied to last.\n> + */\n> +static void strip_last_component(struct strbuf *path, struct strbuf *last)\n>  {\n>  \tsize_t offset = offset_1st_component(path->buf);\n>  \tsize_t len = path->len;\n> @@ -20,6 +24,10 @@ static void strip_last_component(struct strbuf *path)\n>  \t/* Find start of the last component */\n>  \twhile (offset < len && !is_dir_sep(path->buf[len - 1]))\n>  \t\tlen--;\n> +\n> +\tif (last)\n> +\t\tstrbuf_addstr(last, path->buf + len);\n> +\n>  \t/* Skip sequences of multiple path-separators */\n>  \twhile (offset < len && is_dir_sep(path->buf[len - 1]))\n>  \t\tlen--;\n> @@ -118,7 +126,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,\n>  \t\t\tcontinue; /* '.' component */\n>  \t\t} else if (next.len == 2 && !strcmp(next.buf, \"..\")) {\n>  \t\t\t/* '..' component; strip the last path component */\n> -\t\t\tstrip_last_component(resolved);\n> +\t\t\tstrip_last_component(resolved, NULL);\n>  \t\t\tcontinue;\n>  \t\t}\n>  \n> @@ -169,7 +177,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,\n>  \t\t\t\t * strip off the last component since it will\n>  \t\t\t\t * be replaced with the contents of the symlink\n>  \t\t\t\t */\n> -\t\t\t\tstrip_last_component(resolved);\n> +\t\t\t\tstrip_last_component(resolved, NULL);\n>  \t\t\t}\n>  \n>  \t\t\t/*\n> @@ -202,6 +210,40 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,\n>  \treturn retval;\n>  }\n>  \n> +/*\n> + * Like strbuf_realpath, but trailing components which do not exist are copied\n> + * through.\n> + */\n> +char *strbuf_realpath_missing(struct strbuf *resolved, const char *path)\n> +{\n> +\tstruct strbuf remaining = STRBUF_INIT;\n> +\tstruct strbuf trailing = STRBUF_INIT;\n> +\tstruct strbuf component = STRBUF_INIT;\n> +\n> +\tstrbuf_addstr(&remaining, path);\n> +\n> +\twhile (remaining.len) {\n> +\t\tif (strbuf_realpath(resolved, remaining.buf, 0)) {\n> +\t\t\tstrbuf_addbuf(resolved, &trailing);\n> +\n> +\t\t\tstrbuf_release(&component);\n> +\t\t\tstrbuf_release(&remaining);\n> +\t\t\tstrbuf_release(&trailing);\n> +\n> +\t\t\treturn resolved->buf;\n> +\t\t}\n> +\t\tstrip_last_component(&remaining, &component);\n> +\t\tstrbuf_insertstr(&trailing, 0, \"/\");\n> +\t\tstrbuf_insertstr(&trailing, 1, component.buf);\n\nI may be utterly confused, but is this where\n\n    - we started with a/b/c/d, pushed 'd' into trailing and decided\n      to redo with a/b/c\n\n    - now we split the a/b/c into a/b and c, and adjusting what is\n      in trailing from 'd' to 'c/d'\n\nhappens place?  It's a bit sad that we need to repeatedly use\ninsertstr to prepend in front, instead of appending.\n\n> +\t\tstrbuf_reset(&component);\n> +\t}\n> +\n> +\tstrbuf_release(&component);\n> +\tstrbuf_release(&remaining);\n> +\tstrbuf_release(&trailing);\n> +\treturn NULL;\n> +}\n> +\n>  char *real_pathdup(const char *path, int die_on_error)\n>  {\n>  \tstruct strbuf realpath = STRBUF_INIT;\n> diff --git a/cache.h b/cache.h\n> index c0072d43b1..e1e17e108e 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -1320,6 +1320,7 @@ static inline int is_absolute_path(const char *path)\n>  int is_directory(const char *);\n>  char *strbuf_realpath(struct strbuf *resolved, const char *path,\n>  \t\t      int die_on_error);\n> +char *strbuf_realpath_missing(struct strbuf *resolved, const char *path);\n>  char *real_pathdup(const char *path, int die_on_error);\n>  const char *absolute_path(const char *path);\n>  char *absolute_pathdup(const char *path);\n"},{"id":"407257","messageId":"20201010011048.GQ1392312@camp.crustytoothpaste.net","threadId":"54382","inReplyTo":"xmqqk0vzrtqr.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2 1/2] abspath: add a function to resolve paths with missing components","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-10-10T01:10:48Z","receivedAt":"2020-10-10T01:42:53Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2020-10-09 at 21:10:04, Junio C Hamano wrote:\n> \"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n> \n> > We'd like to canonicalize paths such that we can preserve any number of\n> > trailing components that may be missing.\n> \n> Sorry, but at least to me, the above gives no clue what kind of\n> operation is desired to be done on paths.  How would one preserve\n> what does not exist (i.e. are missing)?\n> \n> Do you mean some leading components in a path point at existing\n> directories and after some point a component names a directory\n> that does not exist, so everything after that does not yet exist\n> until you \"mkdir -p\" them?\n> \n> I guess my confusion comes primarily from the fuzziness of the verb\n> \"canonicalize\" in the sentence.  We want to handle a/b/../c/d and\n> there are various combinations of missng and existing directories,\n> e.g. a/b may not exist or a/b may but a/c may not, etc.  Is that\n> what is going on?  Makes me wonder if it makes sense to canonicalize\n> a/b/../c/d into a/c/d when a/b does not exist in the first place,\n> though.\n\nThe behavior that I'm proposing is the realpath -m behavior.  If the\npath we're canonicalizing doesn't exist, we find the closest parent that\ndoes exist, canonicalize it (à la realpath(3)), and then append the\ncomponents that don't exist to the canonicalized portion.\n\n> > Let's add a function to do\n> > that that calls strbuf_realpath to find the canonical path for the\n> > portion we do have and then append the missing part.  We adjust\n> > strip_last_component to return us the component it has stripped and use\n> > that to help us accumulate the missing part.\n> \n> OK, so if we have a/b/c/d and know a/b/c/d does not exist on the\n> filesystem, we start by splitting it to a/b/c and d, see if a/b/c\n> exists, and if not, do the same recursively to a/b/c to split it\n> into a/b and c, and prefix the latter to 'd' that we split earlier\n> (i.e. now we have a/b and c/d), until we have an existing directory\n> on the first half?\n\nCorrect.\n\n> > +/*\n> > + * Like strbuf_realpath, but trailing components which do not exist are copied\n> > + * through.\n> > + */\n> > +char *strbuf_realpath_missing(struct strbuf *resolved, const char *path)\n> > +{\n> > +\tstruct strbuf remaining = STRBUF_INIT;\n> > +\tstruct strbuf trailing = STRBUF_INIT;\n> > +\tstruct strbuf component = STRBUF_INIT;\n> > +\n> > +\tstrbuf_addstr(&remaining, path);\n> > +\n> > +\twhile (remaining.len) {\n> > +\t\tif (strbuf_realpath(resolved, remaining.buf, 0)) {\n> > +\t\t\tstrbuf_addbuf(resolved, &trailing);\n> > +\n> > +\t\t\tstrbuf_release(&component);\n> > +\t\t\tstrbuf_release(&remaining);\n> > +\t\t\tstrbuf_release(&trailing);\n> > +\n> > +\t\t\treturn resolved->buf;\n> > +\t\t}\n> > +\t\tstrip_last_component(&remaining, &component);\n> > +\t\tstrbuf_insertstr(&trailing, 0, \"/\");\n> > +\t\tstrbuf_insertstr(&trailing, 1, component.buf);\n> \n> I may be utterly confused, but is this where\n> \n>     - we started with a/b/c/d, pushed 'd' into trailing and decided\n>       to redo with a/b/c\n> \n>     - now we split the a/b/c into a/b and c, and adjusting what is\n>       in trailing from 'd' to 'c/d'\n> \n> happens place?  It's a bit sad that we need to repeatedly use\n> insertstr to prepend in front, instead of appending.\n\nYes, that's true.  It really isn't avoidable, though, with the functions\nthe way that they are.  We can't use the original path and keep track of\nthe offset because it may contain multiple path separators and we don't\nwant to include those in the path.\n-- \nbrian m. carlson: Houston, Texas, US\n"},{"id":"409176","messageId":"20201104230157.GH2774782@google.com","threadId":"54382","inReplyTo":"20201009191511.267461-1-sandals@crustytoothpaste.net","subject":"Re: [PATCH v2 0/2] rev-parse options for absolute or relative paths","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-11-04T23:01:57Z","receivedAt":"2020-11-04T23:02:07Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"Hiya, we took a look in the review club, but I'll try to keep it simple.\n\nOn Fri, Oct 09, 2020 at 07:15:09PM +0000, brian m. carlson wrote:\n> \n> There are a bunch of different situations in which one would like to\n> have an absolute and canonical or a relative path from Git.\n\nI think specifically you are interested in this situation:\nhttps://github.com/git-lfs/git-lfs/issues/4012\n\nI think this would have been useful to see in the cover letter :) There\nwas a lot of \"but why\" during the review club meeting.\n\nLooking at the git-lfs issue in question, the problem seems to be \"how\nto make sure the path Git gives me will match the path Go gives me\" -\nand like you mentioned in the issue, there are two ways to fix it.\n\nAlso, there was some brief wondering: if this option is useful in 'git\nrev-parse', would it be useful in other commands too? I thought maybe it\nwould be more useful as an arg to 'git' itself, but on inspection, not\nreally - because you wouldn't be able to switch the format in the middle\nof a bunch of args, like you can now. The way it's written here,\nthough, should mean a smooth transition to something like OPT__VERBOSE\nor OPT__QUIET if we discover lots of folks would find use.\n\nThanks for sending it. I'll try and do a detailed review of the patches\nthemselves. To me, the concept seems sound.\n\n - Emily\n"},{"id":"409195","messageId":"20201105032040.GB1332931@camp.crustytoothpaste.net","threadId":"54382","inReplyTo":"20201104230157.GH2774782@google.com","subject":"Re: [PATCH v2 0/2] rev-parse options for absolute or relative paths","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-11-05T03:20:40Z","receivedAt":"2020-11-05T03:20:48Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2020-11-04 at 23:01:57, Emily Shaffer wrote:\n> Hiya, we took a look in the review club, but I'll try to keep it simple.\n> \n> On Fri, Oct 09, 2020 at 07:15:09PM +0000, brian m. carlson wrote:\n> > \n> > There are a bunch of different situations in which one would like to\n> > have an absolute and canonical or a relative path from Git.\n> \n> I think specifically you are interested in this situation:\n> https://github.com/git-lfs/git-lfs/issues/4012\n\nYes, that's the case I'm interested in.\n\n> I think this would have been useful to see in the cover letter :) There\n> was a lot of \"but why\" during the review club meeting.\n\nSorry about that.  When I'm sending patches to Git that benefit Git LFS,\nI want to be careful to build features that are generally applicable and\ndon't just try to shove in features that benefit the project I maintain,\nso I'm often a little hesitant to mention my particular use case and try\nto let the patch stand on its own.  I know that there are several\ncompeting projects in this space, and I want to be sensitive to not\nprivileging my own just because I'm a frequent contributor.\n\nI'll try to strike a better balance here in the future.\n\n> Also, there was some brief wondering: if this option is useful in 'git\n> rev-parse', would it be useful in other commands too? I thought maybe it\n> would be more useful as an arg to 'git' itself, but on inspection, not\n> really - because you wouldn't be able to switch the format in the middle\n> of a bunch of args, like you can now. The way it's written here,\n> though, should mean a smooth transition to something like OPT__VERBOSE\n> or OPT__QUIET if we discover lots of folks would find use.\n\nYeah, I think this could be a thing we add in the future, but I'm\ngenerally comfortable with this being just in rev-parse, since that's\nthe command that folks tend to use when scripting, but we'll see if\nother folks think differently.\n-- \nbrian m. carlson (he/him or they/them)\nHouston, Texas, US\n"},{"id":"409392","messageId":"nycvar.QRO.7.76.6.2011091432380.18437@tvgsbejvaqbjf.bet","threadId":"54382","inReplyTo":"20201009191511.267461-1-sandals@crustytoothpaste.net","subject":"Re: [PATCH v2 0/2] rev-parse options for absolute or relative paths","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-11-09T13:33:34Z","receivedAt":"2020-11-09T13:33:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi brian,\n\nOn Fri, 9 Oct 2020, brian m. carlson wrote:\n\n> There are a bunch of different situations in which one would like to\n> have an absolute and canonical or a relative path from Git.  In many of\n> these cases, these values are already available from git rev-parse, but\n> some values only come in one form or another.\n>\n> Many operating systems, such as macOS, lack a built-in realpath command\n> that can canonicalize paths properly, and additionally some programming\n> languages, like Go, currently do as well.  It's therefore helpful for us\n> to provide a generic way to request that a path is fully canonicalized\n> before using it.  Since users may wish for a relative path, we can\n> provide one of those as well.\n\nI am very much in favor of this patch series' goal. Windows also does not\n_really_ have anything in the way of `realpath`. Besides, it would be good\nto have a way to ask _Git_ what it's idea of the real path is (no\nguessing!).\n\nThanks,\nDscho\n"},{"id":"409393","messageId":"nycvar.QRO.7.76.6.2011091442190.18437@tvgsbejvaqbjf.bet","threadId":"54382","inReplyTo":"20201009191511.267461-2-sandals@crustytoothpaste.net","subject":"Re: [PATCH v2 1/2] abspath: add a function to resolve paths with missing components","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-11-09T13:55:53Z","receivedAt":"2020-11-09T13:55:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi brian,\n\nOn Fri, 9 Oct 2020, brian m. carlson wrote:\n\n> We'd like to canonicalize paths such that we can preserve any number of\n> trailing components that may be missing.  Let's add a function to do\n> that that calls strbuf_realpath to find the canonical path for the\n> portion we do have and then append the missing part.  We adjust\n> strip_last_component to return us the component it has stripped and use\n> that to help us accumulate the missing part.\n>\n> Note that it is intentional that we invoke strbuf_realpath here,\n> repeatedly if necessary, because on Windows that function is replaced\n> with a version that uses the proper system semantics for\n> canonicalization.  Trying to adjust strbuf_realpath to perform this kind\n> of canonicalization with an additional option would fail to work\n> properly on Windows.  The present approach is equivalent to\n> strbuf_realpath for cases where the path exists, and the only other\n> cases where we will use this function the additional overhead of\n> multiple invocations is not significant.\n\nThank you for being so considerate. Yes, on Windows, we use (wherever\npossible) a shortcut that tells us the canonicalized path of existing\nentries.\n\nTechnically, it is not `strbuf_realpath()` that we override, but we take a\nshortcut _in_ that function. That's semantics, though.\n\nMore importantly, we recently fixed a bug in our code to allow for a quirk\nin the `strbuf_realpath()` function: `strbuf_realpath()` allows the last\npath component to not exist. If that is the case, now it's time to try\nwithout last component.\n\nIn a sense, this is a 1-level version of your infinite-level\n`strbuf_realpath_missing()` function.\n\nAn idea that immediately crosses my mind is whether that level could be\nsomething we want to pass directly into `strbuf_realpath()` as a parameter\n(it would be 1 to imitate the current behavior and -1 for the\ninfinite-level case). What do you think? Does that make sense?\n\nIn any case, I think this `_missing()` functionality should be implemented\na bit more tightly with the `strbuf_realpath()` function because of the\nlogic that already allows the last component to be missing:\n\n                if (lstat(resolved->buf, &st)) {\n                        /* error out unless this was the last component */\n                        if (errno != ENOENT || remaining.len) {\n                                if (die_on_error)\n                                        die_errno(\"Invalid path '%s'\",\n                                                  resolved->buf);\n                                else\n                                        goto error_out;\n                        }\n\nSee https://github.com/git/git/blob/v2.29.2/abspath.c#L130-L138 for the\nexact code and context.\n\nSeeing as we _already_ have some code to allow for _some_ missing\ncomponent, it should be possible to extend the logic to allow for\ndifferent levels (e.g. using `count_slashes()` if we want to allow more\nthan just the last component to be missing).\n\n> Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n> ---\n>  abspath.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++----\n>  cache.h   |  1 +\n>  2 files changed, 47 insertions(+), 4 deletions(-)\n>\n> diff --git a/abspath.c b/abspath.c\n> index 6f15a418bb..092bb33b64 100644\n> --- a/abspath.c\n> +++ b/abspath.c\n> @@ -11,8 +11,12 @@ int is_directory(const char *path)\n>  \treturn (!stat(path, &st) && S_ISDIR(st.st_mode));\n>  }\n>\n> -/* removes the last path component from 'path' except if 'path' is root */\n> -static void strip_last_component(struct strbuf *path)\n> +/*\n> + * Removes the last path component from 'path' except if 'path' is root.\n> + *\n> + * If last is not NULL, the last path component is copied to last.\n> + */\n> +static void strip_last_component(struct strbuf *path, struct strbuf *last)\n>  {\n>  \tsize_t offset = offset_1st_component(path->buf);\n>  \tsize_t len = path->len;\n> @@ -20,6 +24,10 @@ static void strip_last_component(struct strbuf *path)\n>  \t/* Find start of the last component */\n>  \twhile (offset < len && !is_dir_sep(path->buf[len - 1]))\n>  \t\tlen--;\n> +\n> +\tif (last)\n> +\t\tstrbuf_addstr(last, path->buf + len);\n> +\n>  \t/* Skip sequences of multiple path-separators */\n>  \twhile (offset < len && is_dir_sep(path->buf[len - 1]))\n>  \t\tlen--;\n> @@ -118,7 +126,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,\n>  \t\t\tcontinue; /* '.' component */\n>  \t\t} else if (next.len == 2 && !strcmp(next.buf, \"..\")) {\n>  \t\t\t/* '..' component; strip the last path component */\n> -\t\t\tstrip_last_component(resolved);\n> +\t\t\tstrip_last_component(resolved, NULL);\n>  \t\t\tcontinue;\n>  \t\t}\n>\n> @@ -169,7 +177,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,\n>  \t\t\t\t * strip off the last component since it will\n>  \t\t\t\t * be replaced with the contents of the symlink\n>  \t\t\t\t */\n> -\t\t\t\tstrip_last_component(resolved);\n> +\t\t\t\tstrip_last_component(resolved, NULL);\n>  \t\t\t}\n>\n>  \t\t\t/*\n> @@ -202,6 +210,40 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,\n>  \treturn retval;\n>  }\n>\n> +/*\n> + * Like strbuf_realpath, but trailing components which do not exist are copied\n> + * through.\n> + */\n> +char *strbuf_realpath_missing(struct strbuf *resolved, const char *path)\n> +{\n> +\tstruct strbuf remaining = STRBUF_INIT;\n> +\tstruct strbuf trailing = STRBUF_INIT;\n> +\tstruct strbuf component = STRBUF_INIT;\n> +\n> +\tstrbuf_addstr(&remaining, path);\n> +\n> +\twhile (remaining.len) {\n> +\t\tif (strbuf_realpath(resolved, remaining.buf, 0)) {\n> +\t\t\tstrbuf_addbuf(resolved, &trailing);\n> +\n> +\t\t\tstrbuf_release(&component);\n> +\t\t\tstrbuf_release(&remaining);\n> +\t\t\tstrbuf_release(&trailing);\n> +\n> +\t\t\treturn resolved->buf;\n> +\t\t}\n> +\t\tstrip_last_component(&remaining, &component);\n> +\t\tstrbuf_insertstr(&trailing, 0, \"/\");\n> +\t\tstrbuf_insertstr(&trailing, 1, component.buf);\n\nNot that it matters a lot, but this could be written shorter via\n\n\t\tstrbuf_insertf(&trailing, \"/%s\", component.buf);\n\nBut as I said above, I think we should be able to fold the logic _into_\n`strbuf_realpath()` (even if this makes my job harder to maintain the\nWindows-specific shortcut).\n\nThanks,\nDscho\n\n> +\t\tstrbuf_reset(&component);\n> +\t}\n> +\n> +\tstrbuf_release(&component);\n> +\tstrbuf_release(&remaining);\n> +\tstrbuf_release(&trailing);\n> +\treturn NULL;\n> +}\n> +\n>  char *real_pathdup(const char *path, int die_on_error)\n>  {\n>  \tstruct strbuf realpath = STRBUF_INIT;\n> diff --git a/cache.h b/cache.h\n> index c0072d43b1..e1e17e108e 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -1320,6 +1320,7 @@ static inline int is_absolute_path(const char *path)\n>  int is_directory(const char *);\n>  char *strbuf_realpath(struct strbuf *resolved, const char *path,\n>  \t\t      int die_on_error);\n> +char *strbuf_realpath_missing(struct strbuf *resolved, const char *path);\n>  char *real_pathdup(const char *path, int die_on_error);\n>  const char *absolute_path(const char *path);\n>  char *absolute_pathdup(const char *path);\n>\n"},{"id":"409394","messageId":"nycvar.QRO.7.76.6.2011091456010.18437@tvgsbejvaqbjf.bet","threadId":"54382","inReplyTo":"20201010011048.GQ1392312@camp.crustytoothpaste.net","subject":"Re: [PATCH v2 1/2] abspath: add a function to resolve paths with missing components","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-11-09T13:57:23Z","receivedAt":"2020-11-09T13:57:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi brian,\n\nOn Sat, 10 Oct 2020, brian m. carlson wrote:\n\n> On 2020-10-09 at 21:10:04, Junio C Hamano wrote:\n> > \"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n> >\n> > > We'd like to canonicalize paths such that we can preserve any number of\n> > > trailing components that may be missing.\n> >\n> > Sorry, but at least to me, the above gives no clue what kind of\n> > operation is desired to be done on paths.  How would one preserve\n> > what does not exist (i.e. are missing)?\n> >\n> > Do you mean some leading components in a path point at existing\n> > directories and after some point a component names a directory\n> > that does not exist, so everything after that does not yet exist\n> > until you \"mkdir -p\" them?\n> >\n> > I guess my confusion comes primarily from the fuzziness of the verb\n> > \"canonicalize\" in the sentence.  We want to handle a/b/../c/d and\n> > there are various combinations of missng and existing directories,\n> > e.g. a/b may not exist or a/b may but a/c may not, etc.  Is that\n> > what is going on?  Makes me wonder if it makes sense to canonicalize\n> > a/b/../c/d into a/c/d when a/b does not exist in the first place,\n> > though.\n>\n> The behavior that I'm proposing is the realpath -m behavior.  If the\n> path we're canonicalizing doesn't exist, we find the closest parent that\n> does exist, canonicalize it (à la realpath(3)), and then append the\n> components that don't exist to the canonicalized portion.\n\nFWIW I was immediately able to think of a handful scenarios where this\nfunctionality would come in handy, but I am probably not a typical example\nfor the median reader. So maybe a concrete example or two why this could\nbe handy could be shown in the cover letter?\n\nThanks,\nDscho\n\n>\n> > > Let's add a function to do\n> > > that that calls strbuf_realpath to find the canonical path for the\n> > > portion we do have and then append the missing part.  We adjust\n> > > strip_last_component to return us the component it has stripped and use\n> > > that to help us accumulate the missing part.\n> >\n> > OK, so if we have a/b/c/d and know a/b/c/d does not exist on the\n> > filesystem, we start by splitting it to a/b/c and d, see if a/b/c\n> > exists, and if not, do the same recursively to a/b/c to split it\n> > into a/b and c, and prefix the latter to 'd' that we split earlier\n> > (i.e. now we have a/b and c/d), until we have an existing directory\n> > on the first half?\n>\n> Correct.\n>\n> > > +/*\n> > > + * Like strbuf_realpath, but trailing components which do not exist are copied\n> > > + * through.\n> > > + */\n> > > +char *strbuf_realpath_missing(struct strbuf *resolved, const char *path)\n> > > +{\n> > > +\tstruct strbuf remaining = STRBUF_INIT;\n> > > +\tstruct strbuf trailing = STRBUF_INIT;\n> > > +\tstruct strbuf component = STRBUF_INIT;\n> > > +\n> > > +\tstrbuf_addstr(&remaining, path);\n> > > +\n> > > +\twhile (remaining.len) {\n> > > +\t\tif (strbuf_realpath(resolved, remaining.buf, 0)) {\n> > > +\t\t\tstrbuf_addbuf(resolved, &trailing);\n> > > +\n> > > +\t\t\tstrbuf_release(&component);\n> > > +\t\t\tstrbuf_release(&remaining);\n> > > +\t\t\tstrbuf_release(&trailing);\n> > > +\n> > > +\t\t\treturn resolved->buf;\n> > > +\t\t}\n> > > +\t\tstrip_last_component(&remaining, &component);\n> > > +\t\tstrbuf_insertstr(&trailing, 0, \"/\");\n> > > +\t\tstrbuf_insertstr(&trailing, 1, component.buf);\n> >\n> > I may be utterly confused, but is this where\n> >\n> >     - we started with a/b/c/d, pushed 'd' into trailing and decided\n> >       to redo with a/b/c\n> >\n> >     - now we split the a/b/c into a/b and c, and adjusting what is\n> >       in trailing from 'd' to 'c/d'\n> >\n> > happens place?  It's a bit sad that we need to repeatedly use\n> > insertstr to prepend in front, instead of appending.\n>\n> Yes, that's true.  It really isn't avoidable, though, with the functions\n> the way that they are.  We can't use the original path and keep track of\n> the offset because it may contain multiple path separators and we don't\n> want to include those in the path.\n> --\n> brian m. carlson: Houston, Texas, US\n>\n"},{"id":"409395","messageId":"nycvar.QRO.7.76.6.2011091457300.18437@tvgsbejvaqbjf.bet","threadId":"54382","inReplyTo":"20201009191511.267461-3-sandals@crustytoothpaste.net","subject":"Re: [PATCH v2 2/2] rev-parse: add option for absolute or relative path formatting","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-11-09T14:46:13Z","receivedAt":"2020-11-09T14:46:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi brian,\n\nOn Fri, 9 Oct 2020, brian m. carlson wrote:\n\n> git rev-parse has several options which print various paths.  Some of\n> these paths are printed relative to the current working directory, and\n> some are absolute.\n>\n> Normally, this is not a problem, but there are times when one wants\n> paths entirely in one format or another.  This can be done trivially if\n> the paths are canonical, but canonicalizing paths is not possible on\n> some shell scripting environments which lack realpath(1) and also in Go,\n> which lacks functions that properly canonicalize paths on Windows.\n>\n> To help out the scripter, let's provide an option which turns most of\n> the paths printed by git rev-parse to be either relative to the current\n> working directory or absolute and canonical.  Document which options are\n> affected and which are not so that users are not confused.\n>\n> This approach is cleaner and tidier than providing duplicates of\n> existing options which are either relative or absolute.\n>\n> Note that if the user needs both forms, it is possible to pass an\n> additional option in the middle of the command line which changes the\n> behavior of subsequent operations.\n\nGood.\n\n> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\n> index ed200c8af1..ec62b4cd16 100644\n> --- a/builtin/rev-parse.c\n> +++ b/builtin/rev-parse.c\n> @@ -583,6 +583,76 @@ static void handle_ref_opt(const char *pattern, const char *prefix)\n>  \tclear_ref_exclusion(&ref_excludes);\n>  }\n>\n> +enum format_type {\n> +\t/* We would like a relative path. */\n> +\tFORMAT_RELATIVE,\n> +\t/* We would like a canonical absolute path. */\n> +\tFORMAT_CANONICAL,\n> +\t/* We would like the default behavior. */\n> +\tFORMAT_DEFAULT,\n> +};\n> +\n> +enum default_type {\n> +\t/* Our default is a relative path. */\n> +\tDEFAULT_RELATIVE,\n> +\t/* Our default is a relative path if there's a shared root. */\n> +\tDEFAULT_RELATIVE_IF_SHARED,\n> +\t/* Our default is a canonical absolute path. */\n> +\tDEFAULT_CANONICAL,\n> +\t/* Our default is not to modify the item. */\n> +\tDEFAULT_UNMODIFIED,\n> +};\n\nI wonder whether it would make sense to consolidate these two enums into a\nsingle one.\n\n> +static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n> +{\n> +\tchar *cwd = NULL;\n> +\t/*\n> +\t * We don't ever produce a relative path if prefix is NULL, so set the\n> +\t * prefix to the current directory so that we can produce a relative\n> +\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n> +\t * we want an absolute path unless the two share a common prefix, so don't\n> +\t * set it in that case, since doing so causes a relative path to always\n> +\t * be produced if possible.\n> +\t */\n> +\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n> +\t\tprefix = cwd = xgetcwd();\n> +\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n> +\t\tputs(path);\n> +\t} else if (format == FORMAT_RELATIVE ||\n> +\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n> +\t\t/*\n> +\t\t * In order for relative_path to work as expected, we need to\n> +\t\t * make sure that both paths are absolute paths.  If we don't,\n> +\t\t * we can end up with an unexpected absolute path that the user\n> +\t\t * didn't want.\n> +\t\t */\n> +\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n> +\t\tif (!is_absolute_path(path)) {\n> +\t\t\tif (!strbuf_realpath_missing(&realbuf, path))\n> +\t\t\t\tdie(_(\"Unable to resolve path '%s'\"), path);\n> +\t\t\tpath = realbuf.buf;\n> +\t\t}\n> +\t\tif (!is_absolute_path(prefix)) {\n> +\t\t\tif (!strbuf_realpath_missing(&prefixbuf, prefix))\n> +\t\t\t\tdie(_(\"Unable to resolve path '%s'\"), path);\n> +\t\t\tprefix = prefixbuf.buf;\n> +\t\t}\n> +\t\tputs(relative_path(path, prefix, &buf));\n> +\t\tstrbuf_release(&buf);\n> +\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n> +\t\tstruct strbuf buf = STRBUF_INIT;\n> +\t\tputs(relative_path(path, prefix, &buf));\n> +\t\tstrbuf_release(&buf);\n> +\t} else {\n> +\t\tstruct strbuf buf = STRBUF_INIT;\n> +\t\tif (!strbuf_realpath_missing(&buf, path))\n> +\t\t\tdie(_(\"Unable to resolve path '%s'\"), path);\n> +\t\tputs(buf.buf);\n> +\t\tstrbuf_release(&buf);\n> +\t}\n> +\tfree(cwd);\n> +}\n> +\n>  int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n>  {\n>  \tint i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;\n> @@ -595,6 +665,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n>  \tstruct object_context unused;\n>  \tstruct strbuf buf = STRBUF_INIT;\n>  \tconst int hexsz = the_hash_algo->hexsz;\n> +\tenum format_type format = FORMAT_DEFAULT;\n>\n>  \tif (argc > 1 && !strcmp(\"--parseopt\", argv[1]))\n>  \t\treturn cmd_parseopt(argc - 1, argv + 1, prefix);\n> @@ -650,8 +721,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n>  \t\t\tif (!argv[i + 1])\n>  \t\t\t\tdie(\"--git-path requires an argument\");\n>  \t\t\tstrbuf_reset(&buf);\n> -\t\t\tputs(relative_path(git_path(\"%s\", argv[i + 1]),\n> -\t\t\t\t\t   prefix, &buf));\n> +\t\t\tprint_path(git_path(\"%s\", argv[i + 1]), prefix, format, DEFAULT_RELATIVE_IF_SHARED);\n\nOne thing that the original code did was to reuse the same `strbuf`. Not\nsure whether this matters in practice.\n\n>  \t\t\ti++;\n>  \t\t\tcontinue;\n>  \t\t}\n> @@ -683,6 +753,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n>  \t\t\t\t\tshow_file(arg, 0);\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n> +\t\t\tif (opt_with_value(arg, \"--path-format\", &arg)) {\n> +\t\t\t\tif (!strcmp(arg, \"absolute\")) {\n> +\t\t\t\t\tformat = FORMAT_CANONICAL;\n> +\t\t\t\t} else if (!strcmp(arg, \"relative\")) {\n> +\t\t\t\t\tformat = FORMAT_RELATIVE;\n> +\t\t\t\t} else {\n> +\t\t\t\t\tdie(\"unknown argument to --path-format: %s\", arg);\n> +\t\t\t\t}\n> +\t\t\t\tcontinue;\n> +\t\t\t}\n>  \t\t\tif (!strcmp(arg, \"--default\")) {\n>  \t\t\t\tdef = argv[++i];\n>  \t\t\t\tif (!def)\n> @@ -803,7 +883,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n>  \t\t\tif (!strcmp(arg, \"--show-toplevel\")) {\n>  \t\t\t\tconst char *work_tree = get_git_work_tree();\n>  \t\t\t\tif (work_tree)\n> -\t\t\t\t\tputs(work_tree);\n> +\t\t\t\t\tprint_path(work_tree, prefix, format, DEFAULT_CANONICAL);\n\nThe way `print_path()`'s code is structured, it is not immediately obvious\nto me whether the patch changes behavior here. I _suspect_ that we're now\ncalling `strbuf_realpath_missing()` and react to its return value, which\nis different from before.\n\nWouldn't make `DEFAULT_UNMODIFIED` make more sense here?\n\n>  \t\t\t\telse\n>  \t\t\t\t\tdie(\"this operation must be run in a work tree\");\n>  \t\t\t\tcontinue;\n> @@ -811,7 +891,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n>  \t\t\tif (!strcmp(arg, \"--show-superproject-working-tree\")) {\n>  \t\t\t\tstruct strbuf superproject = STRBUF_INIT;\n>  \t\t\t\tif (get_superproject_working_tree(&superproject))\n> -\t\t\t\t\tputs(superproject.buf);\n> +\t\t\t\t\tprint_path(superproject.buf, prefix, format, DEFAULT_CANONICAL);\n\nShouldn't this be `DEFAULT_UNMODIFIED`?\n\n>  \t\t\t\tstrbuf_release(&superproject);\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n> @@ -846,16 +926,18 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n>  \t\t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n>  \t\t\t\tchar *cwd;\n>  \t\t\t\tint len;\n> +\t\t\t\tenum format_type wanted = format;\n>  \t\t\t\tif (arg[2] == 'g') {\t/* --git-dir */\n>  \t\t\t\t\tif (gitdir) {\n> -\t\t\t\t\t\tputs(gitdir);\n> +\t\t\t\t\t\tprint_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);\n>  \t\t\t\t\t\tcontinue;\n>  \t\t\t\t\t}\n>  \t\t\t\t\tif (!prefix) {\n> -\t\t\t\t\t\tputs(\".git\");\n> +\t\t\t\t\t\tprint_path(\".git\", prefix, format, DEFAULT_UNMODIFIED);\n>  \t\t\t\t\t\tcontinue;\n>  \t\t\t\t\t}\n>  \t\t\t\t} else {\t\t/* --absolute-git-dir */\n> +\t\t\t\t\twanted = FORMAT_CANONICAL;\n>  \t\t\t\t\tif (!gitdir && !prefix)\n>  \t\t\t\t\t\tgitdir = \".git\";\n>  \t\t\t\t\tif (gitdir) {\n> @@ -868,14 +950,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n>  \t\t\t\t}\n>  \t\t\t\tcwd = xgetcwd();\n>  \t\t\t\tlen = strlen(cwd);\n> -\t\t\t\tprintf(\"%s%s.git\\n\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n> +\t\t\t\tstrbuf_reset(&buf);\n> +\t\t\t\tstrbuf_addf(&buf, \"%s%s.git\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n\nSo `DEFAULT_CANONICAL` ensures a trailing `/`? I do not see that in\n`print_path()`'s implementation, and also, I would love to see a different\nname for that (\"canonical\", from my Java past, suggests something like\n\"real path\" to me).\n\nThanks,\nDscho\n\n>  \t\t\t\tfree(cwd);\n> +\t\t\t\tprint_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n>  \t\t\tif (!strcmp(arg, \"--git-common-dir\")) {\n> -\t\t\t\tstrbuf_reset(&buf);\n> -\t\t\t\tputs(relative_path(get_git_common_dir(),\n> -\t\t\t\t\t\t   prefix, &buf));\n> +\t\t\t\tprint_path(get_git_common_dir(), prefix, format, DEFAULT_RELATIVE_IF_SHARED);\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n>  \t\t\tif (!strcmp(arg, \"--is-inside-git-dir\")) {\n> @@ -905,8 +987,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n>  \t\t\t\tif (the_index.split_index) {\n>  \t\t\t\t\tconst struct object_id *oid = &the_index.split_index->base_oid;\n>  \t\t\t\t\tconst char *path = git_path(\"sharedindex.%s\", oid_to_hex(oid));\n> -\t\t\t\t\tstrbuf_reset(&buf);\n> -\t\t\t\t\tputs(relative_path(path, prefix, &buf));\n> +\t\t\t\t\tprint_path(path, prefix, format, DEFAULT_RELATIVE);\n>  \t\t\t\t}\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n> diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh\n> index 408b97d5af..51d7d40ec1 100755\n> --- a/t/t1500-rev-parse.sh\n> +++ b/t/t1500-rev-parse.sh\n> @@ -3,6 +3,16 @@\n>  test_description='test git rev-parse'\n>  . ./test-lib.sh\n>\n> +test_one () {\n> +\tdir=\"$1\" &&\n> +\texpect=\"$2\" &&\n> +\tshift &&\n> +\tshift &&\n> +\techo \"$expect\" >expect &&\n> +\tgit -C \"$dir\" rev-parse \"$@\" >actual &&\n> +\ttest_cmp expect actual\n> +}\n> +\n>  # usage: [options] label is-bare is-inside-git is-inside-work prefix git-dir absolute-git-dir\n>  test_rev_parse () {\n>  \td=\n> @@ -60,7 +70,13 @@ ROOT=$(pwd)\n>\n>  test_expect_success 'setup' '\n>  \tmkdir -p sub/dir work &&\n> -\tcp -R .git repo.git\n> +\tcp -R .git repo.git &&\n> +\tgit checkout -B main &&\n> +\ttest_commit abc &&\n> +\tgit checkout -b side &&\n> +\ttest_commit def &&\n> +\tgit checkout main &&\n> +\tgit worktree add worktree side\n>  '\n>\n>  test_rev_parse toplevel false false true '' .git \"$ROOT/.git\"\n> @@ -88,6 +104,45 @@ test_rev_parse -C work -g ../repo.git -b t 'GIT_DIR=../repo.git, core.bare = tru\n>\n>  test_rev_parse -C work -g ../repo.git -b u 'GIT_DIR=../repo.git, core.bare undefined' false false true ''\n>\n> +test_expect_success 'rev-parse --path-format=absolute' '\n> +\ttest_one \".\" \"$ROOT/.git\" --path-format=absolute --git-dir &&\n> +\ttest_one \".\" \"$ROOT/.git\" --path-format=absolute --git-common-dir &&\n> +\ttest_one \"sub/dir\" \"$ROOT/.git\" --path-format=absolute --git-dir &&\n> +\ttest_one \"sub/dir\" \"$ROOT/.git\" --path-format=absolute --git-common-dir &&\n> +\ttest_one \"worktree\" \"$ROOT/.git/worktrees/worktree\" --path-format=absolute --git-dir &&\n> +\ttest_one \"worktree\" \"$ROOT/.git\" --path-format=absolute --git-common-dir &&\n> +\ttest_one \".\" \"$ROOT\" --path-format=absolute --show-toplevel &&\n> +\ttest_one \".\" \"$ROOT/.git/objects\" --path-format=absolute --git-path objects &&\n> +\ttest_one \".\" \"$ROOT/.git/objects/foo/bar/baz\" --path-format=absolute --git-path objects/foo/bar/baz\n> +'\n> +\n> +test_expect_success 'rev-parse --path-format=relative' '\n> +\ttest_one \".\" \".git\" --path-format=relative --git-dir &&\n> +\ttest_one \".\" \".git\" --path-format=relative --git-common-dir &&\n> +\ttest_one \"sub/dir\" \"../../.git\" --path-format=relative --git-dir &&\n> +\ttest_one \"sub/dir\" \"../../.git\" --path-format=relative --git-common-dir &&\n> +\ttest_one \"worktree\" \"../.git/worktrees/worktree\" --path-format=relative --git-dir &&\n> +\ttest_one \"worktree\" \"../.git\" --path-format=relative --git-common-dir &&\n> +\ttest_one \".\" \"./\" --path-format=relative --show-toplevel &&\n> +\ttest_one \".\" \".git/objects\" --path-format=relative --git-path objects &&\n> +\ttest_one \".\" \".git/objects/foo/bar/baz\" --path-format=relative --git-path objects/foo/bar/baz\n> +'\n> +\n> +test_expect_success '--path-format=relative does not affect --absolute-git-dir' '\n> +\tgit rev-parse --path-format=relative --absolute-git-dir >actual &&\n> +\techo \"$ROOT/.git\" >expect &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success '--path-format can change in the middle of the command line' '\n> +\tgit rev-parse --path-format=absolute --git-dir --path-format=relative --git-path objects/foo/bar >actual &&\n> +\tcat >expect <<-EOF &&\n> +\t$ROOT/.git\n> +\t.git/objects/foo/bar\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_expect_success 'git-common-dir from worktree root' '\n>  \techo .git >expect &&\n>  \tgit rev-parse --git-common-dir >actual &&\n>\n"},{"id":"409979","messageId":"20201116021500.GA389879@camp.crustytoothpaste.net","threadId":"54382","inReplyTo":"nycvar.QRO.7.76.6.2011091457300.18437@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 2/2] rev-parse: add option for absolute or relative path formatting","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-11-16T02:15:00Z","receivedAt":"2020-11-16T02:15:29Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2020-11-09 at 14:46:13, Johannes Schindelin wrote:\n> On Fri, 9 Oct 2020, brian m. carlson wrote:\n> > diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\n> > index ed200c8af1..ec62b4cd16 100644\n> > --- a/builtin/rev-parse.c\n> > +++ b/builtin/rev-parse.c\n> > @@ -583,6 +583,76 @@ static void handle_ref_opt(const char *pattern, const char *prefix)\n> >  \tclear_ref_exclusion(&ref_excludes);\n> >  }\n> >\n> > +enum format_type {\n> > +\t/* We would like a relative path. */\n> > +\tFORMAT_RELATIVE,\n> > +\t/* We would like a canonical absolute path. */\n> > +\tFORMAT_CANONICAL,\n> > +\t/* We would like the default behavior. */\n> > +\tFORMAT_DEFAULT,\n> > +};\n> > +\n> > +enum default_type {\n> > +\t/* Our default is a relative path. */\n> > +\tDEFAULT_RELATIVE,\n> > +\t/* Our default is a relative path if there's a shared root. */\n> > +\tDEFAULT_RELATIVE_IF_SHARED,\n> > +\t/* Our default is a canonical absolute path. */\n> > +\tDEFAULT_CANONICAL,\n> > +\t/* Our default is not to modify the item. */\n> > +\tDEFAULT_UNMODIFIED,\n> > +};\n> \n> I wonder whether it would make sense to consolidate these two enums into a\n> single one.\n\nTechnically, we can, but because there are cases in each one which don't\nmake sense in the other, we end up with a situation that is hard to\nreason about in print_path, which is, by this point, already a little\ncomplex.  So I think I'd prefer not to consolidate them.\n\n> >  int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n> >  {\n> >  \tint i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;\n> > @@ -595,6 +665,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n> >  \tstruct object_context unused;\n> >  \tstruct strbuf buf = STRBUF_INIT;\n> >  \tconst int hexsz = the_hash_algo->hexsz;\n> > +\tenum format_type format = FORMAT_DEFAULT;\n> >\n> >  \tif (argc > 1 && !strcmp(\"--parseopt\", argv[1]))\n> >  \t\treturn cmd_parseopt(argc - 1, argv + 1, prefix);\n> > @@ -650,8 +721,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n> >  \t\t\tif (!argv[i + 1])\n> >  \t\t\t\tdie(\"--git-path requires an argument\");\n> >  \t\t\tstrbuf_reset(&buf);\n> > -\t\t\tputs(relative_path(git_path(\"%s\", argv[i + 1]),\n> > -\t\t\t\t\t   prefix, &buf));\n> > +\t\t\tprint_path(git_path(\"%s\", argv[i + 1]), prefix, format, DEFAULT_RELATIVE_IF_SHARED);\n> \n> One thing that the original code did was to reuse the same `strbuf`. Not\n> sure whether this matters in practice.\n\nI don't think it does.  I'll make sure to free it, though, since\nstrbuf_reset doesn't do that.\n\n> >  \t\t\ti++;\n> >  \t\t\tcontinue;\n> >  \t\t}\n> > @@ -683,6 +753,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n> >  \t\t\t\t\tshow_file(arg, 0);\n> >  \t\t\t\tcontinue;\n> >  \t\t\t}\n> > +\t\t\tif (opt_with_value(arg, \"--path-format\", &arg)) {\n> > +\t\t\t\tif (!strcmp(arg, \"absolute\")) {\n> > +\t\t\t\t\tformat = FORMAT_CANONICAL;\n> > +\t\t\t\t} else if (!strcmp(arg, \"relative\")) {\n> > +\t\t\t\t\tformat = FORMAT_RELATIVE;\n> > +\t\t\t\t} else {\n> > +\t\t\t\t\tdie(\"unknown argument to --path-format: %s\", arg);\n> > +\t\t\t\t}\n> > +\t\t\t\tcontinue;\n> > +\t\t\t}\n> >  \t\t\tif (!strcmp(arg, \"--default\")) {\n> >  \t\t\t\tdef = argv[++i];\n> >  \t\t\t\tif (!def)\n> > @@ -803,7 +883,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n> >  \t\t\tif (!strcmp(arg, \"--show-toplevel\")) {\n> >  \t\t\t\tconst char *work_tree = get_git_work_tree();\n> >  \t\t\t\tif (work_tree)\n> > -\t\t\t\t\tputs(work_tree);\n> > +\t\t\t\t\tprint_path(work_tree, prefix, format, DEFAULT_CANONICAL);\n> \n> The way `print_path()`'s code is structured, it is not immediately obvious\n> to me whether the patch changes behavior here. I _suspect_ that we're now\n> calling `strbuf_realpath_missing()` and react to its return value, which\n> is different from before.\n> \n> Wouldn't make `DEFAULT_UNMODIFIED` make more sense here?\n\nIt's documented to show the absolute path of the top of the repository,\nso it should be safe to do either one.  Will switch.\n\n> >  \t\t\t\telse\n> >  \t\t\t\t\tdie(\"this operation must be run in a work tree\");\n> >  \t\t\t\tcontinue;\n> > @@ -811,7 +891,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n> >  \t\t\tif (!strcmp(arg, \"--show-superproject-working-tree\")) {\n> >  \t\t\t\tstruct strbuf superproject = STRBUF_INIT;\n> >  \t\t\t\tif (get_superproject_working_tree(&superproject))\n> > -\t\t\t\t\tputs(superproject.buf);\n> > +\t\t\t\t\tprint_path(superproject.buf, prefix, format, DEFAULT_CANONICAL);\n> \n> Shouldn't this be `DEFAULT_UNMODIFIED`?\n\nSame thing as above.  Will change.\n\n> > @@ -868,14 +950,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n> >  \t\t\t\t}\n> >  \t\t\t\tcwd = xgetcwd();\n> >  \t\t\t\tlen = strlen(cwd);\n> > -\t\t\t\tprintf(\"%s%s.git\\n\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n> > +\t\t\t\tstrbuf_reset(&buf);\n> > +\t\t\t\tstrbuf_addf(&buf, \"%s%s.git\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n> \n> So `DEFAULT_CANONICAL` ensures a trailing `/`? I do not see that in\n> `print_path()`'s implementation, and also, I would love to see a different\n> name for that (\"canonical\", from my Java past, suggests something like\n> \"real path\" to me).\n\nI don't think that's what's happening here.  I believe the intent is to\ninsert a slash between the current working directory and the \".git\"\ncomponent if and only if the former lacks one.  My code doesn't change\nthat.\n-- \nbrian m. carlson (he/him or they/them)\nHouston, Texas, US\n"},{"id":"409980","messageId":"20201116022143.GB389879@camp.crustytoothpaste.net","threadId":"54382","inReplyTo":"nycvar.QRO.7.76.6.2011091442190.18437@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2 1/2] abspath: add a function to resolve paths with missing components","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-11-16T02:21:43Z","receivedAt":"2020-11-16T02:22:20Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2020-11-09 at 13:55:53, Johannes Schindelin wrote:\n> Hi brian,\n> \n> On Fri, 9 Oct 2020, brian m. carlson wrote:\n> \n> > We'd like to canonicalize paths such that we can preserve any number of\n> > trailing components that may be missing.  Let's add a function to do\n> > that that calls strbuf_realpath to find the canonical path for the\n> > portion we do have and then append the missing part.  We adjust\n> > strip_last_component to return us the component it has stripped and use\n> > that to help us accumulate the missing part.\n> >\n> > Note that it is intentional that we invoke strbuf_realpath here,\n> > repeatedly if necessary, because on Windows that function is replaced\n> > with a version that uses the proper system semantics for\n> > canonicalization.  Trying to adjust strbuf_realpath to perform this kind\n> > of canonicalization with an additional option would fail to work\n> > properly on Windows.  The present approach is equivalent to\n> > strbuf_realpath for cases where the path exists, and the only other\n> > cases where we will use this function the additional overhead of\n> > multiple invocations is not significant.\n> \n> Thank you for being so considerate. Yes, on Windows, we use (wherever\n> possible) a shortcut that tells us the canonicalized path of existing\n> entries.\n> \n> Technically, it is not `strbuf_realpath()` that we override, but we take a\n> shortcut _in_ that function. That's semantics, though.\n> \n> More importantly, we recently fixed a bug in our code to allow for a quirk\n> in the `strbuf_realpath()` function: `strbuf_realpath()` allows the last\n> path component to not exist. If that is the case, now it's time to try\n> without last component.\n> \n> In a sense, this is a 1-level version of your infinite-level\n> `strbuf_realpath_missing()` function.\n> \n> An idea that immediately crosses my mind is whether that level could be\n> something we want to pass directly into `strbuf_realpath()` as a parameter\n> (it would be 1 to imitate the current behavior and -1 for the\n> infinite-level case). What do you think? Does that make sense?\n> \n> In any case, I think this `_missing()` functionality should be implemented\n> a bit more tightly with the `strbuf_realpath()` function because of the\n> logic that already allows the last component to be missing:\n> \n>                 if (lstat(resolved->buf, &st)) {\n>                         /* error out unless this was the last component */\n>                         if (errno != ENOENT || remaining.len) {\n>                                 if (die_on_error)\n>                                         die_errno(\"Invalid path '%s'\",\n>                                                   resolved->buf);\n>                                 else\n>                                         goto error_out;\n>                         }\n> \n> See https://github.com/git/git/blob/v2.29.2/abspath.c#L130-L138 for the\n> exact code and context.\n> \n> Seeing as we _already_ have some code to allow for _some_ missing\n> component, it should be possible to extend the logic to allow for\n> different levels (e.g. using `count_slashes()` if we want to allow more\n> than just the last component to be missing).\n\nOkay, if you'd prefer to do it that way, that's fine with me.  I'll\nreroll with that change.\n-- \nbrian m. carlson (he/him or they/them)\nHouston, Texas, US\n"}]}