From: Lars Hjemli <hjemli@gmail.com>
To: git@vger.kernel.org
Cc: Lars Hjemli <hjemli@gmail.com>
Subject: [PATCH] all: new command used for multi-repo operations
Date: Tue, 22 Jan 2013 22:10:19 +0100 [thread overview]
Message-ID: <1358889019-4554-1-git-send-email-hjemli@gmail.com> (raw)
When working with multiple, unrelated (or loosly related) git repos,
there is often a need to locate all repos with uncommitted work and
perform some action on them (say, commit and push). Before this patch,
such tasks would require manually visiting all repositories, running
`git status` within each one and then decide what to do next.
This mundane task can now be automated by e.g. `git all --dirty status`,
which will find all git repositories below the current directory (even
nested ones), check if they are dirty (as defined by `git diff --quiet &&
git diff --cached --quiet`), and for each dirty repo print the path to the
repo and then execute `git status` within the repo.
The command also honours the option '--clean' which restricts the set of
repos to those which '--dirty' would skip.
Finally, the command to execute within each repo is optional. If none is
given, git-all will just print the path to each repo found.
Signed-off-by: Lars Hjemli <hjemli@gmail.com>
---
Documentation/git-all.txt | 37 ++++++++++++++++
Makefile | 1 +
builtin.h | 1 +
builtin/all.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++
command-list.txt | 1 +
git.c | 1 +
t/t0064-all.sh | 42 +++++++++++++++++++
7 files changed, 188 insertions(+)
create mode 100644 Documentation/git-all.txt
create mode 100644 builtin/all.c
create mode 100755 t/t0064-all.sh
diff --git a/Documentation/git-all.txt b/Documentation/git-all.txt
new file mode 100644
index 0000000..b25f23c
--- /dev/null
+++ b/Documentation/git-all.txt
@@ -0,0 +1,37 @@
+git-all(1)
+==========
+
+NAME
+----
+git-all - Execute a git command in multiple repositories
+
+SYNOPSIS
+--------
+[verse]
+'git all' [--dirty|--clean] [command]
+
+DESCRIPTION
+-----------
+The git-all command is used to locate all git repositoris within the
+current directory tree, and optionally execute a git command in each
+of the found repos.
+
+OPTIONS
+-------
+-c::
+--clean::
+ Only include repositories with a clean worktree.
+
+-d::
+--dirty::
+ Only include repositories with a dirty worktree.
+
+NOTES
+-----
+
+For the purpose of `git-all`, a dirty worktree is defined as a worktree
+with uncommitted changes.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 1b30d7b..8bf0583 100644
--- a/Makefile
+++ b/Makefile
@@ -840,6 +840,7 @@ LIB_OBJS += xdiff-interface.o
LIB_OBJS += zlib.o
BUILTIN_OBJS += builtin/add.o
+BUILTIN_OBJS += builtin/all.o
BUILTIN_OBJS += builtin/annotate.o
BUILTIN_OBJS += builtin/apply.o
BUILTIN_OBJS += builtin/archive.o
diff --git a/builtin.h b/builtin.h
index 7e7bbd6..438c265 100644
--- a/builtin.h
+++ b/builtin.h
@@ -41,6 +41,7 @@ void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, int sha1_valid, char **buf, unsigned long *buf_size);
extern int cmd_add(int argc, const char **argv, const char *prefix);
+extern int cmd_all(int argc, const char **argv, const char *prefix);
extern int cmd_annotate(int argc, const char **argv, const char *prefix);
extern int cmd_apply(int argc, const char **argv, const char *prefix);
extern int cmd_archive(int argc, const char **argv, const char *prefix);
diff --git a/builtin/all.c b/builtin/all.c
new file mode 100644
index 0000000..ee9270d
--- /dev/null
+++ b/builtin/all.c
@@ -0,0 +1,105 @@
+/*
+ * "git all" builtin command.
+ *
+ * Copyright (c) 2013 Lars Hjemli <hjemli@gmail.com>
+ */
+#include "cache.h"
+#include "color.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "parse-options.h"
+
+static int only_dirty;
+static int only_clean;
+char root[PATH_MAX];
+
+static const char * const builtin_all_usage[] = {
+ N_("git all [options] [cmd]"),
+ NULL
+};
+
+static struct option builtin_all_options[] = {
+ OPT_BOOLEAN('c', "clean", &only_clean, N_("only show clean repositories")),
+ OPT_BOOLEAN('d', "dirty", &only_dirty, N_("only show dirty repositories")),
+ OPT_END(),
+};
+
+static int is_dirty()
+{
+ const char *diffidx[] = {"diff", "--quiet", "--cached", NULL};
+ const char *diffwd[] = {"diff", "--quiet", NULL};
+
+ if (run_command_v_opt(diffidx, RUN_GIT_CMD) != 0)
+ return 1;
+ if (run_command_v_opt(diffwd, RUN_GIT_CMD) != 0)
+ return 1;
+ return 0;
+}
+
+static void handle_repo(char *path, const char **argv)
+{
+ int dirty;
+
+ if (path[0] == '.' && path[1] == '/')
+ path += 2;
+ if (only_dirty || only_clean) {
+ dirty = is_dirty();
+ if ((dirty && only_clean) ||
+ (!dirty && only_dirty))
+ return;
+ }
+ if (*argv) {
+ color_fprintf_ln(stdout, GIT_COLOR_YELLOW, "[%s]", path);
+ run_command_v_opt(argv, RUN_GIT_CMD);
+ } else
+ printf("%s\n", path);
+}
+
+static int walk(struct strbuf *path, int argc, const char **argv)
+{
+ DIR *dir;
+ struct dirent *ent;
+ size_t len;
+
+ dir = opendir(path->buf);
+ if (!dir)
+ return errno;
+ strbuf_addstr(path, "/");
+ len = path->len;
+ while ((ent = readdir(dir))) {
+ if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
+ continue;
+ if (!strcmp(ent->d_name, ".git")) {
+ strbuf_setlen(path, len - 1);
+ chdir(path->buf);
+ handle_repo(path->buf, argv);
+ chdir(root);
+ strbuf_addstr(path, "/");
+ continue;
+ }
+ if (ent->d_type != DT_DIR)
+ continue;
+ strbuf_setlen(path, len);
+ strbuf_addstr(path, ent->d_name);
+ walk(path, argc, argv);
+ }
+ closedir(dir);
+ return 0;
+}
+
+int cmd_all(int argc, const char **argv, const char *prefix)
+{
+ struct strbuf path = STRBUF_INIT;
+
+ if (!getcwd(root, sizeof(root)))
+ return 1;
+
+ argc = parse_options(argc, argv, prefix, builtin_all_options,
+ builtin_all_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+
+ unsetenv(GIT_DIR_ENVIRONMENT);
+ unsetenv(GIT_WORK_TREE_ENVIRONMENT);
+
+ strbuf_addstr(&path, ".");
+ return walk(&path, argc, argv);
+}
diff --git a/command-list.txt b/command-list.txt
index 7e8cfec..f955895 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -1,6 +1,7 @@
# List of known git commands.
# command name category [deprecated] [common]
git-add mainporcelain common
+git-all mainporcelain
git-am mainporcelain
git-annotate ancillaryinterrogators
git-apply plumbingmanipulators
diff --git a/git.c b/git.c
index ed66c66..53fd963 100644
--- a/git.c
+++ b/git.c
@@ -304,6 +304,7 @@ static void handle_internal_command(int argc, const char **argv)
const char *cmd = argv[0];
static struct cmd_struct commands[] = {
{ "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
+ { "all", cmd_all },
{ "annotate", cmd_annotate, RUN_SETUP },
{ "apply", cmd_apply, RUN_SETUP_GENTLY },
{ "archive", cmd_archive },
diff --git a/t/t0064-all.sh b/t/t0064-all.sh
new file mode 100755
index 0000000..932e374
--- /dev/null
+++ b/t/t0064-all.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Lars Hjemli
+#
+
+test_description='Test the git-all command'
+
+. ./test-lib.sh
+
+test_expect_success "setup" '
+ test_create_repo clean &&
+ (cd clean && test_commit foo) &&
+ test_create_repo dirty-wt &&
+ (cd dirty-wt && test_commit foo && rm foo.t) &&
+ test_create_repo dirty-idx &&
+ (cd dirty-idx && test_commit foo && git rm foo.t)
+'
+
+test_expect_success "without flags, all repos are included" '
+ echo "." >expect &&
+ echo "clean" >>expect &&
+ echo "dirty-idx" >>expect &&
+ echo "dirty-wt" >>expect &&
+ git all | sort >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success "--dirty only includes dirty repos" '
+ echo "dirty-idx" >expect &&
+ echo "dirty-wt" >>expect &&
+ git all --dirty | sort >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success "--clean only includes clean repos" '
+ echo "." >expect &&
+ echo "clean" >>expect &&
+ git all --clean | sort >actual &&
+ test_cmp expect actual
+'
+
+test_done
--
1.8.1.1.296.g725455c
next reply other threads:[~2013-01-22 21:10 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-01-22 21:10 Lars Hjemli [this message]
2013-01-22 22:01 ` [PATCH] all: new command used for multi-repo operations Junio C Hamano
2013-01-22 23:35 ` Lars Hjemli
2013-01-23 0:13 ` Junio C Hamano
2013-01-23 0:43 ` David Aguilar
2013-01-23 6:44 ` Junio C Hamano
2013-01-23 7:39 ` Lars Hjemli
2013-01-23 6:52 ` Junio C Hamano
[not found] ` <CAFXTnz51czE5iS_pgZyU7SdUwrmZcmLxjFGpCGVhLGJFvW=HRQ@mail.gmail.com>
2013-01-23 8:52 ` Lars Hjemli
2013-01-23 8:39 ` Duy Nguyen
2013-01-23 8:46 ` Lars Hjemli
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1358889019-4554-1-git-send-email-hjemli@gmail.com \
--to=hjemli@gmail.com \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).