git-multi 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.pryrc +39 -0
- data/.rubocop.yml +2 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +50 -0
- data/Rakefile +28 -0
- data/bin/console +9 -0
- data/bin/setup +13 -0
- data/contrib/git-dash/git-dash.erb +35 -0
- data/contrib/git-dash/git-dash.rb +82 -0
- data/doc/git-multi.man +143 -0
- data/exe/git-multi +42 -0
- data/git-multi.gemspec +32 -0
- data/lib/ext/commify.rb +11 -0
- data/lib/ext/dir.rb +10 -0
- data/lib/ext/notify.rb +20 -0
- data/lib/ext/sawyer/resource.rb +5 -0
- data/lib/ext/string.rb +14 -0
- data/lib/ext/utils.rb +31 -0
- data/lib/git/hub.rb +95 -0
- data/lib/git/multi/commands.rb +189 -0
- data/lib/git/multi/settings.rb +89 -0
- data/lib/git/multi/version.rb +32 -0
- data/lib/git/multi.rb +190 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8ab3d13fb8baa05e875b2bc7139802bff606cab488e33bc59ece66f58cdaad6f
|
4
|
+
data.tar.gz: 88447dad2a38f602c07d4c0e61633669cfbffbc4fb85c14d7f2ecf5e0a5974bd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6d0eab7a6c0ae9d59c8f4618b4c855012db420561a7a21eb9b145a1b4e12e643b957bd54584f7939d755419ffc1a3caecad3e5345223e1a52ecde0499c27bc97
|
7
|
+
data.tar.gz: 7f887eac42a5f845203753b9e3cd7d1990250fac70adb98c478dffa85456a1eb1ba369c1bdbc9c7055ae09ad235e141c683def563f970383445d0bd31e0a60bd
|
data/.gitignore
ADDED
data/.pryrc
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# this loads all of "git-multi"
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'git/multi'
|
5
|
+
|
6
|
+
# this loads all "git multi" contribs
|
7
|
+
Dir.glob File.join(__dir__, 'contrib', '*', '*.rb'), &method(:require)
|
8
|
+
|
9
|
+
# configure a logger
|
10
|
+
require 'logger'
|
11
|
+
logger = Logger.new(STDOUT)
|
12
|
+
logger.level = Logger::INFO
|
13
|
+
|
14
|
+
# configure Octokit middleware with logger
|
15
|
+
require 'octokit'
|
16
|
+
Octokit.middleware.response :logger, logger
|
17
|
+
|
18
|
+
# enumerator for Faraday middleware apps
|
19
|
+
def (middleware = Octokit.middleware).each_app
|
20
|
+
Enumerator.new do |yielder|
|
21
|
+
next_app = app
|
22
|
+
while next_app do
|
23
|
+
yielder << next_app
|
24
|
+
next_app = next_app.instance_variable_get(:@app)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# utility function to set pry context
|
30
|
+
# to an instance of <Octokit::Client>
|
31
|
+
def client() pry Git::Hub.send(:client) ; end
|
32
|
+
|
33
|
+
# utility function to set pry context
|
34
|
+
# to the Array of github repositories
|
35
|
+
def repos() pry Git::Multi.repositories ; end
|
36
|
+
|
37
|
+
# utility function to set pry context
|
38
|
+
# to the various 'git multi' commands:
|
39
|
+
def cmds() pry Git::Multi::Commands ; end
|
data/.rubocop.yml
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.5.1
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Peter Vandenberk
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# `git-multi`
|
2
|
+
|
3
|
+
## Summary
|
4
|
+
|
5
|
+
Execute the same `git` command in a set of related repos.
|
6
|
+
|
7
|
+
There are plenty of other utilities out there that do something similar, but typically they only support a limited number of hard-coded `git` commands which can be executed in multiple repositories.
|
8
|
+
|
9
|
+
`git-multi` is different: any `git` command _(including any `git` extensions you may have installed or any `git` aliases you may have defined)_ can be executed in multiple repositories.
|
10
|
+
|
11
|
+
`git-multi` only concerns itself with iterating over the set of related repos; what it executes in each of them is completely up to you.
|
12
|
+
|
13
|
+
## Features
|
14
|
+
|
15
|
+
* execute any `git` command, extension and alias in multiple repositories _(not just a limited set of pre-packaged commands)_
|
16
|
+
* human-friendly output in interactive mode _(akin to [git porcelain][p-p] commands)_, for every day use
|
17
|
+
* machine parseable output in non-interactive mode _(akin to [git plumbing][p-p] commands)_, for advanced scripting and automation
|
18
|
+
|
19
|
+
[p-p]: https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain
|
20
|
+
|
21
|
+
## Prerequisites
|
22
|
+
|
23
|
+
`git-multi` is a Ruby script, so you will have to have Ruby installed on your system _(system Ruby, [RVM][], [rbenv][], etc)_.
|
24
|
+
|
25
|
+
`git-multi` is also tightly coupled to your [GitHub][] account _(via the github API)_, so you will also need to generate a so-called [personal access token][token] and install it in your git config _(instructions provided below)_.
|
26
|
+
|
27
|
+
[rvm]: https://rvm.io
|
28
|
+
[rbenv]: http://rbenv.org
|
29
|
+
[github]: https://github.com
|
30
|
+
[token]: https://github.com/settings/tokens
|
31
|
+
|
32
|
+
## Installation
|
33
|
+
|
34
|
+
$ gem install git-multi
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
$ git multi --help
|
39
|
+
|
40
|
+
## Known Issues
|
41
|
+
|
42
|
+
1. it probably doesn't work on Windows
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
1. Fork it ( https://github.com/pvdb/git-multi/fork )
|
47
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
48
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
49
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
50
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
def gemspec
|
4
|
+
@gemspec ||= begin
|
5
|
+
# rubocop:disable Security/Eval
|
6
|
+
eval(File.read('git-multi.gemspec'))
|
7
|
+
# rubocop:enable Security/Eval
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
desc 'Validate the gemspec'
|
12
|
+
task :validate do
|
13
|
+
gemspec.validate
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'rake/testtask'
|
17
|
+
|
18
|
+
Rake::TestTask.new(:test) do |t|
|
19
|
+
t.libs << 'test'
|
20
|
+
t.libs << 'lib'
|
21
|
+
t.test_files = FileList['test/**/*_test.rb']
|
22
|
+
end
|
23
|
+
|
24
|
+
# rubocop:disable Style/HashSyntax
|
25
|
+
|
26
|
+
task :default => :test
|
27
|
+
|
28
|
+
# rubocop:enable Style/HashSyntax
|
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
<div style="font-family: monospace;">
|
2
|
+
<% @projects.each do |project| %>
|
3
|
+
<div style="float: left; width: 390px; margin: 10px;">
|
4
|
+
<table border="1" style="border: 1px solid black; width: 100%; padding: 5px;" summary="<%= project.full_name %>">
|
5
|
+
<tr>
|
6
|
+
<th style="text-align: center; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
|
7
|
+
<a href="<%= project.html_url %>" style="color: inherit; text-decoration: none;">
|
8
|
+
<%= project.full_name %>
|
9
|
+
</a>
|
10
|
+
</th>
|
11
|
+
</tr>
|
12
|
+
<tr>
|
13
|
+
<td>
|
14
|
+
<strong>Last commit …</strong>
|
15
|
+
</td>
|
16
|
+
</tr>
|
17
|
+
<tr>
|
18
|
+
<td style="text-align: right; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
|
19
|
+
<span title="<%= project.pushed_at %>">
|
20
|
+
<%= project.age_in_words %> ago
|
21
|
+
</span>
|
22
|
+
</td>
|
23
|
+
</tr>
|
24
|
+
<% TIME_INTERVALS.each_pair do |interval_name, interval_in_seconds| %>
|
25
|
+
<% in_interval = (project.age_in_seconds <= interval_in_seconds) %>
|
26
|
+
<tr style="border: 1px solid black; background-color: <%= in_interval ? GREEN[interval_name] : RED[interval_name] %>;">
|
27
|
+
<td style="color: white; padding-left: 20px;">
|
28
|
+
… in the previous <%= interval_name %>
|
29
|
+
</td>
|
30
|
+
</tr>
|
31
|
+
<% end %>
|
32
|
+
</table>
|
33
|
+
</div>
|
34
|
+
<% end %>
|
35
|
+
</div>
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
TIME_INTERVALS = {
|
4
|
+
|
5
|
+
:minute => ( MINUTE = 60 ), # seconds
|
6
|
+
:hour => ( HOUR = 60 * MINUTE ),
|
7
|
+
:day => ( DAY = 24 * HOUR ),
|
8
|
+
:week => ( WEEK = 7 * DAY ),
|
9
|
+
:year => ( YEAR = 365 * DAY ),
|
10
|
+
:month => ( MONTH = YEAR / 12 ),
|
11
|
+
:quarter => ( QUARTER = YEAR / 4 ),
|
12
|
+
|
13
|
+
}.delete_if { |key, _| key == :minute }.sort_by(&:last).to_h.freeze
|
14
|
+
|
15
|
+
def time_distance_in_words(seconds)
|
16
|
+
case
|
17
|
+
when seconds < HOUR then "less than an hour"
|
18
|
+
when seconds < DAY then "about #{(seconds / HOUR).round} hour(s)"
|
19
|
+
when seconds < MONTH then "about #{(seconds / DAY).round} day(s)"
|
20
|
+
when seconds < QUARTER then "about #{(seconds / WEEK).round} week(s)"
|
21
|
+
when seconds < YEAR then "about #{(seconds / MONTH).round} month(s)"
|
22
|
+
else "about #{(seconds / YEAR).round} year(s)"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
BLUE = {
|
27
|
+
:hour => "#CCCCFF" ,
|
28
|
+
:day => "#AAAAFF" ,
|
29
|
+
:week => "#8888FF" ,
|
30
|
+
:month => "#6666FF" ,
|
31
|
+
:quarter => "#4444FF" ,
|
32
|
+
:year => "#2222FF" ,
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
GREEN = {
|
36
|
+
:hour => "#CCFFCC" ,
|
37
|
+
:day => "#AAFFAA" ,
|
38
|
+
:week => "#88FF88" ,
|
39
|
+
:month => "#66FF66" ,
|
40
|
+
:quarter => "#44FF44" ,
|
41
|
+
:year => "#22FF22" ,
|
42
|
+
}.freeze
|
43
|
+
|
44
|
+
RED = {
|
45
|
+
:hour => "#FFCCCC" ,
|
46
|
+
:day => "#FFAAAA" ,
|
47
|
+
:week => "#FF8888" ,
|
48
|
+
:month => "#FF6666" ,
|
49
|
+
:quarter => "#FF4444" ,
|
50
|
+
:year => "#FF2222" ,
|
51
|
+
}.freeze
|
52
|
+
|
53
|
+
if __FILE__ == $0
|
54
|
+
|
55
|
+
$:.unshift File.expand_path('../../lib', __dir__)
|
56
|
+
|
57
|
+
require 'git/multi'
|
58
|
+
|
59
|
+
# get list of GitHub repositories:
|
60
|
+
@projects = Git::Multi.repositories
|
61
|
+
|
62
|
+
# annotate the list of projects
|
63
|
+
@projects.each do |project|
|
64
|
+
def project.age_in_seconds
|
65
|
+
(Time.now - pushed_at).to_i
|
66
|
+
end
|
67
|
+
def project.age_in_words
|
68
|
+
time_distance_in_words(age_in_seconds)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# reverse-order based on the last commit
|
73
|
+
@projects.sort_by!(&:pushed_at).reverse!
|
74
|
+
|
75
|
+
require 'erb'
|
76
|
+
|
77
|
+
# generate HTML page with a dashboard of global GitHub repository activity:
|
78
|
+
puts ERB.new(File.read(File.join(__dir__, "git-dash.erb"))).result(binding)
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
# That's all Folks!
|
data/doc/git-multi.man
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
GIT-META(1) Git Extensions GIT-META(1)
|
2
|
+
|
3
|
+
NAME
|
4
|
+
git-multi -- execute the same git command in multiple repositories
|
5
|
+
|
6
|
+
|
7
|
+
SYNOPSIS
|
8
|
+
git multi <some_git_command_with_relevant_arguments>
|
9
|
+
|
10
|
+
DESCRIPTION
|
11
|
+
Convenient way to execute the same git command in a set of related repos,
|
12
|
+
currently the list of an organization's repositories on Github.
|
13
|
+
|
14
|
+
Said list is determined via a Github API v3 call, and cached locally for
|
15
|
+
performance.
|
16
|
+
|
17
|
+
OPTIONS
|
18
|
+
|
19
|
+
git multi --help # you're looking at it
|
20
|
+
git multi --check # checks all the required settings and configurations
|
21
|
+
git multi --version # print out this script's version number
|
22
|
+
git multi --refresh # refresh the list of organization repos
|
23
|
+
git multi --json # output repository details to JSON
|
24
|
+
git multi --count # print out the count of organization repos (per type)
|
25
|
+
git multi --list # print out the names of all organization repos
|
26
|
+
git multi --archived # print out the names of all organization repos
|
27
|
+
git multi --forked # print out the names of all organization repos
|
28
|
+
git multi --private # print out the names of all organization repos
|
29
|
+
git multi --paths # print out the full path for each organization repos
|
30
|
+
git multi --spurious # list cloned repos whose remote doesn't match a github.com origin
|
31
|
+
git multi --missing # print out names of repos that haven't been cloned
|
32
|
+
git multi --stale # list repos that have been deleted on github.com
|
33
|
+
git multi --excess # list repos that don't exist on github.com
|
34
|
+
git multi --clone # clones missing repositories into "${HOME}/Workarea" (by default)
|
35
|
+
git multi --query (args) # query Github repo multidata for each repository
|
36
|
+
git multi --find <ruby> # print out the repos for which the Ruby code evaluates to true
|
37
|
+
git multi --eval <ruby> # execute the given Ruby code in the context of each repo
|
38
|
+
get multi --raw <cmd> # execute the given shell command inside each git repository
|
39
|
+
|
40
|
+
EXAMPLES
|
41
|
+
|
42
|
+
# count the number of organization repos
|
43
|
+
git multi --list | wc -l
|
44
|
+
|
45
|
+
# disk usage of each locally cloned organization repo
|
46
|
+
git multi --paths | xargs -n 1 du -hs
|
47
|
+
|
48
|
+
# ... or by using the `--raw` option
|
49
|
+
git multi --raw 'du -hs .'
|
50
|
+
|
51
|
+
# group and count the repos by Github-determined language
|
52
|
+
git multi --query language | cut -f 2 -d : | sort | uniq -c | sort -n -r
|
53
|
+
|
54
|
+
# find out the most-used Ruby versions
|
55
|
+
git multi --raw '[ -f .ruby-version ] && cat .ruby-version' | cut -f 2 -d : | sort | uniq -c | sort -n -r
|
56
|
+
|
57
|
+
# find Github repos without a description
|
58
|
+
git multi --query description | egrep ': *$'
|
59
|
+
|
60
|
+
# fetch remote branches for all organization repos
|
61
|
+
git multi fetch -p
|
62
|
+
|
63
|
+
# print out the local branch for each repo
|
64
|
+
git multi rev-parse --abbrev-ref=strict HEAD
|
65
|
+
|
66
|
+
# find all repos for which the 'origin' remote isn't github.com
|
67
|
+
git multi config --get remote.origin.url | fgrep -v git@github.com:
|
68
|
+
|
69
|
+
# a kind of "repository creation" report: count the number of repos created in each quarter
|
70
|
+
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
|
71
|
+
|
72
|
+
# for each repo, list all remote branches, sorted by the "age" of the last commit on each branch
|
73
|
+
git multi for-each-ref --sort="-authordate" --format="%%(refname)%%09%%(authordate:relative)%%09%%(authorname)" refs/remotes/origin
|
74
|
+
|
75
|
+
# same as above, but columnize the generated output (NOTE: replace '^I' with CTRL-V/CTRL-I in your terminal)
|
76
|
+
git multi for-each-ref --sort="-authordate" --format="%%(refname)%%09%%(authordate:relative)%%09%%(authorname)" refs/remotes/origin | column -t -s "^I"
|
77
|
+
|
78
|
+
# same as above, but refresh the list of remote branches first
|
79
|
+
git multi fetch -p ; git multi for-each-ref --sort="-authordate" --format="%%(refname)%%09%%(authordate:relative)%%09%%(authorname)" refs/remotes/origin
|
80
|
+
|
81
|
+
# find all organization repositories that depend on a given org repo, e.g. 'business_rules'
|
82
|
+
git multi --graph | fgrep business_rules
|
83
|
+
|
84
|
+
# find all Rails projects
|
85
|
+
git multi --raw '[ -f Gemfile ] && fgrep -q -l rails Gemfile && echo uses Rails' | cat
|
86
|
+
|
87
|
+
# find all Mongoid dependencies
|
88
|
+
git multi --raw '[ -f Gemfile.lock ] && egrep -i "^ mongoid (.*)" Gemfile.lock' | column -s: -t
|
89
|
+
|
90
|
+
# find all projects that have been pushed to in the last week
|
91
|
+
git multi --find '((Time.now - pushed_at) / 60 / 60 / 24) <= 7'
|
92
|
+
|
93
|
+
# print out the number of days since the last push to each repository
|
94
|
+
git multi --eval 'puts "%%d days" %% ((Time.now - pushed_at) / 60 / 60 / 24)'
|
95
|
+
|
96
|
+
# find all projects that have seen activity this calendar year
|
97
|
+
git multi --find 'pushed_at >= Date.civil(Date.today.year, 1, 1).to_time'
|
98
|
+
|
99
|
+
# print out all webhooks
|
100
|
+
git multi --eval '(hooks = client.hooks(project.full_name)).any? && begin print project.full_name ; print "\t" ; puts hooks.map { |hook| ["", hook.name, hook.config.url].join("\t") } ; end'
|
101
|
+
|
102
|
+
# print out all deploy keys
|
103
|
+
git multi --eval '(keys = client.list_deploy_keys(project.full_name)).any? && begin print project.full_name ; print "\t" ; puts keys.map(&:title).sort.join("\t") ; end'
|
104
|
+
|
105
|
+
# generate a dependency graph of all organization repositories using yuml.me
|
106
|
+
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}"
|
107
|
+
|
108
|
+
# generate a dependency graph of all organization repositories using Graphviz
|
109
|
+
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
|
110
|
+
|
111
|
+
QUERY ARGUMENTS
|
112
|
+
|
113
|
+
The following is a list of valid arguments for the 'git multi --query' option
|
114
|
+
|
115
|
+
%{query_args}
|
116
|
+
|
117
|
+
USE `jq` TO QUERY THE `git multi` CACHE
|
118
|
+
|
119
|
+
`jq` is like `sed` for JSON data... all of the above query arguments can be
|
120
|
+
used in conjunction with `jq` to query, filter, map and transform the github
|
121
|
+
repository attributes stored in `${HOME}/.gitmulti.byte`
|
122
|
+
|
123
|
+
EXAMPLES
|
124
|
+
|
125
|
+
# print out each repository's name and its description
|
126
|
+
git multi --json | jq '.[] | .name + ": " + .description'
|
127
|
+
|
128
|
+
# print out the name of all "forked" repositories
|
129
|
+
git multi --json | jq '.[] | select(.fork == true) | .full_name'
|
130
|
+
|
131
|
+
FILES
|
132
|
+
|
133
|
+
${HOME}/Workarea # root directory where organization repos have been cloned
|
134
|
+
|
135
|
+
REFERENCES
|
136
|
+
|
137
|
+
# the Github API call used to refresh the list of organization repos
|
138
|
+
http://developer.github.com/v3/orgs/teams/#list-team-repos
|
139
|
+
|
140
|
+
# the `jq` command-line utility
|
141
|
+
http://stedolan.github.io/jq/
|
142
|
+
|
143
|
+
git-multi %{version} 1 March 2015 GIT-META(1)
|
data/exe/git-multi
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path('../../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'git/multi'
|
6
|
+
|
7
|
+
case (command = ARGV.shift)
|
8
|
+
when /\A--/
|
9
|
+
case command
|
10
|
+
when '--help' then Git::Multi::Commands.help
|
11
|
+
when '--check' then Git::Multi::Commands.check
|
12
|
+
when '--version' then Git::Multi::Commands.version
|
13
|
+
when '--refresh' then Git::Multi::Commands.refresh
|
14
|
+
when '--json' then Git::Multi::Commands.json
|
15
|
+
when '--count' then Git::Multi::Commands.count
|
16
|
+
when '--list' then Git::Multi::Commands.list
|
17
|
+
when '--archived'then Git::Multi::Commands.archived
|
18
|
+
when '--forked' then Git::Multi::Commands.forked
|
19
|
+
when '--private' then Git::Multi::Commands.private
|
20
|
+
when '--paths' then Git::Multi::Commands.paths
|
21
|
+
when '--spurious'then Git::Multi::Commands.spurious
|
22
|
+
when '--missing' then Git::Multi::Commands.missing
|
23
|
+
when '--stale' then Git::Multi::Commands.stale
|
24
|
+
when '--excess' then Git::Multi::Commands.excess
|
25
|
+
when '--clone' then Git::Multi::Commands.clone
|
26
|
+
when '--query' then Git::Multi::Commands.query(ARGV)
|
27
|
+
when '--find' then Git::Multi::Commands.find(ARGV)
|
28
|
+
when '--eval' then Git::Multi::Commands.eval(ARGV)
|
29
|
+
when '--raw' then Git::Multi::Commands.raw(ARGV)
|
30
|
+
else
|
31
|
+
abort \
|
32
|
+
"Unknown 'git multi' command: #{command}\n\n" \
|
33
|
+
"(use --help/-h to list all available commands)"
|
34
|
+
end
|
35
|
+
when nil, '', '-h'
|
36
|
+
Git::Multi::Commands.help
|
37
|
+
else
|
38
|
+
Git::Multi::Commands.report
|
39
|
+
Git::Multi::Commands.exec command, ARGV
|
40
|
+
end
|
41
|
+
|
42
|
+
# That's all Folks!
|
data/git-multi.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'git/multi/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = Git::Multi::NAME
|
7
|
+
spec.version = Git::Multi::VERSION
|
8
|
+
spec.authors = ['Peter Vandenberk']
|
9
|
+
spec.email = ['pvandenberk@mac.com']
|
10
|
+
|
11
|
+
spec.summary = 'The ultimate multi-repo utility for git!'
|
12
|
+
spec.description = 'Run the same git command in a set of related repos'
|
13
|
+
spec.homepage = 'https://github.com/pvdb/git-multi'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = begin
|
17
|
+
`git ls-files -z`
|
18
|
+
.split("\x0")
|
19
|
+
.reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
end
|
21
|
+
spec.bindir = 'exe'
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.post_install_message = Git::Multi::PIM
|
26
|
+
|
27
|
+
spec.add_dependency 'octokit', '~> 4.12'
|
28
|
+
|
29
|
+
spec.add_development_dependency 'bundler'
|
30
|
+
spec.add_development_dependency 'pry'
|
31
|
+
spec.add_development_dependency 'rake'
|
32
|
+
end
|
data/lib/ext/commify.rb
ADDED
data/lib/ext/dir.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
class Dir
|
2
|
+
def git_repos(subdir = '*')
|
3
|
+
Dir.glob(File.join(self.path, subdir, '*', '.git')).map { |path_to_git_dir|
|
4
|
+
path_to_git_repo = File.dirname(path_to_git_dir) # without "/.git"
|
5
|
+
repo_name = path_to_git_repo[/[^\/]+\/[^\/]+\z/] # e.g. "pvdb/git-multi"
|
6
|
+
def repo_name.full_name() self ; end # awesome duck-typing!
|
7
|
+
repo_name
|
8
|
+
}
|
9
|
+
end
|
10
|
+
end
|
data/lib/ext/notify.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
begin
|
2
|
+
require 'terminal-notifier'
|
3
|
+
rescue LoadError
|
4
|
+
# NOOP - "TerminalNotifier" is optional
|
5
|
+
# will only be used if it is installed!
|
6
|
+
end
|
7
|
+
|
8
|
+
def notify(message, options = {}, verbose = false)
|
9
|
+
# print the given message to STDERR, if the
|
10
|
+
# script is running with "--verbose" option
|
11
|
+
subtitle = options[:subtitle]
|
12
|
+
warn(subtitle ? "#{subtitle}: #{message}" : message) if $VERBOSE
|
13
|
+
# send a given message to the Mac OS X Notification Center
|
14
|
+
# but only if the git-multi script is running interactively
|
15
|
+
# and if the "terminal-notifier" gem has been installed...
|
16
|
+
if $INTERACTIVE && defined?(TerminalNotifier)
|
17
|
+
options[:title] ||= 'git-multi'
|
18
|
+
TerminalNotifier.notify(message, options, verbose)
|
19
|
+
end
|
20
|
+
end
|
data/lib/ext/string.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
def colorize(color_code) "\e[#{color_code}m#{self}\e[0m" ; end
|
4
|
+
|
5
|
+
def bold() colorize('1') ; end
|
6
|
+
def invert() colorize('7') ; end
|
7
|
+
|
8
|
+
def red() colorize('31') ; end
|
9
|
+
def blue() colorize('34') ; end
|
10
|
+
def green() colorize('32') ; end
|
11
|
+
|
12
|
+
def undent() gsub(/^.{#{slice(/^ +/).length}}/, '') ; end
|
13
|
+
|
14
|
+
end
|
data/lib/ext/utils.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
$INTERACTIVE = (STDOUT.tty? && STDERR.tty?)
|
2
|
+
|
3
|
+
def git_option name, default = nil
|
4
|
+
value = `git config #{name}`.chomp.freeze
|
5
|
+
value.empty? && default ? default : value
|
6
|
+
end
|
7
|
+
|
8
|
+
def env_var name, default = nil
|
9
|
+
value = ENV[name].freeze
|
10
|
+
(value.nil? || value.empty?) && default ? default : value
|
11
|
+
end
|
12
|
+
|
13
|
+
def describe token
|
14
|
+
token.nil? ? '(nil)' : token.empty? ? '(empty)' : "#{'*'*36}#{token[36..-1]}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def symbolize token
|
18
|
+
case token
|
19
|
+
when env_var('OCTOKIT_ACCESS_TOKEN') then '${OCTOKIT_ACCESS_TOKEN}'
|
20
|
+
when git_option('github.token') then 'github.token'
|
21
|
+
else '(unset)'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def abbreviate directory, root_dir = nil
|
26
|
+
case root_dir
|
27
|
+
when :home then directory.gsub(Git::Multi::HOME, '${HOME}')
|
28
|
+
when :workarea then directory.gsub(Git::Multi::WORKAREA, '${WORKAREA}')
|
29
|
+
else abbreviate(abbreviate(directory, :workarea), :home)
|
30
|
+
end
|
31
|
+
end
|