Skip to content

Commit fac60b8

Browse files
bk2204gitster
authored andcommitted
rev-parse: add option for absolute or relative path formatting
git rev-parse has several options which print various paths. Some of these paths are printed relative to the current working directory, and some are absolute. Normally, this is not a problem, but there are times when one wants paths entirely in one format or another. This can be done trivially if the paths are canonical, but canonicalizing paths is not possible on some shell scripting environments which lack realpath(1) and also in Go, which lacks functions that properly canonicalize paths on Windows. To help out the scripter, let's provide an option which turns most of the paths printed by git rev-parse to be either relative to the current working directory or absolute and canonical. Document which options are affected and which are not so that users are not confused. This approach is cleaner and tidier than providing duplicates of existing options which are either relative or absolute. Note that if the user needs both forms, it is possible to pass an additional option in the middle of the command line which changes the behavior of subsequent operations. Signed-off-by: brian m. carlson <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent be6e0da commit fac60b8

File tree

3 files changed

+194
-43
lines changed

3 files changed

+194
-43
lines changed

Documentation/git-rev-parse.txt

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,18 @@ Options for Files
212212
Only the names of the variables are listed, not their value,
213213
even if they are set.
214214

215+
--path-format=(absolute|relative)::
216+
Controls the behavior of certain other options. If specified as absolute, the
217+
paths printed by those options will be absolute and canonical. If specified as
218+
relative, the paths will be relative to the current working directory if that
219+
is possible. The default is option specific.
220+
+
221+
This option may be specified multiple times and affects only the arguments that
222+
follow it on the command line, either to the end of the command line or the next
223+
instance of this option.
224+
225+
The following options are modified by `--path-format`:
226+
215227
--git-dir::
216228
Show `$GIT_DIR` if defined. Otherwise show the path to
217229
the .git directory. The path shown, when relative, is
@@ -221,27 +233,9 @@ If `$GIT_DIR` is not defined and the current directory
221233
is not detected to lie in a Git repository or work tree
222234
print a message to stderr and exit with nonzero status.
223235

224-
--absolute-git-dir::
225-
Like `--git-dir`, but its output is always the canonicalized
226-
absolute path.
227-
228236
--git-common-dir::
229237
Show `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`.
230238

231-
--is-inside-git-dir::
232-
When the current working directory is below the repository
233-
directory print "true", otherwise "false".
234-
235-
--is-inside-work-tree::
236-
When the current working directory is inside the work tree of the
237-
repository print "true", otherwise "false".
238-
239-
--is-bare-repository::
240-
When the repository is bare print "true", otherwise "false".
241-
242-
--is-shallow-repository::
243-
When the repository is shallow print "true", otherwise "false".
244-
245239
--resolve-git-dir <path>::
246240
Check if <path> is a valid repository or a gitfile that
247241
points at a valid repository, and print the location of the
@@ -255,19 +249,9 @@ print a message to stderr and exit with nonzero status.
255249
$GIT_OBJECT_DIRECTORY is set to /foo/bar then "git rev-parse
256250
--git-path objects/abc" returns /foo/bar/abc.
257251

258-
--show-cdup::
259-
When the command is invoked from a subdirectory, show the
260-
path of the top-level directory relative to the current
261-
directory (typically a sequence of "../", or an empty string).
262-
263-
--show-prefix::
264-
When the command is invoked from a subdirectory, show the
265-
path of the current directory relative to the top-level
266-
directory.
267-
268252
--show-toplevel::
269-
Show the absolute path of the top-level directory of the working
270-
tree. If there is no working tree, report an error.
253+
Show the (by default, absolute) path of the top-level directory
254+
of the working tree. If there is no working tree, report an error.
271255

272256
--show-superproject-working-tree::
273257
Show the absolute path of the root of the superproject's
@@ -279,6 +263,36 @@ print a message to stderr and exit with nonzero status.
279263
Show the path to the shared index file in split index mode, or
280264
empty if not in split-index mode.
281265

