{"thread":{"id":"54210","subject":"[PATCH] rev-parse: add option for absolute or relative path formatting","startedAt":"2020-09-08T18:52:01Z","lastAt":"2020-11-06T01:57:16Z","messageCount":11,"participants":["brian m. carlson","Johannes Schindelin","SZEDER Gábor","Jonathan Nieder"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"405170","messageId":"20200908185017.2005159-1-sandals@crustytoothpaste.net","threadId":"54210","inReplyTo":null,"subject":"[PATCH] rev-parse: add option for absolute or relative path formatting","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-09-08T18:50:17Z","receivedAt":"2020-09-08T18:52:01Z","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\nSigned-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n---\nThis impetus for this patch is Git LFS, which is written in Go.  Go\nlacks a cross-platform way to canonicalize paths in the same way that\nGit does, so when Git produces relative paths, such as in some cases\nwith --git-common-dir, we end up with problems when users are doing\nthings with unusual paths on Windows, such as when using SUBST or\nOneDrive or various other edge cases.  Go upstream does not appear eager\nto address this problem.\n\nThe obvious solution to this is to have Git canonicalize all paths, and\nrather than adding yet another one-off, I decided to implement a more\ngeneric solution.  This can also be valuable for shell scripting\nenvironments which lack realpath(1) (e.g. macOS, IIRC).\n\nDscho CC'd for visibility into how this will work on Windows.\n\n Documentation/git-rev-parse.txt | 71 ++++++++++++++++------------\n builtin/rev-parse.c             | 84 ++++++++++++++++++++++++++++-----\n t/t1500-rev-parse.sh            | 36 +++++++++++++-\n 3 files changed, 148 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 669dd2fd6f..311a56ba01 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -583,6 +583,55 @@ 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  (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_reset(&buf);\n+\t} else {\n+\t\tchar *real = real_pathdup(path, 1);\n+\t\tputs(real);\n+\t\tfree(real);\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 +644,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 +700,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 +732,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 +862,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 +870,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 +905,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 +929,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 +966,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..6f3811d189 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,24 @@ 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 \"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+'\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 \"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+'\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":"405229","messageId":"nycvar.QRO.7.76.6.2009090513300.54@tvgsbejvaqbjf.bet","threadId":"54210","inReplyTo":"20200908185017.2005159-1-sandals@crustytoothpaste.net","subject":"Re: [PATCH] rev-parse: add option for absolute or relative path formatting","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-09-09T03:23:32Z","receivedAt":"2020-09-09T08:04:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi brian,\n\nOn Tue, 8 Sep 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> Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n> ---\n\nNicely explained and implemented.\n\n> This impetus for this patch is Git LFS, which is written in Go.  Go\n> lacks a cross-platform way to canonicalize paths in the same way that\n> Git does, so when Git produces relative paths, such as in some cases\n> with --git-common-dir, we end up with problems when users are doing\n> things with unusual paths on Windows, such as when using SUBST or\n> OneDrive or various other edge cases.  Go upstream does not appear eager\n> to address this problem.\n>\n> The obvious solution to this is to have Git canonicalize all paths, and\n> rather than adding yet another one-off, I decided to implement a more\n> generic solution.  This can also be valuable for shell scripting\n> environments which lack realpath(1) (e.g. macOS, IIRC).\n>\n> Dscho CC'd for visibility into how this will work on Windows.\n\nOn Windows, at least in the version from git-for-windows/git,\n`strbuf_realpath()` uses the Win32 API function\n`GetFinalPathNameByHandleW()` to canonicalize paths (whenever possible),\nmeaning that the `subst` issue you mentioned above will be addressed\nadequately.\n\nNote: this platform-specific real path handling has not yet made it\nupstream, it is still \"battle-tested\" in Git for Windows.\n\nThanks,\nDscho\n\n>\n>  Documentation/git-rev-parse.txt | 71 ++++++++++++++++------------\n>  builtin/rev-parse.c             | 84 ++++++++++++++++++++++++++++-----\n>  t/t1500-rev-parse.sh            | 36 +++++++++++++-\n>  3 files changed, 148 insertions(+), 43 deletions(-)\n>\n> diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\n> index 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\n> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\n> index 669dd2fd6f..311a56ba01 100644\n> --- a/builtin/rev-parse.c\n> +++ b/builtin/rev-parse.c\n> @@ -583,6 +583,55 @@ 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  (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_reset(&buf);\n> +\t} else {\n> +\t\tchar *real = real_pathdup(path, 1);\n> +\t\tputs(real);\n> +\t\tfree(real);\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 +644,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 +700,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 +732,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 +862,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 +870,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 +905,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 +929,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 +966,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..6f3811d189 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,24 @@ 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 \"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> +'\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 \"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> +'\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":"405253","messageId":"20200909145114.GE6209@szeder.dev","threadId":"54210","inReplyTo":"20200908185017.2005159-1-sandals@crustytoothpaste.net","subject":"Re: [PATCH] rev-parse: add option for absolute or relative path formatting","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2020-09-09T14:51:14Z","receivedAt":"2020-09-09T17:03:19Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Sep 08, 2020 at 06:50:17PM +0000, brian m. carlson wrote:\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\nIndeed.  I added '--absolute-git-dir' as a one-off, because I didn't\nsee a use case for other absolute paths, and because I saw some design\nissues with a generic solution and I didn't want to go there back\nthen.  In particular:\n\n> diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\n> index 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.\n\nDoes it affect only one subsequent such option on the command line, or\nall such options?  IOW, while standing in the top directory of the\nworktree, would the following command\n\n  git rev-parse --path-format=absolute --git-dir --git-path foo --show-toplevel\n\nprint three absolute paths, or one absolute paths and two relative\npaths?\n\nThe wording here is not clear on this, the commit message doesn't\nmention it, and the tests added in this patch only check one such\noption, but looking at the code and doing some testing of my own I\nfound that it affects all subsequent such options.\n\n>       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\n\n> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\n> index 669dd2fd6f..311a56ba01 100644\n> --- a/builtin/rev-parse.c\n> +++ b/builtin/rev-parse.c\n> @@ -583,6 +583,55 @@ 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  (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_reset(&buf);\n> +\t} else {\n> +\t\tchar *real = real_pathdup(path, 1);\n> +\t\tputs(real);\n> +\t\tfree(real);\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 +644,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 +700,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 +732,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 +862,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 +870,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 +905,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 +929,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 +966,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..6f3811d189 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\nBroken && chain.\n\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,24 @@ 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 \"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> +'\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 \"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> +'\n\nI would like to see a test that demonstrates that '--absolute-git-dir'\nis unaffected by '--path-format=relative', just to be sure.\n\nThere are some cases where this new option doesn't do what I would\nexpect:\n\n  $ ./git -C Documentation/ rev-parse --git-dir --show-toplevel\n  /home/szeder/src/git/.git\n  /home/szeder/src/git\n  $ ./git -C Documentation/ rev-parse --path-format=absolute --git-dir --show-toplevel\n  /home/szeder/src/git/.git\n  /home/szeder/src/git\n  # So far so good, but:\n  $ ./git -C Documentation/ rev-parse --path-format=relative --git-dir --show-toplevel\n  /home/szeder/src/git/.git\n  /home/szeder/src/git\n\n\n  $ ls -l .git/foo\n  ls: cannot access '.git/foo': No such file or directory\n  $ ./git rev-parse --git-path foo\n  .git/foo\n  $ ./git rev-parse --path-format=relative --git-path foo\n  .git/foo\n  $ ./git rev-parse --path-format=absolute --git-path foo\n  /home/szeder/src/git/.git/foo\n  $ ./git rev-parse --git-path foo/bar\n  .git/foo/bar\n  $ ./git rev-parse --path-format=relative --git-path foo/bar\n  .git/foo/bar\n  # So far so good, but:\n  $ ./git rev-parse --path-format=absolute --git-path foo/bar\n  fatal: Invalid path '/home/szeder/src/git/.git/foo': No such file or directory\n\n"},{"id":"405291","messageId":"20200909222333.GH241078@camp.crustytoothpaste.net","threadId":"54210","inReplyTo":"20200909145114.GE6209@szeder.dev","subject":"Re: [PATCH] rev-parse: add option for absolute or relative path formatting","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-09-09T22:23:33Z","receivedAt":"2020-09-09T22:23:46Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2020-09-09 at 14:51:14, SZEDER Gábor wrote:\n> On Tue, Sep 08, 2020 at 06:50:17PM +0000, brian m. carlson wrote:\n> > diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\n> > index 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.\n> \n> Does it affect only one subsequent such option on the command line, or\n> all such options?  IOW, while standing in the top directory of the\n> worktree, would the following command\n> \n>   git rev-parse --path-format=absolute --git-dir --git-path foo --show-toplevel\n> \n> print three absolute paths, or one absolute paths and two relative\n> paths?\n> \n> The wording here is not clear on this, the commit message doesn't\n> mention it, and the tests added in this patch only check one such\n> option, but looking at the code and doing some testing of my own I\n> found that it affects all subsequent such options.\n\nIt affects all subsequent options.  Moreover, I believe it's possible to\nswitch in the middle of the command line if you want some things\nrelative and some absolute.  That seemed to be both the easiest solution\nand the most flexible, so I went with it.  I'll add more tests for this\ncase and improve the commit message.\n\n> > diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh\n> > index 408b97d5af..6f3811d189 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> \n> Broken && chain.\n\nThanks, will fix.\n\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,24 @@ 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 \"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> > +'\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 \"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> > +'\n> \n> I would like to see a test that demonstrates that '--absolute-git-dir'\n> is unaffected by '--path-format=relative', just to be sure.\n> \n> There are some cases where this new option doesn't do what I would\n> expect:\n> \n>   $ ./git -C Documentation/ rev-parse --git-dir --show-toplevel\n>   /home/szeder/src/git/.git\n>   /home/szeder/src/git\n>   $ ./git -C Documentation/ rev-parse --path-format=absolute --git-dir --show-toplevel\n>   /home/szeder/src/git/.git\n>   /home/szeder/src/git\n>   # So far so good, but:\n>   $ ./git -C Documentation/ rev-parse --path-format=relative --git-dir --show-toplevel\n>   /home/szeder/src/git/.git\n>   /home/szeder/src/git\n> \n> \n>   $ ls -l .git/foo\n>   ls: cannot access '.git/foo': No such file or directory\n>   $ ./git rev-parse --git-path foo\n>   .git/foo\n>   $ ./git rev-parse --path-format=relative --git-path foo\n>   .git/foo\n>   $ ./git rev-parse --path-format=absolute --git-path foo\n>   /home/szeder/src/git/.git/foo\n>   $ ./git rev-parse --git-path foo/bar\n>   .git/foo/bar\n>   $ ./git rev-parse --path-format=relative --git-path foo/bar\n>   .git/foo/bar\n>   # So far so good, but:\n>   $ ./git rev-parse --path-format=absolute --git-path foo/bar\n>   fatal: Invalid path '/home/szeder/src/git/.git/foo': No such file or directory\n\nThat's going to be a little tricky to fix, but I'll look into it.\n-- \nbrian m. carlson: Houston, Texas, US\n"},{"id":"405292","messageId":"20200909223151.GI241078@camp.crustytoothpaste.net","threadId":"54210","inReplyTo":"nycvar.QRO.7.76.6.2009090513300.54@tvgsbejvaqbjf.bet","subject":"Re: [PATCH] rev-parse: add option for absolute or relative path formatting","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-09-09T22:31:51Z","receivedAt":"2020-09-09T22:32:29Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2020-09-09 at 03:23:32, Johannes Schindelin wrote:\n> On Tue, 8 Sep 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> > Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>\n> > ---\n> \n> Nicely explained and implemented.\n\nThanks.\n\n> > This impetus for this patch is Git LFS, which is written in Go.  Go\n> > lacks a cross-platform way to canonicalize paths in the same way that\n> > Git does, so when Git produces relative paths, such as in some cases\n> > with --git-common-dir, we end up with problems when users are doing\n> > things with unusual paths on Windows, such as when using SUBST or\n> > OneDrive or various other edge cases.  Go upstream does not appear eager\n> > to address this problem.\n> >\n> > The obvious solution to this is to have Git canonicalize all paths, and\n> > rather than adding yet another one-off, I decided to implement a more\n> > generic solution.  This can also be valuable for shell scripting\n> > environments which lack realpath(1) (e.g. macOS, IIRC).\n> >\n> > Dscho CC'd for visibility into how this will work on Windows.\n> \n> On Windows, at least in the version from git-for-windows/git,\n> `strbuf_realpath()` uses the Win32 API function\n> `GetFinalPathNameByHandleW()` to canonicalize paths (whenever possible),\n> meaning that the `subst` issue you mentioned above will be addressed\n> adequately.\n\nYeah, I looked into the function Git for Windows used, and it's also\nused by Rust's std::path::canonicalize, so I think there's wide\nagreement that this is the right function to be using here.\n\nI think for _most_ of our cases, it matters a little less how we're\nhandling paths than that we're handling them consistently, but if we can\nfix all of the cases, not just most, that's preferable.\n-- \nbrian m. carlson: Houston, Texas, US\n"},{"id":"405370","messageId":"20200910151935.GA5265@szeder.dev","threadId":"54210","inReplyTo":"20200909222333.GH241078@camp.crustytoothpaste.net","subject":"Re: [PATCH] rev-parse: add option for absolute or relative path formatting","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2020-09-10T15:19:35Z","receivedAt":"2020-09-10T19:40:56Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Wed, Sep 09, 2020 at 10:23:33PM +0000, brian m. carlson wrote:\n> On 2020-09-09 at 14:51:14, SZEDER Gábor wrote:\n> > On Tue, Sep 08, 2020 at 06:50:17PM +0000, brian m. carlson wrote:\n> > > diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt\n> > > index 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.\n> > \n> > Does it affect only one subsequent such option on the command line, or\n> > all such options?  IOW, while standing in the top directory of the\n> > worktree, would the following command\n> > \n> >   git rev-parse --path-format=absolute --git-dir --git-path foo --show-toplevel\n> > \n> > print three absolute paths, or one absolute paths and two relative\n> > paths?\n> > \n> > The wording here is not clear on this, the commit message doesn't\n> > mention it, and the tests added in this patch only check one such\n> > option, but looking at the code and doing some testing of my own I\n> > found that it affects all subsequent such options.\n> \n> It affects all subsequent options.  Moreover, I believe it's possible to\n> switch in the middle of the command line if you want some things\n> relative and some absolute.  That seemed to be both the easiest solution\n> and the most flexible, so I went with it.  I'll add more tests for this\n> case and improve the commit message.\n\nWell, on first sight it seems nice that scripts have to specify\n'--path-format=absolute' only once when querying multiple paths at\nonce, though on second thought no prudent script would query multiple\npaths at once, because they might contain newlines.\n\n> >   $ ./git -C Documentation/ rev-parse --path-format=relative --git-dir --show-toplevel\n> >   /home/szeder/src/git/.git\n> >   /home/szeder/src/git\n\n> >   $ ./git rev-parse --path-format=absolute --git-path foo/bar\n> >   fatal: Invalid path '/home/szeder/src/git/.git/foo': No such file or directory\n> \n> That's going to be a little tricky to fix, but I'll look into it.\n\nYeah, I think I found trouble that way, too.  I wonder whether\n'--path-format=relative' is really worth having, though.  Clearly\nthere's a need for absolute paths, because getting relative paths\ncauses difficulties for some scripts; I described one such use case in\na2f5a87626 (rev-parse: add '--absolute-git-dir' option, 2017-02-03),\nand you just ran into another with Git LFS.  However, is there really\na use case that requires relative paths, because an absolute path\nwould cause similar difficulties?  I couldn't come with any.\n\nSo perhaps it would be sufficient to introduce only\n'--path-format=absolute' (or equivalent) for now, and add a relative\npath variant only when there will be an actual compelling reason to do\nso.  And that would save you from the pain of addressing these bugs\nshown above.\n\n"},{"id":"405390","messageId":"20200911000315.GL241078@camp.crustytoothpaste.net","threadId":"54210","inReplyTo":"20200910151935.GA5265@szeder.dev","subject":"Re: [PATCH] rev-parse: add option for absolute or relative path formatting","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-09-11T00:03:15Z","receivedAt":"2020-09-11T00:03:58Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2020-09-10 at 15:19:35, SZEDER Gábor wrote:\n> Well, on first sight it seems nice that scripts have to specify\n> '--path-format=absolute' only once when querying multiple paths at\n> once, though on second thought no prudent script would query multiple\n> paths at once, because they might contain newlines.\n\nWe do that in Git LFS at the cost of not handling paths with newlines\nbecause calling out to Git multiple times is expensive, very especially\non Windows.  Of course, Windows doesn't allow newlines in paths, but it\nseems silly to have two different code paths for the same thing,\nespecially on top of the different paths we have for versions of Git\nwith and without worktrees (and therefore --git-common-dir).\n\nIf you're writing a script, you're not expecting great performance (or\nyou wouldn't be writing shell), so it's less of a problem and you can\nactually afford the cost of being a little more correct.\n\nI think if we wanted to prudently handle paths with newlines, we'd want\nto add a -z option to rev-parse (think about the case in shell where the\npath ends with a trailing newline).  I may get around to that at some\npoint, but anyone is welcome to pick it up before me.\n\n> Yeah, I think I found trouble that way, too.  I wonder whether\n> '--path-format=relative' is really worth having, though.  Clearly\n> there's a need for absolute paths, because getting relative paths\n> causes difficulties for some scripts; I described one such use case in\n> a2f5a87626 (rev-parse: add '--absolute-git-dir' option, 2017-02-03),\n> and you just ran into another with Git LFS.  However, is there really\n> a use case that requires relative paths, because an absolute path\n> would cause similar difficulties?  I couldn't come with any.\n\nThe only case I can come up with is possibly wanting to know if a path\nis within another path, or if it's outside, and doing that with relative\npaths may be easier.\n\n> So perhaps it would be sufficient to introduce only\n> '--path-format=absolute' (or equivalent) for now, and add a relative\n> path variant only when there will be an actual compelling reason to do\n> so.  And that would save you from the pain of addressing these bugs\n> shown above.\n\nI'll consider it when I do my reroll, which will probably be this\nweekend.\n-- \nbrian m. carlson: Houston, Texas, US\n"},{"id":"409173","messageId":"20201104221659.GA3183353@google.com","threadId":"54210","inReplyTo":"20200908185017.2005159-1-sandals@crustytoothpaste.net","subject":"Re: [PATCH] rev-parse: add option for absolute or relative path formatting","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-11-04T22:16:59Z","receivedAt":"2020-11-04T22:17:39Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"brian m. carlson wrote:\n\n> This impetus for this patch is Git LFS, which is written in Go.  Go\n> lacks a cross-platform way to canonicalize paths in the same way that\n> Git does, so when Git produces relative paths, such as in some cases\n> with --git-common-dir, we end up with problems when users are doing\n> things with unusual paths on Windows, such as when using SUBST or\n> OneDrive or various other edge cases.  Go upstream does not appear eager\n> to address this problem.\n\nCan you describe the user-facing symptom?  While reviewing\nhttps://lore.kernel.org/git/20201009191511.267461-1-sandals@crustytoothpaste.net/\nI'm trying to understand the motivation and I'm getting stuck at\ntrying to understand the basics of the problem being solved.\n\nE.g. is this about being able to compare paths, being able to open\nfiles, resolving symlinks, something else?\n\nThanks,\nJonathan\n"},{"id":"409194","messageId":"20201105031153.GA1332931@camp.crustytoothpaste.net","threadId":"54210","inReplyTo":"20201104221659.GA3183353@google.com","subject":"Re: [PATCH] rev-parse: add option for absolute or relative path formatting","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-11-05T03:11:53Z","receivedAt":"2020-11-05T03:12:33Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2020-11-04 at 22:16:59, Jonathan Nieder wrote:\n> brian m. carlson wrote:\n> \n> > This impetus for this patch is Git LFS, which is written in Go.  Go\n> > lacks a cross-platform way to canonicalize paths in the same way that\n> > Git does, so when Git produces relative paths, such as in some cases\n> > with --git-common-dir, we end up with problems when users are doing\n> > things with unusual paths on Windows, such as when using SUBST or\n> > OneDrive or various other edge cases.  Go upstream does not appear eager\n> > to address this problem.\n> \n> Can you describe the user-facing symptom?  While reviewing\n> https://lore.kernel.org/git/20201009191511.267461-1-sandals@crustytoothpaste.net/\n> I'm trying to understand the motivation and I'm getting stuck at\n> trying to understand the basics of the problem being solved.\n> \n> E.g. is this about being able to compare paths, being able to open\n> files, resolving symlinks, something else?\n\nThe goal is to resolve paths the way Git does and allow verify that a\npath is within the repository.\n\nSome paths, such as SUBST drives, are canonicalized by Git using\nGetFinalPathNameByHandle, which is the standard way of canonicalizing\npaths on Windows.\n\nGo provides filepath.EvalSymlinks, which does not handle these paths,\nand consequently, a check that a given path is within the repository (or\ngit directory) fails because Go sees the path as being on one drive\n(after canonicalization) and Git sees the repository as being on\nanother.  This is in part because the .git directory we've canonicalized\nin Go is totally different from the path that Git has for it, and\nconsequently they aren't comparable.\n\nThat's the user-facing symptom.\n\nThere are a variety of other Windows paths that are handled differently\nas well beyond the SUBST paths.  I'm not an expert on Windows or Windows\npaths, so I can't enumerate all the cases where one case works and the\nother fails.  Ideally Go would learn to canonicalize paths the way the\nsystem does using a cross-platform function.  I believe there's an issue\nupstream to deal with this at https://github.com/golang/go/issues/42201.\n\nThis is also generally applicable for scripting, where realpath(1) is\nnot always available (e.g., on macOS), but mostly this is here to make\nWindows work more nicely, since it has more complex path functionality.\n-- \nbrian m. carlson (he/him or they/them)\nHouston, Texas, US\n"},{"id":"409257","messageId":"20201106005110.GA3479573@google.com","threadId":"54210","inReplyTo":"20201105031153.GA1332931@camp.crustytoothpaste.net","subject":"Re: [PATCH] rev-parse: add option for absolute or relative path formatting","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-11-06T00:51:10Z","receivedAt":"2020-11-06T00:51:15Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"brian m. carlson wrote:\n> On 2020-11-04 at 22:16:59, Jonathan Nieder wrote:\n> > brian m. carlson wrote:\n\n>>> This impetus for this patch is Git LFS, which is written in Go.  Go\n>>> lacks a cross-platform way to canonicalize paths in the same way that\n>>> Git does, so when Git produces relative paths, such as in some cases\n>>> with --git-common-dir, we end up with problems when users are doing\n>>> things with unusual paths on Windows, such as when using SUBST\n[...]\n>> Can you describe the user-facing symptom?  While reviewing\n>> https://lore.kernel.org/git/20201009191511.267461-1-sandals@crustytoothpaste.net/\n>> I'm trying to understand the motivation and I'm getting stuck at\n>> trying to understand the basics of the problem being solved.\n[...]\n> The goal is to resolve paths the way Git does and allow verify that a\n> path is within the repository.\n\nAh, thank you.  So if I am understanding the above and [1] correctly,\nthis means:\n\n- when a path is within a repository, converting it to a path relative\n  to the repository root\n\n- when a path is not within a repository, learning so\n\nand that making relative paths with ../../ portion that exits the\nrepository is *not* an important part of this use case (though it\ncould be useful for other things).\n\n[...]\n> This is also generally applicable for scripting, where realpath(1) is\n> not always available (e.g., on macOS), but mostly this is here to make\n> Windows work more nicely, since it has more complex path functionality.\n\nThanks much.  I think this tells me enough to understand the series.\n\nSincerely,\nJonathan\n\n[1] https://github.com/git-lfs/git-lfs/issues/4012 --- thanks to Emily\nfor the pointer in [2].\n[2] https://lore.kernel.org/git/20201104230157.GH2774782@google.com/\n"},{"id":"409262","messageId":"20201106015708.GB6252@camp.crustytoothpaste.net","threadId":"54210","inReplyTo":"20201106005110.GA3479573@google.com","subject":"Re: [PATCH] rev-parse: add option for absolute or relative path formatting","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-11-06T01:57:08Z","receivedAt":"2020-11-06T01:57:16Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2020-11-06 at 00:51:10, Jonathan Nieder wrote:\n> Ah, thank you.  So if I am understanding the above and [1] correctly,\n> this means:\n> \n> - when a path is within a repository, converting it to a path relative\n>   to the repository root\n> \n> - when a path is not within a repository, learning so\n> \n> and that making relative paths with ../../ portion that exits the\n> repository is *not* an important part of this use case (though it\n> could be useful for other things).\n\nCorrect.  I felt that adding an --absolute option would be less\nbeneficial than both an absolute and a relative option, so I implemented\na more generic solution, even though my use case didn't require it.\n\n> Thanks much.  I think this tells me enough to understand the series.\n\nSure, thanks for asking.\n-- \nbrian m. carlson (he/him or they/them)\nHouston, Texas, US\n"}]}