rev-parse: add --show-superproject-working-tree

In some situations it is useful to know if the given repository is a submodule of another repository. Add the flag --show-superproject-working-tree to git-rev-parse to make it easy to find out if there is a superproject. When no superproject exists, the output will be empty. Signed-off-by: Stefan Beller <sbeller@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Stefan Beller committed Mar 8, 2017 at 15:07 UTC bf0231c66140be5194ac76a4a9f79e949c588293
5 files changed +117
Documentation/git-rev-parse.txt
+6
@@ -261,6 +261,12 @@ print a message to stderr and exit with nonzero status.
261 --show-toplevel::
262 Show the absolute path of the top-level directory.
263
264 +--show-superproject-working-tree
265 + Show the absolute path of the root of the superproject's
266 + working tree (if exists) that uses the current repository as
267 + its submodule. Outputs nothing if the current repository is
268 + not used as a submodule by any project.
269 +
270 --shared-index-path::
271 Show the path to the shared index file in split index mode, or
272 empty if not in split-index mode.
builtin/rev-parse.c
+7
@@ -12,6 +12,7 @@
12 #include "diff.h"
13 #include "revision.h"
14 #include "split-index.h"
15 +#include "submodule.h"
16
17 #define DO_REVS 1
18 #define DO_NOREV 2
@@ -779,6 +780,12 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
780 puts(work_tree);
781 continue;
782 }
783 + if (!strcmp(arg, "--show-superproject-working-tree")) {
784 + const char *superproject = get_superproject_working_tree();
785 + if (superproject)
786 + puts(superproject);
787 + continue;
788 + }
789 if (!strcmp(arg, "--show-prefix")) {
790 if (prefix)
791 puts(prefix);
submodule.c
+82
@@ -1514,3 +1514,85 @@ void absorb_git_dir_into_superproject(const char *prefix,
1514 strbuf_release(&sb);
1515 }
1516 }
1517 +
1518 +const char *get_superproject_working_tree(void)
1519 +{
1520 + struct child_process cp = CHILD_PROCESS_INIT;
1521 + struct strbuf sb = STRBUF_INIT;
1522 + const char *one_up = real_path_if_valid("../");
1523 + const char *cwd = xgetcwd();
1524 + const char *ret = NULL;
1525 + const char *subpath;
1526 + int code;
1527 + ssize_t len;
1528 +
1529 + if (!is_inside_work_tree())
1530 + /*
1531 + * FIXME:
1532 + * We might have a superproject, but it is harder
1533 + * to determine.
1534 + */
1535 + return NULL;
1536 +
1537 + if (!one_up)
1538 + return NULL;
1539 +
1540 + subpath = relative_path(cwd, one_up, &sb);
1541 +
1542 + prepare_submodule_repo_env(&cp.env_array);
1543 + argv_array_pop(&cp.env_array);
1544 +
1545 + argv_array_pushl(&cp.args, "--literal-pathspecs", "-C", "..",
1546 + "ls-files", "-z", "--stage", "--full-name", "--",
1547 + subpath, NULL);
1548 + strbuf_reset(&sb);
1549 +
1550 + cp.no_stdin = 1;
1551 + cp.no_stderr = 1;
1552 + cp.out = -1;
1553 + cp.git_cmd = 1;
1554 +
1555 + if (start_command(&cp))
1556 + die(_("could not start ls-files in .."));
1557 +
1558 + len = strbuf_read(&sb, cp.out, PATH_MAX);
1559 + close(cp.out);
1560 +
1561 + if (starts_with(sb.buf, "160000")) {
1562 + int super_sub_len;
1563 + int cwd_len = strlen(cwd);
1564 + char *super_sub, *super_wt;
1565 +
1566 + /*
1567 + * There is a superproject having this repo as a submodule.
1568 + * The format is <mode> SP <hash> SP <stage> TAB <full name> \0,
1569 + * We're only interested in the name after the tab.
1570 + */
1571 + super_sub = strchr(sb.buf, '\t') + 1;
1572 + super_sub_len = sb.buf + sb.len - super_sub - 1;
1573 +
1574 + if (super_sub_len > cwd_len ||
1575 + strcmp(&cwd[cwd_len - super_sub_len], super_sub))
1576 + die (_("BUG: returned path string doesn't match cwd?"));
1577 +
1578 + super_wt = xstrdup(cwd);
1579 + super_wt[cwd_len - super_sub_len] = '\0';
1580 +
1581 + ret = real_path(super_wt);
1582 + free(super_wt);
1583 + }
1584 + strbuf_release(&sb);
1585 +
1586 + code = finish_command(&cp);
1587 +
1588 + if (code == 128)
1589 + /* '../' is not a git repository */
1590 + return NULL;
1591 + if (code == 0 && len == 0)
1592 + /* There is an unrelated git repository at '../' */
1593 + return NULL;
1594 + if (code)
1595 + die(_("ls-tree returned unexpected return code %d"), code);
1596 +
1597 + return ret;
1598 +}
submodule.h
+8
@@ -93,4 +93,12 @@ extern void prepare_submodule_repo_env(struct argv_array *out);
93 extern void absorb_git_dir_into_superproject(const char *prefix,
94 const char *path,
95 unsigned flags);
96 +
97 +/*
98 + * Return the absolute path of the working tree of the superproject, which this
99 + * project is a submodule of. If this repository is not a submodule of
100 + * another repository, return NULL.
101 + */
102 +extern const char *get_superproject_working_tree(void);
103 +
104 #endif
t/t1500-rev-parse.sh
+14
@@ -116,4 +116,18 @@ test_expect_success 'git-path inside sub-dir' '
116 test_cmp expect actual
117 '
118
119 +test_expect_success 'showing the superproject correctly' '
120 + git rev-parse --show-superproject-working-tree >out &&
121 + test_must_be_empty out &&
122 +
123 + test_create_repo super &&
124 + test_commit -C super test_commit &&
125 + test_create_repo sub &&
126 + test_commit -C sub test_commit &&
127 + git -C super submodule add ../sub dir/sub &&
128 + echo $(pwd)/super >expect &&
129 + git -C super/dir/sub rev-parse --show-superproject-working-tree >out &&
130 + test_cmp expect out
131 +'
132 +
133 test_done