{"thread":{"id":"32698","subject":"[PATCH v2] all: new command used for multi-repo operations","startedAt":"2013-01-23T08:12:47Z","lastAt":"2013-01-23T20:17:06Z","messageCount":9,"participants":["Lars Hjemli","Duy Nguyen","Junio C Hamano","Jens Lehmann"],"isPatch":true,"patchVersion":2,"patchTotal":null},"messages":[{"id":"207571","messageId":"1358928767-16283-1-git-send-email-hjemli@gmail.com","threadId":"32698","inReplyTo":null,"subject":"[PATCH v2] all: new command used for multi-repo operations","fromName":"Lars Hjemli","fromEmail":"hjemli@gmail.com","sentAt":"2013-01-23T08:12:47Z","receivedAt":"2013-01-23T08:12:47Z","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 all --dirty status`,\nwhich will find all git repositories below the current directory (even\nnested ones), check if they are dirty (as defined by `git diff --quiet &&\ngit diff --cached --quiet`), and for each dirty repo print the path to the\nrepo 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-all will just print the path to each repo found.\n\nSigned-off-by: Lars Hjemli <hjemli@gmail.com>\n---\n\nChanges since v1:\n* uses setenv() instead of chdir(), which fixes .gitfile handling\n* uses DTYPE() and stat(), which fixes NO_D_TYPE_IN_DIRENT platforms\n* uses OPT_SET_INT() instead of OPT_BOOLEAN\n* support for --all (complements --clean/--dirty)\n* removed from command-list.txt\n* added to .gitignore\n\nI've not yet renamed the command. If it should be changed to 'git\nfor-each-repo', I'm tempted to make a patch which transforms\n`git -ad status` into `git for-each-repo -d status`.\n\n .gitignore                |   1 +\n Documentation/git-all.txt |  42 ++++++++++++++++++\n Makefile                  |   1 +\n builtin.h                 |   1 +\n builtin/all.c             | 110 ++++++++++++++++++++++++++++++++++++++++++++++\n git.c                     |   1 +\n t/t0064-all.sh            |  46 +++++++++++++++++++\n 7 files changed, 202 insertions(+)\n create mode 100644 Documentation/git-all.txt\n create mode 100644 builtin/all.c\n create mode 100755 t/t0064-all.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex aa258a6..27118d7 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -10,6 +10,7 @@\n /git\n /git-add\n /git-add--interactive\n+/git-all\n /git-am\n /git-annotate\n /git-apply\ndiff --git a/Documentation/git-all.txt b/Documentation/git-all.txt\nnew file mode 100644\nindex 0000000..baaa57e\n--- /dev/null\n+++ b/Documentation/git-all.txt\n@@ -0,0 +1,42 @@\n+git-all(1)\n+==========\n+\n+NAME\n+----\n+git-all - Execute a git command in multiple repositories\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git all' [--all|--clean|--dirty] [command]\n+\n+DESCRIPTION\n+-----------\n+The git-all command is used to locate all git repositoris within the\n+current directory tree, and optionally execute a git command in each\n+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-all`).\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+NOTES\n+-----\n+\n+For the purpose of `git-all`, a dirty worktree is defined as a worktree\n+with uncommitted changes.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 1b30d7b..8bf0583 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -840,6 +840,7 @@ LIB_OBJS += xdiff-interface.o\n LIB_OBJS += zlib.o\n \n BUILTIN_OBJS += builtin/add.o\n+BUILTIN_OBJS += builtin/all.o\n BUILTIN_OBJS += builtin/annotate.o\n BUILTIN_OBJS += builtin/apply.o\n BUILTIN_OBJS += builtin/archive.o\ndiff --git a/builtin.h b/builtin.h\nindex 7e7bbd6..438c265 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -41,6 +41,7 @@ void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);\n extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, int sha1_valid, char **buf, unsigned long *buf_size);\n \n extern int cmd_add(int argc, const char **argv, const char *prefix);\n+extern int cmd_all(int argc, const char **argv, const char *prefix);\n extern int cmd_annotate(int argc, const char **argv, const char *prefix);\n extern int cmd_apply(int argc, const char **argv, const char *prefix);\n extern int cmd_archive(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/all.c b/builtin/all.c\nnew file mode 100644\nindex 0000000..b170b26\n--- /dev/null\n+++ b/builtin/all.c\n@@ -0,0 +1,110 @@\n+/*\n+ * \"git all\" 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_all_usage[] = {\n+\tN_(\"git all [--all|--clean|--dirty] [cmd]\"),\n+\tNULL\n+};\n+\n+static struct option builtin_all_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+\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\tsetenv(GIT_DIR_ENVIRONMENT, path->buf, 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\t\t/* Use stat() instead of lstat(), since we want to\n+\t\t\t * know if we can follow this path into another\n+\t\t\t * directory - it's  not important if it's actually\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_all(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\n+\targc = parse_options(argc, argv, prefix, builtin_all_options,\n+\t\t\t     builtin_all_usage, 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..53fd963 100644\n--- a/git.c\n+++ b/git.c\n@@ -304,6 +304,7 @@ static void handle_internal_command(int argc, const char **argv)\n \tconst char *cmd = argv[0];\n \tstatic struct cmd_struct commands[] = {\n \t\t{ \"add\", cmd_add, RUN_SETUP | NEED_WORK_TREE },\n+\t\t{ \"all\", cmd_all },\n \t\t{ \"annotate\", cmd_annotate, RUN_SETUP },\n \t\t{ \"apply\", cmd_apply, RUN_SETUP_GENTLY },\n \t\t{ \"archive\", cmd_archive },\ndiff --git a/t/t0064-all.sh b/t/t0064-all.sh\nnew file mode 100755\nindex 0000000..3738ab2\n--- /dev/null\n+++ b/t/t0064-all.sh\n@@ -0,0 +1,46 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2013 Lars Hjemli\n+#\n+\n+test_description='Test the git-all 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 && 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+'\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 all | 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 all --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 all --clean | sort >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n1.8.1.1.296.g725455c\n"},{"id":"207575","messageId":"CACsJy8DskoCi9Lg+HW0JeQBe4HX-bMXNHUgfrsg+DoqBN9-ntQ@mail.gmail.com","threadId":"32698","inReplyTo":"1358928767-16283-1-git-send-email-hjemli@gmail.com","subject":"Re: [PATCH v2] all: new command used for multi-repo operations","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2013-01-23T08:55:31Z","receivedAt":"2013-01-23T08:55:31Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Jan 23, 2013 at 3:12 PM, Lars Hjemli <hjemli@gmail.com> wrote:\n> +NAME\n> +----\n> +git-all - Execute a git command in multiple repositories\n\nI agree with Junio \"git-all\" is too generic. Maybe \"git-for-each-repo\"\n\n> +static int get_repo_state()\n> +{\n> +       const char *diffidx[] = {\"diff\", \"--quiet\", \"--cached\", NULL};\n> +       const char *diffwd[] = {\"diff\", \"--quiet\", NULL};\n> +\n> +       if (run_command_v_opt(diffidx, RUN_GIT_CMD) != 0)\n> +               return DIRTY;\n> +       if (run_command_v_opt(diffwd, RUN_GIT_CMD) != 0)\n> +               return DIRTY;\n> +       return CLEAN;\n> +}\n\nPerhaps we could add the subrepo's object data to the in-memory object\ndatabase of git-all, then do the diff without launching new commands?\n\n> +static int walk(struct strbuf *path, int argc, const char **argv)\n> +{\n> +       DIR *dir;\n> +       struct dirent *ent;\n> +       struct stat st;\n> +       size_t len;\n> +\n> +       dir = opendir(path->buf);\n> +       if (!dir)\n> +               return errno;\n> +       strbuf_addstr(path, \"/\");\n> +       len = path->len;\n> +       while ((ent = readdir(dir))) {\n> +               if (!strcmp(ent->d_name, \".\") || !strcmp(ent->d_name, \"..\"))\n> +                       continue;\n> +               if (!strcmp(ent->d_name, \".git\")) {\n> +                       strbuf_addstr(path, ent->d_name);\n> +                       setenv(GIT_DIR_ENVIRONMENT, path->buf, 1);\n> +                       strbuf_setlen(path, len - 1);\n> +                       setenv(GIT_WORK_TREE_ENVIRONMENT, path->buf, 1);\n> +                       handle_repo(path->buf, argv);\n> +                       strbuf_addstr(path, \"/\");\n> +                       continue;\n> +               }\n> +               strbuf_setlen(path, len);\n> +               strbuf_addstr(path, ent->d_name);\n> +               switch (DTYPE(ent)) {\n> +               case DT_UNKNOWN:\n> +                       /* Use stat() instead of lstat(), since we want to\n> +                        * know if we can follow this path into another\n> +                        * directory - it's  not important if it's actually\n> +                        * a symlink which gets us there.\n> +                        */\n> +                       if (stat(path->buf, &st) || !S_ISDIR(st.st_mode))\n> +                               break;\n> +                       /* fallthrough */\n> +               case DT_DIR:\n> +                       walk(path, argc, argv);\n> +                       break;\n> +               }\n> +               strbuf_setlen(path, len);\n> +       }\n> +       closedir(dir);\n> +       return 0;\n> +}\n\nI'm not a user of this command so this is more of bikeshedding. I\nthink we should have an option to list repos listed in index. For\ndirectory walk, how about reusing fill_directory() to do the job for\nyou? You could then limit repositories by name. \"ls-files -o\" code\nshould be very similar.\n-- \nDuy\n"},{"id":"207577","messageId":"CAFXTnz6mU999DBQG9mPKyjtit4e4WF=bUf_E8XwGR8d5sUPO0w@mail.gmail.com","threadId":"32698","inReplyTo":"CACsJy8DskoCi9Lg+HW0JeQBe4HX-bMXNHUgfrsg+DoqBN9-ntQ@mail.gmail.com","subject":"Re: [PATCH v2] all: new command used for multi-repo operations","fromName":"Lars Hjemli","fromEmail":"hjemli@gmail.com","sentAt":"2013-01-23T09:24:19Z","receivedAt":"2013-01-23T09:24:19Z","isPatch":true,"sender":{"key":"hjemli@gmail.com","avatar":null},"body":"On Wed, Jan 23, 2013 at 9:55 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n> Perhaps we could add the subrepo's object data to the in-memory object\n> database of git-all, then do the diff without launching new commands?\n\nThe `git all` command is regularly invoked outside of git repos, so\nI'm not sure if this would work.\n\n>\n> I'm not a user of this command so this is more of bikeshedding. I\n> think we should have an option to list repos listed in index.\n\ngit-submodule uses something like `git ls-files --stage|grep\n\"^160000\"`. Having a better way to achieve this would be nice, but I\ndon't think it is a job for 'git [all|for-each-repo|ls-repo].\n\n> For\n> directory walk, how about reusing fill_directory() to do the job for\n> you? You could then limit repositories by name. \"ls-files -o\" code\n> should be very similar.\n\nA cursory look into dir.c seems to indicate that this could work\n(possibly except for get_index_dtype()), but it would also load the\ncomplete directory tree (which could be extremly big) into ram,\nincluding file entries (which is not necessary) while dropping '.git'\nentries (which is what we're looking for).\n\n-- \nlarsh\n"},{"id":"207580","messageId":"CACsJy8BHZ--n6HeuGt3xk13vwE9ec=dQYhiF_FpdKF47yo=Gmw@mail.gmail.com","threadId":"32698","inReplyTo":"CAFXTnz6mU999DBQG9mPKyjtit4e4WF=bUf_E8XwGR8d5sUPO0w@mail.gmail.com","subject":"Re: [PATCH v2] all: new command used for multi-repo operations","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2013-01-23T10:15:16Z","receivedAt":"2013-01-23T10:15:16Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Jan 23, 2013 at 4:24 PM, Lars Hjemli <hjemli@gmail.com> wrote:\n> On Wed, Jan 23, 2013 at 9:55 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n>> Perhaps we could add the subrepo's object data to the in-memory object\n>> database of git-all, then do the diff without launching new commands?\n>\n> The `git all` command is regularly invoked outside of git repos, so\n> I'm not sure if this would work.\n\nWe could initialize an empty object database in memory. But my\nsuggestion is probably off topic. I thought this was about a a git\nrepository with a collection of submodules. If there are no\nconnections between repos, perhaps git-all fits better as a subcommand\nof git-repo:\n\nhttp://source.android.com/source/version-control.html\n-- \nDuy\n"},{"id":"207607","messageId":"7v622nj0ys.fsf@alter.siamese.dyndns.org","threadId":"32698","inReplyTo":"1358928767-16283-1-git-send-email-hjemli@gmail.com","subject":"Re: [PATCH v2] all: new command used for multi-repo operations","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-01-23T16:52:43Z","receivedAt":"2013-01-23T16:52:43Z","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> +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> +\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\tsetenv(GIT_DIR_ENVIRONMENT, path->buf, 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\t\t/* Use stat() instead of lstat(), since we want to\n> +\t\t\t * know if we can follow this path into another\n> +\t\t\t * directory - it's  not important if it's actually\n> +\t\t\t * a symlink which gets us there.\n> +\t\t\t */\n\nThis is wrong if you are on a platform that does have d_type, no?\nIt may say it is a symbolic link, and until you stat you wouldn't\nknow if it may lead to a directory.  You can add \"case DT_LNK:\" that\nbehaves the same as DT_UNKNOWN, I think.\n\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\nBut I still do not think this loop is correct.  In a repository that\nhas a working tree, you would learn that directory $D has $D/.git in\nit, feed $D to handle_repo(), and then descend into $D/.git/objects/,\n$D/.git/refs, and other random directories to see if you can find\nother repositories.  That is just not right.\n\nIf this check were doing something like \"The directory $D is worth\nhanding to handle_repo() if it has all of the following: objects/,\nrefs/ and HEAD that either points inside refs/ or 40-hex.\", then it\nwould make a lot more sense to me, including the part that goes on\nto check sibling directories.  As a bonus side effect, it will give\nyou a support for bare repositories for free.\n"},{"id":"207609","messageId":"7vwqv3hlu7.fsf@alter.siamese.dyndns.org","threadId":"32698","inReplyTo":"7v622nj0ys.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v2] all: new command used for multi-repo operations","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-01-23T17:04:48Z","receivedAt":"2013-01-23T17:04:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> But I still do not think this loop is correct.  In a repository that\n> has a working tree, you would learn that directory $D has $D/.git in\n> it, feed $D to handle_repo(), and then descend into $D/.git/objects/,\n> $D/.git/refs, and other random directories to see if you can find\n> other repositories....\n\nAhh, no, you don't.\n\nI still think calling is_git_directory() on $D + \"/.git\" would be a\nbetter implementation, though.\n"},{"id":"207613","messageId":"CAFXTnz5VJCwk7DWMe_9kZdY4OfLzbj0eqVB976DUT_bogzr4sg@mail.gmail.com","threadId":"32698","inReplyTo":"7v622nj0ys.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v2] all: new command used for multi-repo operations","fromName":"Lars Hjemli","fromEmail":"hjemli@gmail.com","sentAt":"2013-01-23T18:19:01Z","receivedAt":"2013-01-23T18:19:01Z","isPatch":true,"sender":{"key":"hjemli@gmail.com","avatar":null},"body":"On Wed, Jan 23, 2013 at 5:52 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Lars Hjemli <hjemli@gmail.com> writes:\n>\n>> +static int walk(struct strbuf *path, int argc, const char **argv)\n>> +{\n>> +     DIR *dir;\n>> +     struct dirent *ent;\n>> +     struct stat st;\n>> +     size_t len;\n>> +\n>> +     dir = opendir(path->buf);\n>> +     if (!dir)\n>> +             return errno;\n>> +     strbuf_addstr(path, \"/\");\n>> +     len = path->len;\n>> +     while ((ent = readdir(dir))) {\n>> +             if (!strcmp(ent->d_name, \".\") || !strcmp(ent->d_name, \"..\"))\n>> +                     continue;\n>> +             if (!strcmp(ent->d_name, \".git\")) {\n>> +                     strbuf_addstr(path, ent->d_name);\n>> +                     setenv(GIT_DIR_ENVIRONMENT, path->buf, 1);\n>> +                     strbuf_setlen(path, len - 1);\n>> +                     setenv(GIT_WORK_TREE_ENVIRONMENT, path->buf, 1);\n>> +                     handle_repo(path->buf, argv);\n>> +                     strbuf_addstr(path, \"/\");\n>> +                     continue;\n>> +             }\n>> +             strbuf_setlen(path, len);\n>> +             strbuf_addstr(path, ent->d_name);\n>> +             switch (DTYPE(ent)) {\n>> +             case DT_UNKNOWN:\n>> +                     /* Use stat() instead of lstat(), since we want to\n>> +                      * know if we can follow this path into another\n>> +                      * directory - it's  not important if it's actually\n>> +                      * a symlink which gets us there.\n>> +                      */\n>\n> This is wrong if you are on a platform that does have d_type, no?\n> It may say it is a symbolic link, and until you stat you wouldn't\n> know if it may lead to a directory.  You can add \"case DT_LNK:\" that\n> behaves the same as DT_UNKNOWN, I think.\n\nYeah, that seems right, thanks.\n\n-- \nlarsh\n"},{"id":"207614","messageId":"CAFXTnz7r79JYXQBBcpiOPtUzeafuN3fa82F5rGXd-Hr7tSAEOw@mail.gmail.com","threadId":"32698","inReplyTo":"7vwqv3hlu7.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v2] all: new command used for multi-repo operations","fromName":"Lars Hjemli","fromEmail":"hjemli@gmail.com","sentAt":"2013-01-23T18:29:08Z","receivedAt":"2013-01-23T18:29:08Z","isPatch":true,"sender":{"key":"hjemli@gmail.com","avatar":null},"body":"On Wed, Jan 23, 2013 at 6:04 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> But I still do not think this loop is correct.  In a repository that\n>> has a working tree, you would learn that directory $D has $D/.git in\n>> it, feed $D to handle_repo(), and then descend into $D/.git/objects/,\n>> $D/.git/refs, and other random directories to see if you can find\n>> other repositories....\n>\n> Ahh, no, you don't.\n>\n> I still think calling is_git_directory() on $D + \"/.git\" would be a\n> better implementation, though.\n\nExcept for the .gitfile case, which is_git_directory() doesn't seem to\nhandle. I guess I can invoke read_gitfile() when i see that .git is a\nfile.\n\n-- \nlarsh\n"},{"id":"207630","messageId":"51004542.8010708@web.de","threadId":"32698","inReplyTo":"CACsJy8DskoCi9Lg+HW0JeQBe4HX-bMXNHUgfrsg+DoqBN9-ntQ@mail.gmail.com","subject":"Re: [PATCH v2] all: new command used for multi-repo operations","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-01-23T20:17:06Z","receivedAt":"2013-01-23T20:17:06Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Am 23.01.2013 09:55, schrieb Duy Nguyen:\n> On Wed, Jan 23, 2013 at 3:12 PM, Lars Hjemli <hjemli@gmail.com> wrote:\n>> +NAME\n>> +----\n>> +git-all - Execute a git command in multiple repositories\n> \n> I agree with Junio \"git-all\" is too generic.\n\n+1\n\n>> +static int get_repo_state()\n>> +{\n>> +       const char *diffidx[] = {\"diff\", \"--quiet\", \"--cached\", NULL};\n>> +       const char *diffwd[] = {\"diff\", \"--quiet\", NULL};\n>> +\n>> +       if (run_command_v_opt(diffidx, RUN_GIT_CMD) != 0)\n>> +               return DIRTY;\n>> +       if (run_command_v_opt(diffwd, RUN_GIT_CMD) != 0)\n>> +               return DIRTY;\n>> +       return CLEAN;\n>> +}\n> \n> Perhaps we could add the subrepo's object data to the in-memory object\n> database of git-all, then do the diff without launching new commands?\n\nYou could do that for the \"--cached\" case, but not for the plain diff.\nBut I think forking a \"git status --porcelain -uno\" and testing if it\nproduced any output should do the trick with a single fork.\n"}]}