VERSION
This is v6.0.0
of git multi … hooray!
SYNOPSIS
There are some options for git multi
itself, in which case it
is invoked as follows:
git multi --<option> [<option_arguments>]
To execute the same git command in multiple repositories, the invocation is as follows:
git multi <git_command> [<git_command_arguments>]
Both ways of running git multi
take an optional, so-called "multi-repo"
argument to limit the operation to the list of repositories in the referenced
"multi-repo", ie. a single GitHub user or a single GitHub organization:
git multi ++<multi_repo> --<option> [<option_arguments>]
and…
git multi ++<multi_repo> <git_command> [<git_command_arguments>]
DESCRIPTION
Convenient way to execute the same git command in a set of related repositories, which could be all GitHub repos for a given user, or all repos for a given GitHub organization.
Multipe users and organizations can be configured, and by default
git multi
operates on all repositories for all users and all orgs;
to limit the operation to a single user or a single org, the optional
++<multi_repo>
argument can be used.
The list of repos for a user or an org is determined via a GitHub API v3 call and cached locally (in binary format) for performance and offline usage.
OPTIONS
- --version
-
print out this script’s version number
- --help
-
you’re looking at it: show the man page
- --html
-
open the HTML version of the man page
- --report
-
report on various repository stats and metrics
- --count
-
print out the count of repos (per type)
- --refresh
-
refresh the list of user & organization repos
- --json
-
output repository details to JSON
- --list
-
print out the names of all repos
- --archived
-
print out the names of all repos
- --forked
-
print out the names of all repos
- --private
-
print out the names of all repos
- --paths
-
print out the full path for each repos
- --missing
-
print out names of repos that haven’t been cloned
- --clone
-
clones missing repositories into
${HOME}/Workarea
(by default) - --stale
-
list repos that have been deleted on github.com
- --excess
-
list repos that don’t exist on github.com
- --spurious
-
list cloned repos whose remote doesn’t match a github.com origin
- --query (args)
-
query GitHub repo metadata for each repository
- --find <ruby>
-
print out the repos for which the Ruby code evaluates to true
- --eval <ruby>
-
execute the given Ruby code in the context of each repo
- --raw <cmd>
-
execute the given shell command inside each git repository
- --shell
-
run an interactive login shell inside each git directory
EXAMPLES
count the number of repos
git multi --list | wc -l
disk usage of each locally cloned repo
git multi --paths | xargs -n 1 du -hs
disk usage using the --raw
option
git multi --raw 'du -hs .'
group and count the repos by GitHub-determined language
git multi --query language | cut -f 2 -d : | sort | uniq -c | sort -n -r
find out the most-used Ruby versions
git multi --raw '[ -f .ruby-version ] && cat .ruby-version' | cut -f 2 -d : | sort | uniq -c | sort -n -r
find GitHub repos without a description
git multi --query description | egrep ': *$'
fetch remote branches for all repos
git multi fetch -p
print out the local branch for each repo (using symbolic-ref
)
git multi symbolic-ref --quiet --short HEAD
print out the local branch for each repo (using rev-parse
)
git multi rev-parse --abbrev-ref=strict HEAD
find all repos for which the origin remote isn’t github.com
git multi config --get remote.origin.url | fgrep -v git@github.com:
a kind of "repository creation" report: count the number of repos created in each quarter
git multi --eval "class ::Time; def quarter() (month.to_f / 3.0).ceil; end; end; puts format('%d-Q%d', created_at.year, created_at.quarter)" | sort | uniq -c
for each repo, list all remote branches, sorted by the "age" of the last commit on each branch
git multi for-each-ref --sort="-authordate" --format="%(refname)%09%(authordate:relative)%09%(authorname)" refs/remotes/origin
same as above, but columnize the generated output (NOTE: replace ^I with CTRL-V/CTRL-I in your terminal)
git multi for-each-ref --sort="-authordate" --format="%(refname)%09%(authordate:relative)%09%(authorname)" refs/remotes/origin | column -t -s "^I"
same as above, but refresh the list of remote branches first
git multi fetch -p ; git multi for-each-ref --sort="-authordate" --format="%(refname)%09%(authordate:relative)%09%(authorname)" refs/remotes/origin
find all Rails projects
git multi --raw '[ -f Gemfile ] && fgrep -q -l rails Gemfile && echo uses Rails' | cat
find all Mongoid dependencies
git multi --raw '[ -f Gemfile.lock ] && egrep -i "^ mongoid (.*)" Gemfile.lock' | column -s: -t
find all projects that have been pushed to in the last week
git multi --find '((Time.now.utc - pushed_at) / 60 / 60 / 24) <= 7'
print out the number of days since the last push to each repository
git multi --eval 'puts "%s - %d days" % [full_name, ((Time.now.utc - pushed_at) / 60 / 60 / 24).ceil]'
find all projects that have seen activity this calendar year
git multi --find 'pushed_at >= Date.civil(Date.today.year, 1, 1).to_time.utc'
print out all webhooks
git multi --eval 'puts client.hooks(full_name).map { |hook| [full_name, hook.name, hook.config.url].join("\t") }'
delete all webhooks with matching URLs (in this case: notify.travis-ci.org
)
git multi --eval 'client.hooks(full_name).find_all { |hook| hook.config.url =~ /notify.travis-ci.org/ }.each { |hook| client.remove_hook(full_name, hook.id) }'
print out all deploy keys
git multi --eval '(keys = client.list_deploy_keys(full_name)).any? && begin print full_name ; print "\t" ; puts keys.map(&:title).sort.join("\t") ; end'
find all organization repositories that depend on a given org repo, e.g. business_rules
git multi --graph | fgrep business_rules
generate a dependency graph of all organization repositories using yuml.me
DEPENDENCIES=$( git multi --graph | ruby -n -e 'parent, children = $_.split(": ") ; puts children.split(" ").map { |child| "[#{parent}]->[#{child}]" }' | tr '\n' ',' ) ; open "http://yuml.me/diagram/scruffy/class/${DEPENDENCIES}"
generate a dependency graph of all organization repositories using Graphviz
git multi --graph | ruby -n -e 'parent, children = $_.split(": ") ; puts children.split(" ").map { |child| "\"#{parent}\"->\"#{child}\";" }' | awk 'BEGIN { print "digraph {\nrankdir=\"LR\";\n" } ; { print ; } END { print "}\n" } ; ' | dot -Tpng > /tmp/ghor.png ; open -a Preview /tmp/ghor.png
QUERY ARGUMENTS
The following is a list of valid arguments for the git multi --query
option:
allow_forking archive_url archived
assignees_url blobs_url branches_url
clone_url collaborators_url comments_url
commits_url compare_url contents_url
contributors_url created_at default_branch
deployments_url description disabled
downloads_url events_url fork
forks forks_count forks_url
full_name git_commits_url git_refs_url
git_tags_url git_url has_downloads
has_issues has_pages has_projects
has_wiki homepage hooks_url
html_url id is_template
issue_comment_url issue_events_url issues_url
keys_url labels_url language
languages_url license merges_url
milestones_url mirror_url name
network_count node_id notifications_url
open_issues open_issues_count organization
owner permissions private
pulls_url pushed_at releases_url
size ssh_url stargazers_count
stargazers_url statuses_url subscribers_count
subscribers_url subscription_url svn_url
tags_url teams_url temp_clone_token
topics trees_url updated_at
url visibility watchers
watchers_count
JQ INTEGRATION
jq
is like sed
for JSON data… all of the above query arguments can be
used in conjunction with jq
to query, filter, map and transform the GitHub
repository attributes stored in the local, binary repository cache; here
are some examples:
# print out each repository's name and its description
git multi --json | jq -r '.[] | .name + ": " + .description'
# print out the name of all "forked" repositories
git multi --json | jq -r '.[] | select(.fork == true) | .full_name'
CHAINED INVOCATION
When git multi
gets its input from a Unix pipeline (as opposed to from a TTY), it constructs an "implicit" multi repo comprised of the "full" repo names it reads from STDIN
(one full repo name per line).
This allows multiple git multi
invocations to be chained, for example by using the --json
or --find
options as follows:
# run a git query or subcommand on repos that aren't archived (on GitHub)
git multi ++<multi_repo> --json | jq -r '.[] | select(."archived" == false) | ."full_name"' | git multi <git_command>
# run a shell command inside repos that have Ruby as their main language
git multi ++<multi_repo> --find 'language == "Ruby"' | git multi --raw 'cat .ruby-version'
FILES
-
${HOME}/Workarea
-
root directory where repos will been cloned
-
${HOME}/.git/multi/repositories.byte
-
local, binary cache of GitHub repository metadata
-
${HOME}/.git/multi/superprojects.config
-
definitions for so-called "superproject" multi-repos
REFERENCES
-
homepage for
git-multi
: https://github.com/pvdb/git-multi -
the GitHub API: https://developer.github.com/v3/
-
the
jq
command-line utility: http://stedolan.github.io/jq/