rev-parse: add --prefix option

This makes 'git rev-parse' behave as if it were invoked from the specified subdirectory of a repository, with the difference that any file paths which it prints are prefixed with the full path from the top of the working tree. This is useful for shell scripts where we may want to cd to the top of the working tree but need to handle relative paths given by the user on the command line. Signed-off-by: John Keeping <john@keeping.me.uk> Signed-off-by: Junio C Hamano <gitster@pobox.com>

John Keeping committed Jun 16, 2013 at 15:18 UTC 12b9d32790b40bf3ea49134095619700191abf1f
3 files changed +131 -5
Documentation/git-rev-parse.txt
+16
@@ -59,6 +59,22 @@ OPTIONS
59 If there is no parameter given by the user, use `<arg>`
60 instead.
61
62 +--prefix <arg>::
63 + Behave as if 'git rev-parse' was invoked from the `<arg>`
64 + subdirectory of the working tree. Any relative filenames are
65 + resolved as if they are prefixed by `<arg>` and will be printed
66 + in that form.
67 ++
68 +This can be used to convert arguments to a command run in a subdirectory
69 +so that they can still be used after moving to the top-level of the
70 +repository. For example:
71 ++
72 +----
73 +prefix=$(git rev-parse --show-prefix)
74 +cd "$(git rev-parse --show-toplevel)"
75 +eval "set -- $(git rev-parse --sq --prefix "$prefix" "$@")"
76 +----
77 +
78 --verify::
79 Verify that exactly one parameter is provided, and that it
80 can be turned into a raw 20-byte SHA-1 that can be used to
builtin/rev-parse.c
+19 -5
@@ -212,11 +212,17 @@ static void show_datestring(const char *flag, const char *datestr)
212 show(buffer);
213 }
214
215 -static int show_file(const char *arg)
215 +static int show_file(const char *arg, int output_prefix)
216 {
217 show_default();
218 if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) {
219 - show(arg);
219 + if (output_prefix) {
220 + const char *prefix = startup_info->prefix;
221 + show(prefix_filename(prefix,
222 + prefix ? strlen(prefix) : 0,
223 + arg));
224 + } else
225 + show(arg);
226 return 1;
227 }
228 return 0;
@@ -470,6 +476,7 @@ N_("git rev-parse --parseopt [options] -- [<args>...]\n"
476 int cmd_rev_parse(int argc, const char **argv, const char *prefix)
477 {
478 int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
479 + int output_prefix = 0;
480 unsigned char sha1[20];
481 const char *name = NULL;
482
@@ -503,7 +510,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
510 const char *arg = argv[i];
511
512 if (as_is) {
506 - if (show_file(arg) && as_is < 2)
513 + if (show_file(arg, output_prefix) && as_is < 2)
514 verify_filename(prefix, arg, 0);
515 continue;
516 }
@@ -527,7 +534,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
534 as_is = 2;
535 /* Pass on the "--" if we show anything but files.. */
536 if (filter & (DO_FLAGS | DO_REVS))
530 - show_file(arg);
537 + show_file(arg, 0);
538 continue;
539 }
540 if (!strcmp(arg, "--default")) {
@@ -535,6 +542,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
542 i++;
543 continue;
544 }
545 + if (!strcmp(arg, "--prefix")) {
546 + prefix = argv[i+1];
547 + startup_info->prefix = prefix;
548 + output_prefix = 1;
549 + i++;
550 + continue;
551 + }
552 if (!strcmp(arg, "--revs-only")) {
553 filter &= ~DO_NOREV;
554 continue;
@@ -754,7 +768,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
768 if (verify)
769 die_no_single_rev(quiet);
770 as_is = 1;
757 - if (!show_file(arg))
771 + if (!show_file(arg, output_prefix))
772 continue;
773 verify_filename(prefix, arg, 1);
774 }
t/t1513-rev-parse-prefix.sh new
+96
@@ -0,0 +1,96 @@
1 +#!/bin/sh
2 +
3 +test_description='Tests for rev-parse --prefix'
4 +
5 +. ./test-lib.sh
6 +
7 +test_expect_success 'setup' '
8 + mkdir -p sub1/sub2 &&
9 + echo top >top &&
10 + echo file1 >sub1/file1 &&
11 + echo file2 >sub1/sub2/file2 &&
12 + git add top sub1/file1 sub1/sub2/file2 &&
13 + git commit -m commit
14 +'
15 +
16 +test_expect_success 'empty prefix -- file' '
17 + git rev-parse --prefix "" -- top sub1/file1 >actual &&
18 + cat <<-\EOF >expected &&
19 + --
20 + top
21 + sub1/file1
22 + EOF
23 + test_cmp expected actual
24 +'
25 +
26 +test_expect_success 'valid prefix -- file' '
27 + git rev-parse --prefix sub1/ -- file1 sub2/file2 >actual &&
28 + cat <<-\EOF >expected &&
29 + --
30 + sub1/file1
31 + sub1/sub2/file2
32 + EOF
33 + test_cmp expected actual
34 +'
35 +
36 +test_expect_success 'valid prefix -- ../file' '
37 + git rev-parse --prefix sub1/ -- ../top sub2/file2 >actual &&
38 + cat <<-\EOF >expected &&
39 + --
40 + sub1/../top
41 + sub1/sub2/file2
42 + EOF
43 + test_cmp expected actual
44 +'
45 +
46 +test_expect_success 'empty prefix HEAD:./path' '
47 + git rev-parse --prefix "" HEAD:./top >actual &&
48 + git rev-parse HEAD:top >expected &&
49 + test_cmp expected actual
50 +'
51 +
52 +test_expect_success 'valid prefix HEAD:./path' '
53 + git rev-parse --prefix sub1/ HEAD:./file1 >actual &&
54 + git rev-parse HEAD:sub1/file1 >expected &&
55 + test_cmp expected actual
56 +'
57 +
58 +test_expect_success 'valid prefix HEAD:../path' '
59 + git rev-parse --prefix sub1/ HEAD:../top >actual &&
60 + git rev-parse HEAD:top >expected &&
61 + test_cmp expected actual
62 +'
63 +
64 +test_expect_success 'prefix ignored with HEAD:top' '
65 + git rev-parse --prefix sub1/ HEAD:top >actual &&
66 + git rev-parse HEAD:top >expected &&
67 + test_cmp expected actual
68 +'
69 +
70 +test_expect_success 'disambiguate path with valid prefix' '
71 + git rev-parse --prefix sub1/ file1 >actual &&
72 + cat <<-\EOF >expected &&
73 + sub1/file1
74 + EOF
75 + test_cmp expected actual
76 +'
77 +
78 +test_expect_success 'file and refs with prefix' '
79 + git rev-parse --prefix sub1/ master file1 >actual &&
80 + cat <<-EOF >expected &&
81 + $(git rev-parse master)
82 + sub1/file1
83 + EOF
84 + test_cmp expected actual
85 +'
86 +
87 +test_expect_success 'two-levels deep' '
88 + git rev-parse --prefix sub1/sub2/ -- file2 >actual &&
89 + cat <<-\EOF >expected &&
90 + --
91 + sub1/sub2/file2
92 + EOF
93 + test_cmp expected actual
94 +'
95 +
96 +test_done