266+
The following options are unaffected by `--path-format`:
267+
268+
--absolute-git-dir::
269+
Like `--git-dir`, but its output is always the canonicalized
270+
absolute path.
271+
272+
--is-inside-git-dir::
273+
When the current working directory is below the repository
274+
directory print "true", otherwise "false".
275+
276+
--is-inside-work-tree::
277+
When the current working directory is inside the work tree of the
278+
repository print "true", otherwise "false".
279+
280+
--is-bare-repository::
281+
When the repository is bare print "true", otherwise "false".
282+
283+
--is-shallow-repository::
284+
When the repository is shallow print "true", otherwise "false".
285+
286+
--show-cdup::
287+
When the command is invoked from a subdirectory, show the
288+
path of the top-level directory relative to the current
289+
directory (typically a sequence of "../", or an empty string).
290+
291+
--show-prefix::
292+
When the command is invoked from a subdirectory, show the
293+
path of the current directory relative to the top-level
294+
directory.
295+
282296
--show-object-format[=(storage|input|output)]::
283297
Show the object format (hash algorithm) used for the repository
284298
for storage inside the `.git` directory, input, or output. For

builtin/rev-parse.c

Lines changed: 94 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,75 @@ static void handle_ref_opt(const char *pattern, const char *prefix)
583583
clear_ref_exclusion(&ref_excludes);
584584
}
585585

