{"thread":{"id":"32708","subject":"[PATCH v3 0/2] Add git-for-each-repo","startedAt":"2013-01-23T19:59:38Z","lastAt":"2013-01-23T20:54:17Z","messageCount":4,"participants":["Lars Hjemli","Junio C Hamano"],"isPatch":true,"patchVersion":3,"patchTotal":2},"messages":[{"id":"207624","messageId":"1358971180-10652-1-git-send-email-hjemli@gmail.com","threadId":"32708","inReplyTo":null,"subject":"[PATCH v3 0/2] Add git-for-each-repo","fromName":"Lars Hjemli","fromEmail":"hjemli@gmail.com","sentAt":"2013-01-23T19:59:38Z","receivedAt":"2013-01-23T19:59:38Z","isPatch":true,"sender":{"key":"hjemli@gmail.com","avatar":null},"body":"Lars Hjemli (2):\n  for-each-repo: new command used for multi-repo operations\n  git: rewrite `git -a` to become a git-for-each-repo command\n\n .gitignore                          |   1 +\n Documentation/git-for-each-repo.txt |  62 +++++++++++++++++++\n Makefile                            |   1 +\n builtin.h                           |   1 +\n builtin/for-each-repo.c             | 119 ++++++++++++++++++++++++++++++++++++\n git.c                               |  37 +++++++++++\n t/t6400-for-each-repo.sh            |  54 ++++++++++++++++\n 7 files changed, 275 insertions(+)\n create mode 100644 Documentation/git-for-each-repo.txt\n create mode 100644 builtin/for-each-repo.c\n create mode 100755 t/t6400-for-each-repo.sh\n\n-- \n1.8.1.1.350.g3346805\n"},{"id":"207625","messageId":"1358971180-10652-2-git-send-email-hjemli@gmail.com","threadId":"32708","inReplyTo":"1358971180-10652-1-git-send-email-hjemli@gmail.com","subject":"[PATCH v3 1/2] for-each-repo: new command used for multi-repo operations","fromName":"Lars Hjemli","fromEmail":"hjemli@gmail.com","sentAt":"2013-01-23T19:59:39Z","receivedAt":"2013-01-23T19:59:39Z","isPatch":true,"sender":{"key":"hjemli@gmail.com","avatar":null},"body":"When working with multiple, unrelated (or loosly related) git repos,\nthere is often a need to locate all repos with uncommitted work and\nperform some action on them (say, commit and push). Before this patch,\nsuch tasks would require manually visiting all repositories, running\n`git status` within each one and then decide what to do next.\n\nThis mundane task can now be automated by e.g. `git for-each-repo --dirty\nstatus`, which will find all git repositories below the current directory\n(even nested ones), check if they are dirty (as defined by `git diff --quiet\n&& git diff --cached --quiet`), and for each dirty repo print the path to\nthe repo and then execute `git status` within the repo.\n\nThe command also honours the option '--clean' which restricts the set of\nrepos to those which '--dirty' would skip.\n\nFinally, the command to execute within each repo is optional. If none is\ngiven, git-for-each-repo will just print the path to each repo found.\n\nSigned-off-by: Lars Hjemli <hjemli@gmail.com>\n---\n .gitignore                          |   1 +\n Documentation/git-for-each-repo.txt |  62 +++++++++++++++++++\n Makefile                            |   1 +\n builtin.h                           |   1 +\n builtin/for-each-repo.c             | 119 ++++++++++++++++++++++++++++++++++++\n git.c                               |   1 +\n t/t6400-for-each-repo.sh            |  48 +++++++++++++++\n 7 files changed, 233 insertions(+)\n create mode 100644 Documentation/git-for-each-repo.txt\n create mode 100644 builtin/for-each-repo.c\n create mode 100755 t/t6400-for-each-repo.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 63d4904..5036b84 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -56,6 +56,7 @@\n /git-filter-branch\n /git-fmt-merge-msg\n /git-for-each-ref\n+/git-for-each-repo\n /git-format-patch\n /git-fsck\n /git-fsck-objects\ndiff --git a/Documentation/git-for-each-repo.txt b/Documentation/git-for-each-repo.txt\nnew file mode 100644\nindex 0000000..be49e96\n--- /dev/null\n+++ b/Documentation/git-for-each-repo.txt\n@@ -0,0 +1,62 @@\n+git-for-each-repo(1)\n+====================\n+\n+NAME\n+----\n+git-for-each-repo - Execute a git command in multiple repositories\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git for-each-repo' [--all|--clean|--dirty] [command]\n+\n+DESCRIPTION\n+-----------\n+The git-for-each-repo command is used to locate all git repositoris\n+within the current directory tree, and optionally execute a git command\n+in each of the found repos.\n+\n+OPTIONS\n+-------\n+-a::\n+--all::\n+\tInclude both clean and dirty repositories (this is the default\n+\tbehaviour of `git-for-each-repo`).\n+\n+-c::\n+--clean::\n+\tOnly include repositories with a clean worktree.\n+\n+-d::\n+--dirty::\n+\tOnly include repositories with a dirty worktree.\n+\n+EXAMPLES\n+--------\n+\n+Various ways to exploit this command::\n++\n+------------\n+$ git for-each-repo            <1>\n+$ git for-each-repo fetch      <2>\n+$ git for-each-repo -d gui     <3>\n+$ git for-each-repo -c push    <4>\n+------------\n++\n+<1> Print the path to all repos found below the current directory.\n+\n+<2> Fetch updates from default remote in all repos.\n+\n+<3> Start linkgit:git-gui[1] in each repo containing uncommitted changes.\n+\n+<4> Push the current branch in each repo with no uncommited changes.\n+\n+NOTES\n+-----\n+\n+For the purpose of `git-for-each-repo`, a dirty worktree is defined as a\n+worktree with uncommitted changes.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex a786d4c..8c42c17 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -870,6 +870,7 @@ BUILTIN_OBJS += builtin/fetch-pack.o\n BUILTIN_OBJS += builtin/fetch.o\n BUILTIN_OBJS += builtin/fmt-merge-msg.o\n BUILTIN_OBJS += builtin/for-each-ref.o\n+BUILTIN_OBJS += builtin/for-each-repo.o\n BUILTIN_OBJS += builtin/fsck.o\n BUILTIN_OBJS += builtin/gc.o\n BUILTIN_OBJS += builtin/grep.o\ndiff --git a/builtin.h b/builtin.h\nindex 7e7bbd6..02fc712 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -73,6 +73,7 @@ extern int cmd_fetch(int argc, const char **argv, const char *prefix);\n extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\n extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);\n extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix);\n+extern int cmd_for_each_repo(int argc, const char **argv, const char *prefix);\n extern int cmd_format_patch(int argc, const char **argv, const char *prefix);\n extern int cmd_fsck(int argc, const char **argv, const char *prefix);\n extern int cmd_gc(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/for-each-repo.c b/builtin/for-each-repo.c\nnew file mode 100644\nindex 0000000..9bdeb4a\n--- /dev/null\n+++ b/builtin/for-each-repo.c\n@@ -0,0 +1,119 @@\n+/*\n+ * \"git for-each-repo\" builtin command.\n+ *\n+ * Copyright (c) 2013 Lars Hjemli <hjemli@gmail.com>\n+ */\n+#include \"cache.h\"\n+#include \"color.h\"\n+#include \"builtin.h\"\n+#include \"run-command.h\"\n+#include \"parse-options.h\"\n+\n+#define ALL 0\n+#define DIRTY 1\n+#define CLEAN 2\n+\n+static int match;\n+\n+static const char * const builtin_foreachrepo_usage[] = {\n+\tN_(\"git for-each-repo [--all|--clean|--dirty] [cmd]\"),\n+\tNULL\n+};\n+\n+static struct option builtin_foreachrepo_options[] = {\n+\tOPT_SET_INT('a', \"all\", &match, N_(\"match both clean and dirty repositories\"), ALL),\n+\tOPT_SET_INT('c', \"clean\", &match, N_(\"only show clean repositories\"), CLEAN),\n+\tOPT_SET_INT('d', \"dirty\", &match, N_(\"only show dirty repositories\"), DIRTY),\n+\tOPT_END(),\n+};\n+\n+static int get_repo_state()\n+{\n+\tconst char *diffidx[] = {\"diff\", \"--quiet\", \"--cached\", NULL};\n+\tconst char *diffwd[] = {\"diff\", \"--quiet\", NULL};\n+\n+\tif (run_command_v_opt(diffidx, RUN_GIT_CMD) != 0)\n+\t\treturn DIRTY;\n+\tif (run_command_v_opt(diffwd, RUN_GIT_CMD) != 0)\n+\t\treturn DIRTY;\n+\treturn CLEAN;\n+}\n+\n+static void handle_repo(char *path, const char **argv)\n+{\n+\tif (path[0] == '.' && path[1] == '/')\n+\t\tpath += 2;\n+\tif (match != ALL && match != get_repo_state())\n+\t\treturn;\n+\tif (*argv) {\n+\t\tcolor_fprintf_ln(stdout, GIT_COLOR_YELLOW, \"[%s]\", path);\n+\t\trun_command_v_opt(argv, RUN_GIT_CMD);\n+\t} else\n+\t\tprintf(\"%s\\n\", path);\n+}\n+\n+static int walk(struct strbuf *path, int argc, const char **argv)\n+{\n+\tDIR *dir;\n+\tstruct dirent *ent;\n+\tstruct stat st;\n+\tsize_t len;\n+\tconst char *gitdir;\n+\n+\tdir = opendir(path->buf);\n+\tif (!dir)\n+\t\treturn errno;\n+\tstrbuf_addstr(path, \"/\");\n+\tlen = path->len;\n+\twhile ((ent = readdir(dir))) {\n+\t\tif (!strcmp(ent->d_name, \".\") || !strcmp(ent->d_name, \"..\"))\n+\t\t\tcontinue;\n+\t\tif (!strcmp(ent->d_name, \".git\")) {\n+\t\t\tstrbuf_addstr(path, ent->d_name);\n+\t\t\tgitdir = resolve_gitdir(path->buf);\n+\t\t\tif (!gitdir) {\n+\t\t\t\tstrbuf_setlen(path, len - 1);\n+\t\t\t\tstrbuf_addstr(path, \"/\");\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tsetenv(GIT_DIR_ENVIRONMENT, gitdir, 1);\n+\t\t\tstrbuf_setlen(path, len - 1);\n+\t\t\tsetenv(GIT_WORK_TREE_ENVIRONMENT, path->buf, 1);\n+\t\t\thandle_repo(path->buf, argv);\n+\t\t\tstrbuf_addstr(path, \"/\");\n+\t\t\tcontinue;\n+\t\t}\n+\t\tstrbuf_setlen(path, len);\n+\t\tstrbuf_addstr(path, ent->d_name);\n+\t\tswitch (DTYPE(ent)) {\n+\t\tcase DT_UNKNOWN:\n+\t\tcase DT_LNK:\n+\t\t\t/* Use stat() to figure out if this path leads\n+\t\t\t * to a directory - it's  not important if it's\n+\t\t\t * a symlink which gets us there.\n+\t\t\t */\n+\t\t\tif (stat(path->buf, &st) || !S_ISDIR(st.st_mode))\n+\t\t\t\tbreak;\n+\t\t\t/* fallthrough */\n+\t\tcase DT_DIR:\n+\t\t\twalk(path, argc, argv);\n+\t\t\tbreak;\n+\t\t}\n+\t\tstrbuf_setlen(path, len);\n+\t}\n+\tclosedir(dir);\n+\treturn 0;\n+}\n+\n+int cmd_for_each_repo(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\n+\targc = parse_options(argc, argv, prefix,\n+\t\t\t     builtin_foreachrepo_options,\n+\t\t\t     builtin_foreachrepo_usage,\n+\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n+\n+\tstrbuf_addstr(&path, \".\");\n+\treturn walk(&path, argc, argv);\n+}\ndiff --git a/git.c b/git.c\nindex ed66c66..6b53169 100644\n--- a/git.c\n+++ b/git.c\n@@ -337,6 +337,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP },\n \t\t{ \"fmt-merge-msg\", cmd_fmt_merge_msg, RUN_SETUP },\n \t\t{ \"for-each-ref\", cmd_for_each_ref, RUN_SETUP },\n+\t\t{ \"for-each-repo\", cmd_for_each_repo },\n \t\t{ \"format-patch\", cmd_format_patch, RUN_SETUP },\n \t\t{ \"fsck\", cmd_fsck, RUN_SETUP },\n \t\t{ \"fsck-objects\", cmd_fsck, RUN_SETUP },\ndiff --git a/t/t6400-for-each-repo.sh b/t/t6400-for-each-repo.sh\nnew file mode 100755\nindex 0000000..4797629\n--- /dev/null\n+++ b/t/t6400-for-each-repo.sh\n@@ -0,0 +1,48 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2013 Lars Hjemli\n+#\n+\n+test_description='Test the git-for-each-repo command'\n+\n+. ./test-lib.sh\n+\n+test_expect_success \"setup\" '\n+\ttest_create_repo clean &&\n+\t(cd clean && test_commit foo) &&\n+\tgit init --separate-git-dir=.cleansub clean/gitfile &&\n+\t(cd clean/gitfile && test_commit foo && echo bar >>foo.t) &&\n+\ttest_create_repo dirty-wt &&\n+\t(cd dirty-wt && mv .git .linkedgit && ln -s .linkedgit .git &&\n+\t  test_commit foo && rm foo.t) &&\n+\ttest_create_repo dirty-idx &&\n+\t(cd dirty-idx && test_commit foo && git rm foo.t) &&\n+\tmkdir fakedir && mkdir fakedir/.git\n+'\n+\n+test_expect_success \"without flags, all repos are included\" '\n+\techo \".\" >expect &&\n+\techo \"clean\" >>expect &&\n+\techo \"clean/gitfile\" >>expect &&\n+\techo \"dirty-idx\" >>expect &&\n+\techo \"dirty-wt\" >>expect &&\n+\tgit for-each-repo | sort >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"--dirty only includes dirty repos\" '\n+\techo \"clean/gitfile\" >expect &&\n+\techo \"dirty-idx\" >>expect &&\n+\techo \"dirty-wt\" >>expect &&\n+\tgit for-each-repo --dirty | sort >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"--clean only includes clean repos\" '\n+\techo \".\" >expect &&\n+\techo \"clean\" >>expect &&\n+\tgit for-each-repo --clean | sort >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n1.8.1.1.350.g3346805\n"},{"id":"207626","messageId":"1358971180-10652-3-git-send-email-hjemli@gmail.com","threadId":"32708","inReplyTo":"1358971180-10652-1-git-send-email-hjemli@gmail.com","subject":"[PATCH v3 2/2] git: rewrite `git -a` to become a git-for-each-repo command","fromName":"Lars Hjemli","fromEmail":"hjemli@gmail.com","sentAt":"2013-01-23T19:59:40Z","receivedAt":"2013-01-23T19:59:40Z","isPatch":true,"sender":{"key":"hjemli@gmail.com","avatar":null},"body":"With this rewriting, it is now possible to run e.g. `git -ad gui` to\nstart up git-gui in each repo within the current directory which\ncontains uncommited work.\n\nSigned-off-by: Lars Hjemli <hjemli@gmail.com>\n---\n git.c                    | 36 ++++++++++++++++++++++++++++++++++++\n t/t6400-for-each-repo.sh |  6 ++++++\n 2 files changed, 42 insertions(+)\n\ndiff --git a/git.c b/git.c\nindex 6b53169..f933b5d 100644\n--- a/git.c\n+++ b/git.c\n@@ -31,8 +31,42 @@ static void commit_pager_choice(void) {\n \t}\n }\n \n+/*\n+ * Rewrite 'git -ad status' to 'git for-each-repo -d status'\n+ */\n+static int rewrite_foreach_repo(const char ***orig_argv,\n+\t\t\t\tconst char **curr_argv,\n+\t\t\t\tint *curr_argc)\n+{\n+\tconst char **new_argv;\n+\tchar *tmp;\n+\tint new_argc, curr_pos, i, j;\n+\n+\tcurr_pos = curr_argv - *orig_argv;\n+\tif (strlen(curr_argv[0]) == 2) {\n+\t\tcurr_argv[0] = \"for-each-repo\";\n+\t\treturn curr_pos - 1;\n+\t}\n+\n+\tnew_argc = curr_pos + *curr_argc + 1;\n+\tnew_argv = xmalloc(new_argc * sizeof(void *));\n+\tfor (i = j = 0; j < new_argc; i++, j++) {\n+\t\tif (i == curr_pos) {\n+\t\t\tasprintf(&tmp, \"-%s\", (*orig_argv)[i] + 2);\n+\t\t\tnew_argv[j] = \"for-each-repo\";\n+\t\t\tnew_argv[++j] = tmp;\n+\t\t} else {\n+\t\t\tnew_argv[j] = (*orig_argv)[i];\n+\t\t}\n+\t}\n+\t*orig_argv = new_argv;\n+\t(*curr_argc)++;\n+\treturn curr_pos;\n+}\n+\n static int handle_options(const char ***argv, int *argc, int *envchanged)\n {\n+\tconst char ***pargv = argv;\n \tconst char **orig_argv = *argv;\n \n \twhile (*argc > 0) {\n@@ -143,6 +177,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tsetenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, \"0\", 1);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n+\t\t} else if (!strncmp(cmd, \"-a\", 2)) {\n+\t\t\treturn rewrite_foreach_repo(pargv, *argv, argc);\n \t\t} else {\n \t\t\tfprintf(stderr, \"Unknown option: %s\\n\", cmd);\n \t\t\tusage(git_usage_string);\ndiff --git a/t/t6400-for-each-repo.sh b/t/t6400-for-each-repo.sh\nindex 4797629..b501605 100755\n--- a/t/t6400-for-each-repo.sh\n+++ b/t/t6400-for-each-repo.sh\n@@ -27,6 +27,8 @@ test_expect_success \"without flags, all repos are included\" '\n \techo \"dirty-idx\" >>expect &&\n \techo \"dirty-wt\" >>expect &&\n \tgit for-each-repo | sort >actual &&\n+\ttest_cmp expect actual &&\n+\tgit -a | sort >actual &&\n \ttest_cmp expect actual\n '\n \n@@ -35,6 +37,8 @@ test_expect_success \"--dirty only includes dirty repos\" '\n \techo \"dirty-idx\" >>expect &&\n \techo \"dirty-wt\" >>expect &&\n \tgit for-each-repo --dirty | sort >actual &&\n+\ttest_cmp expect actual &&\n+\tgit -ad | sort >actual &&\n \ttest_cmp expect actual\n '\n \n@@ -42,6 +46,8 @@ test_expect_success \"--clean only includes clean repos\" '\n \techo \".\" >expect &&\n \techo \"clean\" >>expect &&\n \tgit for-each-repo --clean | sort >actual &&\n+\ttest_cmp expect actual &&\n+\tgit -ac | sort >actual &&\n \ttest_cmp expect actual\n '\n \n-- \n1.8.1.1.350.g3346805\n"},{"id":"207636","messageId":"7v1udbhb7q.fsf@alter.siamese.dyndns.org","threadId":"32708","inReplyTo":"1358971180-10652-2-git-send-email-hjemli@gmail.com","subject":"Re: [PATCH v3 1/2] for-each-repo: new command used for multi-repo operations","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-01-23T20:54:17Z","receivedAt":"2013-01-23T20:54:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Lars Hjemli <hjemli@gmail.com> writes:\n\n> diff --git a/Documentation/git-for-each-repo.txt b/Documentation/git-for-each-repo.txt\n> new file mode 100644\n> index 0000000..be49e96\n> --- /dev/null\n> +++ b/Documentation/git-for-each-repo.txt\n> @@ -0,0 +1,62 @@\n> +git-for-each-repo(1)\n> +====================\n> +\n> +NAME\n> +----\n> +git-for-each-repo - Execute a git command in multiple repositories\n\n\"multiple non-bare repositories\", I think.\n\n> +\n> +SYNOPSIS\n> +--------\n> +[verse]\n> +'git for-each-repo' [--all|--clean|--dirty] [command]\n> +\n> +DESCRIPTION\n> +-----------\n> +The git-for-each-repo command is used to locate all git repositoris\n\nLikewise; \"all non-bare Git repositories\".\n\n> diff --git a/t/t6400-for-each-repo.sh b/t/t6400-for-each-repo.sh\n> new file mode 100755\n> index 0000000..4797629\n> --- /dev/null\n> +++ b/t/t6400-for-each-repo.sh\n> @@ -0,0 +1,48 @@\n> +#!/bin/sh\n> +#\n> +# Copyright (c) 2013 Lars Hjemli\n> +#\n> +\n> +test_description='Test the git-for-each-repo command'\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success \"setup\" '\n> +\ttest_create_repo clean &&\n> +\t(cd clean && test_commit foo) &&\n> +\tgit init --separate-git-dir=.cleansub clean/gitfile &&\n> +\t(cd clean/gitfile && test_commit foo && echo bar >>foo.t) &&\n> +\ttest_create_repo dirty-wt &&\n> +\t(cd dirty-wt && mv .git .linkedgit && ln -s .linkedgit .git &&\n> +\t  test_commit foo && rm foo.t) &&\n> +\ttest_create_repo dirty-idx &&\n> +\t(cd dirty-idx && test_commit foo && git rm foo.t) &&\n> +\tmkdir fakedir && mkdir fakedir/.git\n> +'\n> +\n> +test_expect_success \"without flags, all repos are included\" '\n> +\techo \".\" >expect &&\n> +\techo \"clean\" >>expect &&\n> +\techo \"clean/gitfile\" >>expect &&\n> +\techo \"dirty-idx\" >>expect &&\n> +\techo \"dirty-wt\" >>expect &&\n> +\tgit for-each-repo | sort >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success \"--dirty only includes dirty repos\" '\n> +\techo \"clean/gitfile\" >expect &&\n> +\techo \"dirty-idx\" >>expect &&\n> +\techo \"dirty-wt\" >>expect &&\n> +\tgit for-each-repo --dirty | sort >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success \"--clean only includes clean repos\" '\n> +\techo \".\" >expect &&\n> +\techo \"clean\" >>expect &&\n> +\tgit for-each-repo --clean | sort >actual &&\n> +\ttest_cmp expect actual\n> +'\n\nPlease add tests to show some command executions (e.g. test output\nfrom \"git ls-files\", or something).\n\n> +static void handle_repo(char *path, const char **argv)\n> +{\n> +\tif (path[0] == '.' && path[1] == '/')\n> +\t\tpath += 2;\n> +\tif (match != ALL && match != get_repo_state())\n> +\t\treturn;\n> +\tif (*argv) {\n> +\t\tcolor_fprintf_ln(stdout, GIT_COLOR_YELLOW, \"[%s]\", path);\n> +\t\trun_command_v_opt(argv, RUN_GIT_CMD);\n\nThis seems to allow people to run only a single Git subcommand,\nwhich is probably not what most people want to see.  Don't we want\nto support something as simple as this?\n\n\tgit for-each-repository sh -c \"ls *.c\"\n\n> +\t} else\n> +\t\tprintf(\"%s\\n\", path);\n\nAssuming that the non *argv case is for consumption by programs and\nscripts (similar to the way \"ls-files\" output is piped to downstream),\nwe prefer to (1) support \"-z\" so that \"xargs -0\" can read paths with\nfunny characters, and (2) use quote_c_style() from quote.c when \"-z\"\nis not in effect.\n\n> +}\n> + ...\n> +\t\t\tsetenv(GIT_DIR_ENVIRONMENT, gitdir, 1);\n> +\t\t\tstrbuf_setlen(path, len - 1);\n> +\t\t\tsetenv(GIT_WORK_TREE_ENVIRONMENT, path->buf, 1);\n> +\t\t\thandle_repo(path->buf, argv);\n\nWhen you are only showing the path to a repository, I do not think\nyou want setenv() or chdir() at all. Shouldn't these be done inside\nhandle_repo() function?  As you are only dealing with non-bare\nrepositories (and that is what you print in \"listing only\" mode\nanyway), handle_repo() can borrow path (not path->buf) and append\nand strip \"/.git\" as needed.\n\nAlso, while it is a good idea to protect this program from stray\nGIT_DIR/GIT_WORK_TREE the user may have in the environment when this\nprogram is started, I think this is not enough, if you allow the\n*argv commands to run worktree related operations in each repository\nyou discover.  You would need to chdir() to the top of the working\ntree.\n\nThe run-command API lets you specify custom environment only for the\nchild process without affecting yourself by setting .env member of\nthe child_process structure, so we may want to use that instead of\ndoing setenv() on ourselves (and letting it inherited by the child).\n"}]}