git-multi 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
data/lib/git/hub.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'net/http/persistent'
|
5
|
+
Octokit.middleware.swap(
|
6
|
+
Faraday::Adapter::NetHttp, # default Faraday adapter
|
7
|
+
Faraday::Adapter::NetHttpPersistent # experimental Faraday adapter
|
8
|
+
)
|
9
|
+
rescue LoadError
|
10
|
+
# NOOP - `Net::HTTP::Persistent` is optional, so
|
11
|
+
# if the gem isn't installed, then we run with the
|
12
|
+
# default `Net::HTTP` Faraday adapter; if however
|
13
|
+
# the gem is installed then we make Faraday use it
|
14
|
+
# to benefit from persistent HTTP connections
|
15
|
+
end
|
16
|
+
|
17
|
+
module Git
|
18
|
+
module Hub
|
19
|
+
|
20
|
+
module_function
|
21
|
+
|
22
|
+
def client
|
23
|
+
@client ||= Octokit::Client.new(
|
24
|
+
:access_token => Git::Multi::TOKEN,
|
25
|
+
:auto_paginate => true,
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
private :client
|
31
|
+
end
|
32
|
+
|
33
|
+
def connected?
|
34
|
+
@connected ||= begin
|
35
|
+
client.validate_credentials
|
36
|
+
true
|
37
|
+
rescue Faraday::ConnectionFailed
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# FIXME update login as part of `--refresh`
|
43
|
+
|
44
|
+
def login
|
45
|
+
@login ||= begin
|
46
|
+
client.user.login
|
47
|
+
rescue Octokit::Unauthorized, Faraday::ConnectionFailed
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# FIXME update orgs as part of `--refresh`
|
53
|
+
|
54
|
+
def orgs
|
55
|
+
@orgs ||= begin
|
56
|
+
client.organizations.map(&:login)
|
57
|
+
rescue Octokit::Unauthorized, Faraday::ConnectionFailed
|
58
|
+
[]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# https://developer.github.com/v3/repos/#list-user-repositories
|
64
|
+
#
|
65
|
+
|
66
|
+
@user_repositories = Hash.new { |repos, (user, type)|
|
67
|
+
repos[[user, type]] = begin
|
68
|
+
notify("Refreshing #{type} '#{user}' repositories from GitHub")
|
69
|
+
client.repositories(user, :type => type).
|
70
|
+
sort_by { |repo| repo[:name].downcase }
|
71
|
+
end
|
72
|
+
}
|
73
|
+
|
74
|
+
def user_repositories(user, type = :owner) # all, owner, member
|
75
|
+
@user_repositories[[user, type]]
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# https://developer.github.com/v3/repos/#list-organization-repositories
|
80
|
+
#
|
81
|
+
|
82
|
+
@org_repositories = Hash.new { |repos, (org, type)|
|
83
|
+
repos[[org, type]] = begin
|
84
|
+
notify("Refreshing #{type} '#{org}' repositories from GitHub")
|
85
|
+
client.org_repositories(org, :type => type).
|
86
|
+
sort_by { |repo| repo[:name].downcase }
|
87
|
+
end
|
88
|
+
}
|
89
|
+
|
90
|
+
def org_repositories(org, type = :owner) # all, public, private, forks, sources, member
|
91
|
+
@org_repositories[[org, type]]
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
module Git
|
2
|
+
module Multi
|
3
|
+
module Commands
|
4
|
+
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def version
|
8
|
+
puts Git::Multi::LONG_VERSION
|
9
|
+
end
|
10
|
+
|
11
|
+
def check
|
12
|
+
Settings.user_status(Git::Multi::USER)
|
13
|
+
Settings.organization_status(Git::Multi::ORGANIZATIONS)
|
14
|
+
Settings.token_status(Git::Multi::TOKEN)
|
15
|
+
Settings.home_status(Git::Multi::HOME)
|
16
|
+
Settings.main_workarea_status(Git::Multi::WORKAREA)
|
17
|
+
Settings.user_workarea_status(Git::Multi::USER)
|
18
|
+
Settings.organization_workarea_status(Git::Multi::ORGANIZATIONS)
|
19
|
+
Settings.file_status(Git::Multi::REPOSITORIES)
|
20
|
+
end
|
21
|
+
|
22
|
+
def help
|
23
|
+
# instead of maintaining a list of valid query args in the help-
|
24
|
+
# file, we determine it at runtime... less is more, and all that
|
25
|
+
# TODO remove attributes we 'adorned' the repos with on line 95?
|
26
|
+
query_args = Git::Multi.repositories.sample.fields.sort.each_slice(3).map {
|
27
|
+
|foo, bar, qux| '%-20s %-20s %-20s' % [foo, bar, qux]
|
28
|
+
}
|
29
|
+
puts File.read(Git::Multi::MAN_PAGE) % {
|
30
|
+
:version => Git::Multi::VERSION,
|
31
|
+
:query_args => query_args.join("\n "),
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def report
|
36
|
+
if (missing_repos = Git::Multi::missing_repositories).any?
|
37
|
+
notify(missing_repos.map(&:full_name), :subtitle => "#{missing_repos.count} missing repos")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def list
|
42
|
+
puts Git::Multi.repositories.map(&:full_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def archived
|
46
|
+
puts Git::Multi.archived_repositories.map(&:full_name)
|
47
|
+
end
|
48
|
+
|
49
|
+
def forked
|
50
|
+
puts Git::Multi.forked_repositories.map(&:full_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def private
|
54
|
+
puts Git::Multi.private_repositories.map(&:full_name)
|
55
|
+
end
|
56
|
+
|
57
|
+
def paths
|
58
|
+
puts Git::Multi.repositories.map(&:local_path)
|
59
|
+
end
|
60
|
+
|
61
|
+
def missing
|
62
|
+
puts Git::Multi.missing_repositories.map(&:full_name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def excess
|
66
|
+
puts Git::Multi.excess_repositories.map(&:full_name)
|
67
|
+
end
|
68
|
+
|
69
|
+
def stale
|
70
|
+
puts Git::Multi.stale_repositories.map(&:full_name)
|
71
|
+
end
|
72
|
+
|
73
|
+
def spurious
|
74
|
+
puts Git::Multi.spurious_repositories.map(&:full_name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def count
|
78
|
+
# https://developer.github.com/v3/repos/#list-user-repositories
|
79
|
+
user = Git::Multi::USER
|
80
|
+
%w{ all owner member }.each { |type|
|
81
|
+
puts ["#{user}/#{type}", Git::Hub.user_repositories(user, type).count].join("\t")
|
82
|
+
}
|
83
|
+
# https://developer.github.com/v3/repos/#list-organization-repositories
|
84
|
+
for org in Git::Multi::ORGANIZATIONS
|
85
|
+
%w{ all public private forks sources member }.each { |type|
|
86
|
+
puts ["#{org}/#{type}", Git::Hub.org_repositories(org, type).count].join("\t")
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def refresh
|
92
|
+
Git::Multi.refresh_repositories
|
93
|
+
end
|
94
|
+
|
95
|
+
def json
|
96
|
+
puts Git::Multi.repositories.to_json
|
97
|
+
end
|
98
|
+
|
99
|
+
def clone
|
100
|
+
Git::Multi.missing_repositories.each do |repo|
|
101
|
+
FileUtils.mkdir_p repo.parent_dir
|
102
|
+
repo.just_do_it(
|
103
|
+
->(project) {
|
104
|
+
notify "Cloning '#{repo.full_name}' repo into #{repo.parent_dir.parent}"
|
105
|
+
Kernel.system "git clone -q #{project.rels[:ssh].href.shellescape}"
|
106
|
+
},
|
107
|
+
->(project) {
|
108
|
+
Kernel.system "git clone -q #{project.rels[:ssh].href.shellescape}"
|
109
|
+
},
|
110
|
+
:in_dir => :parent_dir
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def query args = []
|
116
|
+
Git::Multi.repositories.each do |repo|
|
117
|
+
repo.just_do_it(
|
118
|
+
->(project) {
|
119
|
+
args.each do |attribute|
|
120
|
+
puts "#{attribute}: #{project[attribute]}"
|
121
|
+
end
|
122
|
+
},
|
123
|
+
->(project) {
|
124
|
+
print "#{project.full_name}: "
|
125
|
+
puts args.map { |attribute| project[attribute] }.join(' ')
|
126
|
+
},
|
127
|
+
)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def system args = []
|
132
|
+
args.map!(&:shellescape)
|
133
|
+
Git::Multi.cloned_repositories.each do |repo|
|
134
|
+
repo.just_do_it(
|
135
|
+
->(project) {
|
136
|
+
Kernel.system "#{args.join(' ')}"
|
137
|
+
},
|
138
|
+
->(project) {
|
139
|
+
Kernel.system "#{args.join(' ')} 2>&1 | sed -e 's#^##{project.full_name.shellescape}: #'"
|
140
|
+
},
|
141
|
+
:in_dir => :local_path
|
142
|
+
)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def raw args
|
147
|
+
args.unshift ['sh', '-c']
|
148
|
+
system args.flatten
|
149
|
+
end
|
150
|
+
|
151
|
+
def exec command, args = []
|
152
|
+
args.unshift ['git', '--no-pager', command]
|
153
|
+
system args.flatten
|
154
|
+
end
|
155
|
+
|
156
|
+
def find commands
|
157
|
+
Git::Multi.cloned_repositories.each do |repo|
|
158
|
+
Dir.chdir(repo.local_path) do
|
159
|
+
begin
|
160
|
+
if repo.instance_eval(commands.join(' && '))
|
161
|
+
repo.just_do_it(
|
162
|
+
->(project) { nil ; },
|
163
|
+
->(project) { puts project.full_name ; },
|
164
|
+
)
|
165
|
+
end
|
166
|
+
rescue Octokit::NotFound
|
167
|
+
# project no longer exists on github.com
|
168
|
+
# consider running "git multi --stale"...
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def eval commands
|
175
|
+
Git::Multi.cloned_repositories.each do |repo|
|
176
|
+
Dir.chdir(repo.local_path) do
|
177
|
+
begin
|
178
|
+
repo.instance_eval(commands.join(' ; '))
|
179
|
+
rescue Octokit::NotFound
|
180
|
+
# project no longer exists on github.com
|
181
|
+
# consider running "git multi --stale"...
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Git
|
2
|
+
module Multi
|
3
|
+
module Settings
|
4
|
+
|
5
|
+
TICK = ["2714".hex].pack("U*").green.freeze
|
6
|
+
CROSS = ["2718".hex].pack("U*").red.freeze
|
7
|
+
ARROW = ["2794".hex].pack("U*").blue.freeze
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def setting_status messages, valid, optional = false
|
12
|
+
fields = messages.compact.join(' - ')
|
13
|
+
icon = valid ? TICK : optional ? ARROW : CROSS
|
14
|
+
if $INTERACTIVE
|
15
|
+
print " #{fields}" ; sleep 0.75 ; puts "\x0d#{icon}"
|
16
|
+
else
|
17
|
+
puts "#{icon} #{fields}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def file_status file, message = 'File'
|
22
|
+
setting_status(
|
23
|
+
[
|
24
|
+
message,
|
25
|
+
abbreviate(file),
|
26
|
+
File.file?(file) ? "#{File.size(file).commify} bytes" : nil,
|
27
|
+
],
|
28
|
+
file && !file.empty? && File.file?(file),
|
29
|
+
false
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def directory_status messages, directory
|
34
|
+
setting_status(
|
35
|
+
messages,
|
36
|
+
directory && !directory.empty? && File.directory?(directory),
|
37
|
+
false
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def workarea_status message, workarea, owner
|
42
|
+
directory_status(
|
43
|
+
[
|
44
|
+
message,
|
45
|
+
File.join(abbreviate(workarea, :workarea), owner),
|
46
|
+
File.directory?(workarea) ? "#{Dir.new(workarea).git_repos(owner).count.commify} repos" : nil
|
47
|
+
],
|
48
|
+
workarea
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def user_status user
|
53
|
+
setting_status(["User", user], user && !user.empty?)
|
54
|
+
end
|
55
|
+
|
56
|
+
def organization_status orgs
|
57
|
+
for org in orgs
|
58
|
+
setting_status(["Organization", org], org && !org.empty?, true)
|
59
|
+
setting_status(["Organization", "member?"], Git::Hub.orgs.include?(org), !Git::Hub.connected?)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def token_status token
|
64
|
+
setting_status(["Token", symbolize(token), describe(token)], !token.nil? && !token.empty?)
|
65
|
+
setting_status(["Token", "valid?"], !token.nil? && !token.empty? && Git::Hub.login, !Git::Hub.connected?)
|
66
|
+
setting_status(["Token", "owned by #{Git::Multi::USER}?"], Git::Hub.login == Git::Multi::USER, !Git::Hub.connected?)
|
67
|
+
end
|
68
|
+
|
69
|
+
def home_status home
|
70
|
+
directory_status(["Home", home], home)
|
71
|
+
end
|
72
|
+
|
73
|
+
def main_workarea_status workarea
|
74
|
+
directory_status(["Workarea (main)", abbreviate(workarea, :home)], workarea)
|
75
|
+
end
|
76
|
+
|
77
|
+
def user_workarea_status user
|
78
|
+
workarea_status("Workarea (user: #{user})", Git::Multi::WORKAREA, user)
|
79
|
+
end
|
80
|
+
|
81
|
+
def organization_workarea_status orgs
|
82
|
+
for org in orgs
|
83
|
+
workarea_status("Workarea (org: #{org})", Git::Multi::WORKAREA, org)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
require 'sawyer'
|
3
|
+
require 'psych'
|
4
|
+
|
5
|
+
module Git
|
6
|
+
module Multi
|
7
|
+
NAME = 'git-multi'
|
8
|
+
VERSION = '1.0.0'
|
9
|
+
|
10
|
+
LONG_VERSION = "%s v%s (%s v%s, %s v%s, %s v%s)" % [
|
11
|
+
NAME,
|
12
|
+
VERSION,
|
13
|
+
'octokit.rb',
|
14
|
+
Octokit::VERSION,
|
15
|
+
'sawyer',
|
16
|
+
Sawyer::VERSION,
|
17
|
+
'psych',
|
18
|
+
Psych::VERSION,
|
19
|
+
]
|
20
|
+
|
21
|
+
PIM = <<~"EOPIM" # gem post_install_message
|
22
|
+
|
23
|
+
The required settings are as follows:
|
24
|
+
|
25
|
+
git config --global --add github.user <your_github_username>
|
26
|
+
git config --global --add github.organizations <your_github_orgs>
|
27
|
+
git config --global --add github.token <your_github_token>
|
28
|
+
git config --global --add gitmulti.workarea <your_root_workarea>
|
29
|
+
|
30
|
+
EOPIM
|
31
|
+
end
|
32
|
+
end
|
data/lib/git/multi.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'etc'
|
2
|
+
require 'json'
|
3
|
+
require 'pathname'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'shellwords'
|
6
|
+
|
7
|
+
require 'octokit'
|
8
|
+
require 'sawyer'
|
9
|
+
|
10
|
+
require 'ext/dir'
|
11
|
+
require 'ext/utils'
|
12
|
+
require 'ext/string'
|
13
|
+
require 'ext/notify'
|
14
|
+
require 'ext/commify'
|
15
|
+
require 'ext/sawyer/resource'
|
16
|
+
|
17
|
+
require 'git/hub'
|
18
|
+
|
19
|
+
require 'git/multi/version'
|
20
|
+
require 'git/multi/settings'
|
21
|
+
require 'git/multi/commands'
|
22
|
+
|
23
|
+
module Git
|
24
|
+
module Multi
|
25
|
+
|
26
|
+
HOME = Dir.home
|
27
|
+
|
28
|
+
DEFAULT_WORKAREA = File.join(HOME, 'Workarea')
|
29
|
+
WORKAREA = git_option('gitmulti.workarea', DEFAULT_WORKAREA)
|
30
|
+
|
31
|
+
DEFAULT_TOKEN = env_var('OCTOKIT_ACCESS_TOKEN') # same as Octokit
|
32
|
+
TOKEN = git_option('github.token', DEFAULT_TOKEN)
|
33
|
+
|
34
|
+
CACHE = File.join(HOME, '.git', 'multi')
|
35
|
+
REPOSITORIES = File.join(CACHE, 'repositories.byte')
|
36
|
+
|
37
|
+
USER = git_option('github.user')
|
38
|
+
ORGANIZATIONS = git_option('github.organizations').split(/\s*,\s*/)
|
39
|
+
|
40
|
+
MAN_PAGE = File.expand_path('../../doc/git-multi.man', __dir__)
|
41
|
+
|
42
|
+
module_function
|
43
|
+
|
44
|
+
#
|
45
|
+
# local repositories (in WORKAREA)
|
46
|
+
#
|
47
|
+
|
48
|
+
@local_user_repositories = Hash.new { |repos, user|
|
49
|
+
repos[user] = Dir.new(WORKAREA).git_repos(user)
|
50
|
+
}
|
51
|
+
|
52
|
+
@local_org_repositories = Hash.new { |repos, org|
|
53
|
+
repos[org] = Dir.new(WORKAREA).git_repos(org)
|
54
|
+
}
|
55
|
+
|
56
|
+
def local_repositories
|
57
|
+
(
|
58
|
+
@local_user_repositories[USER] +
|
59
|
+
ORGANIZATIONS.map { |org| @local_org_repositories[org] }
|
60
|
+
).flatten
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# remote repositories (on GitHub)
|
65
|
+
#
|
66
|
+
|
67
|
+
def github_repositories
|
68
|
+
@github_repositories ||= (
|
69
|
+
Git::Hub.user_repositories(USER) +
|
70
|
+
ORGANIZATIONS.map { |org| Git::Hub.org_repositories(org) }
|
71
|
+
).flatten
|
72
|
+
end
|
73
|
+
|
74
|
+
def refresh_repositories
|
75
|
+
File.directory?(CACHE) || FileUtils.mkdir_p(CACHE)
|
76
|
+
|
77
|
+
File.open(REPOSITORIES, 'wb') do |file|
|
78
|
+
Marshal.dump(github_repositories, file)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# the main `Git::Multi` capabilities
|
84
|
+
#
|
85
|
+
|
86
|
+
module Nike
|
87
|
+
|
88
|
+
def just_do_it interactive, pipeline, options = {}
|
89
|
+
working_dir = case options[:in_dir]
|
90
|
+
when :parent_dir then self.parent_dir
|
91
|
+
when :local_path then self.local_path
|
92
|
+
else Dir.pwd
|
93
|
+
end
|
94
|
+
Dir.chdir(working_dir) do
|
95
|
+
if $INTERACTIVE
|
96
|
+
puts "%s (%s)" % [
|
97
|
+
self.full_name.invert,
|
98
|
+
self.fractional_index
|
99
|
+
]
|
100
|
+
interactive.call(self)
|
101
|
+
else
|
102
|
+
pipeline.call(self)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
def repositories
|
110
|
+
if File.size?(REPOSITORIES)
|
111
|
+
@repositories ||= Marshal.load(File.read(REPOSITORIES)).tap do |projects|
|
112
|
+
notify "Finished loading #{REPOSITORIES}"
|
113
|
+
projects.each_with_index do |project, index|
|
114
|
+
# ensure 'project' has handle on an Octokit client
|
115
|
+
project.client = Git::Hub.send(:client)
|
116
|
+
# adorn 'project', which is a Sawyer::Resource
|
117
|
+
project.parent_dir = Pathname.new(File.join(WORKAREA, project.owner.login))
|
118
|
+
project.local_path = Pathname.new(File.join(WORKAREA, project.full_name))
|
119
|
+
project.fractional_index = "#{index + 1}/#{projects.count}"
|
120
|
+
# fix 'project' => https://github.com/octokit/octokit.rb/issues/727
|
121
|
+
project.compliant_ssh_url = 'ssh://%s/%s' % project.ssh_url.split(':', 2)
|
122
|
+
# extend 'project' with 'just do it' capabilities
|
123
|
+
project.extend Nike
|
124
|
+
end
|
125
|
+
end
|
126
|
+
else
|
127
|
+
refresh_repositories and repositories
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# lists of repositories with a given state
|
133
|
+
#
|
134
|
+
|
135
|
+
def archived_repositories
|
136
|
+
repositories.find_all(&:archived)
|
137
|
+
end
|
138
|
+
|
139
|
+
def forked_repositories
|
140
|
+
repositories.find_all(&:fork)
|
141
|
+
end
|
142
|
+
|
143
|
+
def private_repositories
|
144
|
+
repositories.find_all(&:private)
|
145
|
+
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# derived lists of repositories
|
149
|
+
#
|
150
|
+
|
151
|
+
def excess_repositories
|
152
|
+
repository_full_names = repositories.map(&:full_name)
|
153
|
+
local_repositories.reject { |project|
|
154
|
+
repository_full_names.include? project.full_name
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
def stale_repositories
|
159
|
+
repository_full_names = github_repositories.map(&:full_name)
|
160
|
+
repositories.reject { |project|
|
161
|
+
repository_full_names.include? project.full_name
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
def spurious_repositories
|
166
|
+
cloned_repositories.find_all { |project|
|
167
|
+
origin_url = `git -C #{project.local_path} config --get remote.origin.url`.chomp
|
168
|
+
![
|
169
|
+
project.clone_url,
|
170
|
+
project.ssh_url,
|
171
|
+
project.compliant_ssh_url,
|
172
|
+
project.git_url,
|
173
|
+
].include? origin_url
|
174
|
+
}
|
175
|
+
end
|
176
|
+
|
177
|
+
def missing_repositories
|
178
|
+
repositories.find_all { |project|
|
179
|
+
!File.directory? project.local_path
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
def cloned_repositories
|
184
|
+
repositories.find_all { |project|
|
185
|
+
File.directory? project.local_path
|
186
|
+
}
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|