586+
enum format_type {
587+
/* We would like a relative path. */
588+
FORMAT_RELATIVE,
589+
/* We would like a canonical absolute path. */
590+
FORMAT_CANONICAL,
591+
/* We would like the default behavior. */
592+
FORMAT_DEFAULT,
593+
};
594+
595+
enum default_type {
596+
/* Our default is a relative path. */
597+
DEFAULT_RELATIVE,
598+
/* Our default is a relative path if there's a shared root. */
599+
DEFAULT_RELATIVE_IF_SHARED,
600+
/* Our default is a canonical absolute path. */
601+
DEFAULT_CANONICAL,
602+
/* Our default is not to modify the item. */
603+
DEFAULT_UNMODIFIED,
604+
};
605+
606+
static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)
607+
{
608+
char *cwd = NULL;
609+
/*
610+
* We don't ever produce a relative path if prefix is NULL, so set the
611+
* prefix to the current directory so that we can produce a relative
612+
* path whenever possible. If we're using RELATIVE_IF_SHARED mode, then
613+
* we want an absolute path unless the two share a common prefix, so don't
614+
* set it in that case, since doing so causes a relative path to always
615+
* be produced if possible.
616+
*/
617+
if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))
618+
prefix = cwd = xgetcwd();
619+
if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {
620+
puts(path);
621+
} else if (format == FORMAT_RELATIVE ||
622+
(format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {
623+
/*
624+
* In order for relative_path to work as expected, we need to
625+
* make sure that both paths are absolute paths. If we don't,
626+
* we can end up with an unexpected absolute path that the user
627+
* didn't want.
628+
*/
629+
struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;
630+
if (!is_absolute_path(path)) {
631+
strbuf_realpath_forgiving(&realbuf, path, 1);
632+
path = realbuf.buf;
633+
}
634+
if (!is_absolute_path(prefix)) {
635+
strbuf_realpath_forgiving(&prefixbuf, prefix, 1);
636+
prefix = prefixbuf.buf;
637+
}
638+
puts(relative_path(path, prefix, &buf));
639+
strbuf_release(&buf);
640+
strbuf_release(&realbuf);
641+
strbuf_release(&prefixbuf);
642+
} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {
643+
struct strbuf buf = STRBUF_INIT;
644+
puts(relative_path(path, prefix, &buf));
645+
strbuf_release(&buf);
646+
} else {
647+
struct strbuf buf = STRBUF_INIT;
648+
strbuf_realpath_forgiving(&buf, path, 1);
649+
puts(buf.buf);
650+
strbuf_release(&buf);
651+
}
652+
free(cwd);
653+
}
654+
586655
int cmd_rev_parse(int argc, const char **argv, const char *prefix)
587656
{
588657
int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
@@ -596,6 +665,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
596665
struct strbuf buf = STRBUF_INIT;
597666
const int hexsz = the_hash_algo->hexsz;
598667
int seen_end_of_options = 0;
668+
enum format_type format = FORMAT_DEFAULT;
599669

600670
if (argc > 1 && !strcmp("--parseopt", argv[1]))
601671
return cmd_parseopt(argc - 1, argv + 1, prefix);
@@ -668,8 +738,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
668738
if (!argv[i + 1])
669739
die("--git-path requires an argument");
670740
strbuf_reset(&buf);
671-
puts(relative_path(git_path("%s", argv[i + 1]),
672-
prefix, &buf));
741+
print_path(git_path("%s", argv[i + 1]), prefix,
742+
format,
743+
DEFAULT_RELATIVE_IF_SHARED);
673744
i++;
674745
continue;
675746
}
@@ -687,6 +758,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
687758
show(arg);
688759
continue;
689760
}
761+
if (opt_with_value(arg, "--path-format", &arg)) {
762+
if (!strcmp(arg, "absolute")) {
763+
format = FORMAT_CANONICAL;
764+
} else if (!strcmp(arg, "relative")) {
765+
format = FORMAT_RELATIVE;
766+
} else {
767+
die("unknown argument to --path-format: %s", arg);
768+
}
769+
continue;
770+
}
690771
if (!strcmp(arg, "--default")) {
691772
def = argv[++i];
692773
if (!def)
@@ -807,15 +888,15 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
807888
if (!strcmp(arg, "--show-toplevel")) {
808889
const char *work_tree = get_git_work_tree();
809890
if (work_tree)
810-
puts(work_tree);
891+
print_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);
811892
else
812893
die("this operation must be run in a work tree");
813894
continue;
814895
}
815896
if (!strcmp(arg, "--show-superproject-working-tree")) {
816897
struct strbuf superproject = STRBUF_INIT;
817898
if (get_superproject_working_tree(&superproject))
818-
puts(superproject.buf);
899+
print_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED);
819900
strbuf_release(&superproject);
820901
continue;
821902
}
@@ -850,16 +931,18 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
850931
const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
851932
char *cwd;
852933
int len;
934+
enum format_type wanted = format;
853935
if (arg[2] == 'g') { /* --git-dir */
854936
if (gitdir) {
855-
puts(gitdir);
937+
print_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);
856938
continue;
857939
}
858940
if (!prefix) {
859-
puts(".git");
941+
print_path(".git", prefix, format, DEFAULT_UNMODIFIED);
860942
continue;
861943
}
862944
} else { /* --absolute-git-dir */
945+
wanted = FORMAT_CANONICAL;
863946
if (!gitdir && !prefix)
864947
gitdir = ".git";
865948
if (gitdir) {
@@ -872,14 +955,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
872955
}
873956
cwd = xgetcwd();
874957
len = strlen(cwd);
875-
printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
958+
strbuf_reset(&buf);
959+
strbuf_addf(&buf, "%s%s.git", cwd, len && cwd[len-1] != '/' ? "/" : "");
876960
free(cwd);
961+
print_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);
877962
continue;
878963
}
879964
if (!strcmp(arg, "--git-common-dir")) {
880-
strbuf_reset(&buf);
881-
puts(relative_path(get_git_common_dir(),
882-
prefix, &buf));
965+
print_path(get_git_common_dir(), prefix, format, DEFAULT_RELATIVE_IF_SHARED);
883966
continue;
884967
}
885968
if (!strcmp(arg, "--is-inside-git-dir")) {
@@ -909,8 +992,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
909992
if (the_index.split_index) {
910993
const struct object_id *oid = &the_index.split_index->base_oid;
911994
const char *path = git_path("sharedindex.%s", oid_to_hex(oid));
912-
strbuf_reset(&buf);
913-
puts(relative_path(path, prefix, &buf));
995+
print_path(path, prefix, format, DEFAULT_RELATIVE);
914996
}
915997
continue;
916998
}

