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
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
|