t/t1500-rev-parse.sh

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
test_description='test git rev-parse'
44
. ./test-lib.sh
55

6+
test_one () {
7+
dir="$1" &&
8+
expect="$2" &&
9+
shift &&
10+
shift &&
11+
echo "$expect" >expect &&
12+
git -C "$dir" rev-parse "$@" >actual &&
13+
test_cmp expect actual
14+
}
15+
616
# usage: [options] label is-bare is-inside-git is-inside-work prefix git-dir absolute-git-dir
717
test_rev_parse () {
818
d=
@@ -60,7 +70,13 @@ ROOT=$(pwd)
6070

6171
test_expect_success 'setup' '
6272
mkdir -p sub/dir work &&
63-
cp -R .git repo.git
73+
cp -R .git repo.git &&
74+
git checkout -B main &&
75+
test_commit abc &&
76+
git checkout -b side &&
77+
test_commit def &&
78+
git checkout main &&
79+
git worktree add worktree side
6480
'
6581

6682
test_rev_parse toplevel false false true '' .git "$ROOT/.git"
@@ -88,6 +104,45 @@ test_rev_parse -C work -g ../repo.git -b t 'GIT_DIR=../repo.git, core.bare = tru
88104

89105
test_rev_parse -C work -g ../repo.git -b u 'GIT_DIR=../repo.git, core.bare undefined' false false true ''
90106

107+
test_expect_success 'rev-parse --path-format=absolute' '
108+
test_one "." "$ROOT/.git" --path-format=absolute --git-dir &&
109+
test_one "." "$ROOT/.git" --path-format=absolute --git-common-dir &&
110+
test_one "sub/dir" "$ROOT/.git" --path-format=absolute --git-dir &&
111+
test_one "sub/dir" "$ROOT/.git" --path-format=absolute --git-common-dir &&
112+
test_one "worktree" "$ROOT/.git/worktrees/worktree" --path-format=absolute --git-dir &&
113+
test_one "worktree" "$ROOT/.git" --path-format=absolute --git-common-dir &&
114+
test_one "." "$ROOT" --path-format=absolute --show-toplevel &&
115+
test_one "." "$ROOT/.git/objects" --path-format=absolute --git-path objects &&
116+
test_one "." "$ROOT/.git/objects/foo/bar/baz" --path-format=absolute --git-path objects/foo/bar/baz
117+
'
118+
119+
test_expect_success 'rev-parse --path-format=relative' '
120+
test_one "." ".git" --path-format=relative --git-dir &&
121+
test_one "." ".git" --path-format=relative --git-common-dir &&
122+
test_one "sub/dir" "../../.git" --path-format=relative --git-dir &&
123+
test_one "sub/dir" "../../.git" --path-format=relative --git-common-dir &&
124+
test_one "worktree" "../.git/worktrees/worktree" --path-format=relative --git-dir &&
125+
test_one "worktree" "../.git" --path-format=relative --git-common-dir &&
126+
test_one "." "./" --path-format=relative --show-toplevel &&
127+
test_one "." ".git/objects" --path-format=relative --git-path objects &&
128+
test_one "." ".git/objects/foo/bar/baz" --path-format=relative --git-path objects/foo/bar/baz
129+
'
130+
131+
test_expect_success '--path-format=relative does not affect --absolute-git-dir' '
132+
git rev-parse --path-format=relative --absolute-git-dir >actual &&
133+
echo "$ROOT/.git" >expect &&
134+
test_cmp expect actual
135+
'
136+
137+
test_expect_success '--path-format can change in the middle of the command line' '
138+
git rev-parse --path-format=absolute --git-dir --path-format=relative --git-path objects/foo/bar >actual &&
139+
cat >expect <<-EOF &&
140+
$ROOT/.git
141+
.git/objects/foo/bar
142+
EOF
143+
test_cmp expect actual
144+
'
145+
91146
test_expect_success 'git-common-dir from worktree root' '
92147
echo .git >expect &&
93148
git rev-parse --git-common-dir >actual &&

0 commit comments

Comments
 (0)