github-ripper 0.1.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/.github/CODEOWNERS +5 -0
- data/.github/CODE_OF_CONDUCT.md +76 -0
- data/.github/CONTRIBUTING.md +9 -0
- data/.github/FUNDING.yml +4 -0
- data/.github/ISSUE_TEMPLATE/ask_question.md +10 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +30 -0
- data/.github/ISSUE_TEMPLATE/config.yml +1 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +28 -0
- data/.github/SECURITY.md +39 -0
- data/.github/SUPPORT.md +8 -0
- data/.gitignore +17 -0
- data/.reek.yml +6 -0
- data/.rubocop.yml +31 -0
- data/.travis.yml +129 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +6 -0
- data/LICENSE.md +25 -0
- data/README.md +139 -0
- data/Rakefile +6 -0
- data/VERSION.txt +1 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/ghrip +106 -0
- data/github-ripper.gemspec +49 -0
- data/lib/github-ripper.rb +36 -0
- data/lib/github-ripper/function-maps.rb +31 -0
- data/lib/github-ripper/git.rb +81 -0
- data/lib/github-ripper/report.rb +38 -0
- data/lib/github-ripper/repos.rb +57 -0
- data/lib/github-ripper/table.rb +49 -0
- data/lib/github-ripper/utils.rb +64 -0
- data/lib/github-ripper/version.rb +6 -0
- data/lib/github-ripper/wrapper.rb +21 -0
- data/stale.yml +17 -0
- data/testing/VERSION.txt +1 -0
- data/testing/get-raw.rb +15 -0
- data/testing/ghrip +110 -0
- metadata +270 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
#
|
2
|
+
# Class docs to go here
|
3
|
+
#
|
4
|
+
|
5
|
+
#
|
6
|
+
# This class smells of :reek:DataClump
|
7
|
+
#
|
8
|
+
class GithubRipper
|
9
|
+
class << self
|
10
|
+
#
|
11
|
+
# Everything below here is private
|
12
|
+
#
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def get_clone_type(options)
|
17
|
+
return 'git clone git@github.com:' if flag_set?(options, :use_git)
|
18
|
+
|
19
|
+
return "git clone https://#{options[:token]}@github.com/" if get_option(options, :token)
|
20
|
+
|
21
|
+
'git clone https://github.com/'
|
22
|
+
end
|
23
|
+
|
24
|
+
def repo_full_path(options, repo)
|
25
|
+
"#{options[:base_dir]}/Repos/#{repo}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def repo_exists?(repo_path)
|
29
|
+
File.directory?(repo_path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute_command(command)
|
33
|
+
error_string = ''
|
34
|
+
return_code = 0
|
35
|
+
|
36
|
+
Open3.popen3(command) do |_stdin, stdout, _stderr, wait_thr|
|
37
|
+
error_string = stdout.read.chomp.to_s
|
38
|
+
return_code = wait_thr.value.exitstatus
|
39
|
+
end
|
40
|
+
output = error_string.split(/\n+/).reject(&:empty?).join(', ')
|
41
|
+
[return_code, output]
|
42
|
+
end
|
43
|
+
|
44
|
+
def clone_repo(options, repo, repo_path)
|
45
|
+
return { :repo => repo, :path => repo_path, :status => 'Dry Run', :when => 'git clone', :info => '' } if flag_set?(options, :dry_run)
|
46
|
+
|
47
|
+
FileUtils.mkdir_p repo_path
|
48
|
+
|
49
|
+
clone_type = get_clone_type(options)
|
50
|
+
command = "#{clone_type}#{repo} #{repo_path}"
|
51
|
+
return_code, output = execute_command(command)
|
52
|
+
|
53
|
+
return { :repo => repo, :path => repo_path, :status => 'Failed', :when => 'git clone', :info => output } if return_code.positive?
|
54
|
+
|
55
|
+
{ :repo => repo, :path => repo_path, :status => 'Success', :when => 'git clone', :info => 'Clone Succeeded' }
|
56
|
+
end
|
57
|
+
|
58
|
+
def update_repo(options, repo, repo_path)
|
59
|
+
return { :repo => repo, :path => repo_path, :status => 'Dry Run', :when => 'git clone', :info => '' } if flag_set?(options, :dry_run)
|
60
|
+
|
61
|
+
olddir = Dir.pwd
|
62
|
+
Dir.chdir repo_path
|
63
|
+
|
64
|
+
command = 'git pull 2>&1'
|
65
|
+
return_code, output = execute_command(command)
|
66
|
+
Dir.chdir olddir
|
67
|
+
|
68
|
+
return { :repo => repo, :path => repo_path, :status => 'Failed', :when => 'git pull', :info => output } if return_code.positive?
|
69
|
+
|
70
|
+
{ :repo => repo, :path => repo_path, :status => 'Success', :when => 'git pull', :info => 'Pull Succeeded' }
|
71
|
+
end
|
72
|
+
|
73
|
+
def clone_repo_wrapper(options, repo)
|
74
|
+
repo_path = repo_full_path(options, repo)
|
75
|
+
|
76
|
+
return update_repo(options, repo, repo_path) if repo_exists?(repo_path)
|
77
|
+
|
78
|
+
clone_repo(options, repo, repo_path)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#
|
2
|
+
# Class level docs
|
3
|
+
#
|
4
|
+
class GithubRipper
|
5
|
+
class << self
|
6
|
+
#
|
7
|
+
# Everything below here is private
|
8
|
+
#
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def display_table?(error_count, options)
|
13
|
+
# Dry run - force display of all
|
14
|
+
return true if flag_set?(options, :dry_run)
|
15
|
+
|
16
|
+
# Quite / Silent - show nothing
|
17
|
+
return false if flag_set?(options, :quiet) || flag_set?(options, :silent)
|
18
|
+
|
19
|
+
# Errors or full report wanted
|
20
|
+
return true if error_count.positive? || flag_set?(options, :full_report)
|
21
|
+
|
22
|
+
# If in doubt say nothing
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# This method smells of :reek:LongParameterList, :reek:DuplicateMethodCall
|
28
|
+
#
|
29
|
+
def draw_report(results, repo_count, error_count, options)
|
30
|
+
return unless display_table?(error_count, options)
|
31
|
+
|
32
|
+
table = create_table
|
33
|
+
table = add_title(table, repo_count, error_count, options)
|
34
|
+
table = add_rows(table, results, options)
|
35
|
+
display_table(table)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#
|
2
|
+
# Class docs to go here
|
3
|
+
#
|
4
|
+
class GithubRipper
|
5
|
+
class << self
|
6
|
+
#
|
7
|
+
# Everything below here is private
|
8
|
+
#
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
13
|
+
# This method smells of :reek:NilCheck
|
14
|
+
def handle_required_parameters(options)
|
15
|
+
return if get_option(options, :token)
|
16
|
+
|
17
|
+
raise StandardError.new('Please supply a username (-u) or a token (-t)') if (flag_set?(options, :user_repos) || flag_set?(options, :org_members_repos) || flag_set?(options, :all_repos)) && get_option(options, :user).nil?
|
18
|
+
|
19
|
+
raise StandardError.new('Please supply an organisation name (-o) or a token (-t)') if flag_set?(options, :org_repos) && get_option(options, :org).nil?
|
20
|
+
end
|
21
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
22
|
+
|
23
|
+
#
|
24
|
+
# docs go here
|
25
|
+
#
|
26
|
+
def get_repo_list(options)
|
27
|
+
handle_required_parameters(options)
|
28
|
+
|
29
|
+
function = function_map_lookup(options)
|
30
|
+
|
31
|
+
raise StandardError.new('Missing parameters') unless function
|
32
|
+
|
33
|
+
JSON.parse(function_wrapper(function, options))
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# This method smells of :reek:DuplicateMethodCall
|
38
|
+
#
|
39
|
+
def rip_repos(options, repos)
|
40
|
+
repo_count = repos.size
|
41
|
+
|
42
|
+
results = if flag_set?(options, :silent)
|
43
|
+
Parallel.map(repos) { |repo| clone_repo_wrapper(options, repo) }
|
44
|
+
else
|
45
|
+
Parallel.map(repos, :progress => "Cloning #{repo_count}") { |repo| clone_repo_wrapper(options, repo) }
|
46
|
+
end
|
47
|
+
results.compact
|
48
|
+
end
|
49
|
+
|
50
|
+
def process_results(results, options)
|
51
|
+
repo_count = count_repos(results)
|
52
|
+
error_count = count_errors(results)
|
53
|
+
results = filter_results(results, options)
|
54
|
+
[results, repo_count, error_count]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#
|
2
|
+
# Class level docs
|
3
|
+
#
|
4
|
+
class GithubRipper
|
5
|
+
class << self
|
6
|
+
#
|
7
|
+
# Everything below here is private
|
8
|
+
#
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# This method smells of :reek:UtilityFunction
|
13
|
+
def create_table
|
14
|
+
Terminal::Table.new :headings => ['Repo', 'Path', 'Status', 'When', 'Information']
|
15
|
+
end
|
16
|
+
|
17
|
+
# This method smells of :reek:ControlParameter, :reek:LongParameterList
|
18
|
+
def add_title(table, repo_count, error_count, options)
|
19
|
+
title = "There were #{error_count} #{plural(error_count, 'error')}"
|
20
|
+
title += " from #{repo_count} #{plural(repo_count, 'repository', 'respositories')}" if flag_set?(options, :full_report) || flag_set?(options, :dry_run)
|
21
|
+
|
22
|
+
table.title = title
|
23
|
+
table
|
24
|
+
end
|
25
|
+
|
26
|
+
# This method smells of :reek:UtilityFunction, :reek:ControlParameter
|
27
|
+
def visible_row?(repo, options)
|
28
|
+
# Dry run - force display of all
|
29
|
+
return true if flag_set?(options, :dry_run)
|
30
|
+
|
31
|
+
repo[:status] == 'Failed' || flag_set?(options, :full_report)
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_single_row(table, repo, options)
|
35
|
+
status = repo[:status]
|
36
|
+
table.add_row [set_colour(repo[:repo], status), set_colour(repo[:path], status), set_colour(status, status), set_colour(repo[:when], status), set_colour(repo[:info], status)] if visible_row?(repo, options)
|
37
|
+
table
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_rows(table, results, options)
|
41
|
+
results.each { |repo| table = add_single_row(table, repo, options) }
|
42
|
+
table
|
43
|
+
end
|
44
|
+
|
45
|
+
def display_table(table)
|
46
|
+
puts table
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#
|
2
|
+
# Class docs to go here
|
3
|
+
#
|
4
|
+
class GithubRipper
|
5
|
+
class << self
|
6
|
+
#
|
7
|
+
# Everything below here is private
|
8
|
+
#
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
#
|
13
|
+
# This method smells of :reek:DuplicateMethodCall
|
14
|
+
#
|
15
|
+
def set_colour(item, status)
|
16
|
+
return Rainbow(item).yellow if item.downcase == 'dry run'
|
17
|
+
|
18
|
+
case status.downcase
|
19
|
+
when 'dry run'
|
20
|
+
Rainbow(item).cyan
|
21
|
+
when 'failed'
|
22
|
+
Rainbow(item).red
|
23
|
+
else
|
24
|
+
Rainbow(item).green
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# This method smells :reek:UtilityFunction, :reek:ControlParameter
|
29
|
+
def plural(count, singular, plural = nil)
|
30
|
+
if count == 1
|
31
|
+
singular.to_s
|
32
|
+
elsif plural
|
33
|
+
plural.to_s
|
34
|
+
else
|
35
|
+
"#{singular}s"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# This method smells of :reek:UtilityFunction
|
40
|
+
def count_repos(results)
|
41
|
+
results.size
|
42
|
+
end
|
43
|
+
|
44
|
+
# This method smells of :reek:UtilityFunction
|
45
|
+
def count_errors(results)
|
46
|
+
results.select { |repo| repo[:status] == 'Failed' }.size
|
47
|
+
end
|
48
|
+
|
49
|
+
# This method smells of :reek:UtilityFunction
|
50
|
+
def filter_results(results, options)
|
51
|
+
results.select { |repo| repo[:status] == 'Failed' || flag_set?(options, :full_report) || flag_set?(options, :dry_run) }.sort_by { |repo| repo[:repo].downcase }
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_option(options, name)
|
55
|
+
options[name] if options.key?(name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def flag_set?(options, name)
|
59
|
+
return true if options.key?(name) && options[name] == true
|
60
|
+
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# Class docs to go here
|
3
|
+
#
|
4
|
+
class GithubRipper
|
5
|
+
class << self
|
6
|
+
#
|
7
|
+
# Everything below here is private
|
8
|
+
#
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def function_wrapper(function, options)
|
13
|
+
begin
|
14
|
+
results = GithubListerCore.send(function, options)
|
15
|
+
rescue UnknownError, InvalidTokenError, MissingTokenError, TooManyRequests, NotFoundError, MissingOrganisationError, InvalidOptionsHashError => exception
|
16
|
+
raise StandardError.new(exception.to_s)
|
17
|
+
end
|
18
|
+
results || []
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/stale.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Number of days of inactivity before an issue becomes stale
|
2
|
+
daysUntilStale: 60
|
3
|
+
# Number of days of inactivity before a stale issue is closed
|
4
|
+
daysUntilClose: 7
|
5
|
+
# Issues with these labels will never be considered stale
|
6
|
+
exemptLabels:
|
7
|
+
- pinned
|
8
|
+
- security
|
9
|
+
# Label to use when marking an issue as stale
|
10
|
+
staleLabel: wontfix
|
11
|
+
# Comment to post when marking an issue as stale. Set to `false` to disable
|
12
|
+
markComment: >
|
13
|
+
This issue has been automatically marked as stale because it has not had
|
14
|
+
recent activity. It will be closed if no further activity occurs. Thank you
|
15
|
+
for your contributions.
|
16
|
+
# Comment to post when closing a stale issue. Set to `false` to disable
|
17
|
+
closeComment: true
|
data/testing/VERSION.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/testing/get-raw.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This is a very simply testing script to allow for testing of local changes without having to install the gem locally
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
$LOAD_PATH.unshift('./lib')
|
9
|
+
|
10
|
+
require 'bundler/setup'
|
11
|
+
require 'github-ripper'
|
12
|
+
|
13
|
+
options = {}
|
14
|
+
|
15
|
+
pp GithubRipper.rip(options)
|
data/testing/ghrip
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift('./lib')
|
4
|
+
|
5
|
+
require 'bundler/setup'
|
6
|
+
|
7
|
+
require 'github-ripper'
|
8
|
+
require 'json'
|
9
|
+
require 'optparse'
|
10
|
+
require 'pp'
|
11
|
+
|
12
|
+
MANDATORY_PARAMETERS = [].freeze
|
13
|
+
DEFAULT_VALUES = {}.freeze
|
14
|
+
|
15
|
+
def init_default_parser
|
16
|
+
parser = OptionParser.new
|
17
|
+
|
18
|
+
parser.banner = "Usage: #{$PROGRAM_NAME}"
|
19
|
+
|
20
|
+
parser.on('-h', '--help', 'Display this screen') do
|
21
|
+
puts parser
|
22
|
+
exit(1)
|
23
|
+
end
|
24
|
+
parser
|
25
|
+
end
|
26
|
+
|
27
|
+
# This method reeks of :reek:UtilityFunction, :reek:TooManyStatements
|
28
|
+
def add_parameters(parser, options)
|
29
|
+
parser.separator ''
|
30
|
+
parser.separator 'Parameters:'
|
31
|
+
parser.on('-t', '--token <token>', 'GitHub personal access token (PAT)') { |token| options[:token] = token }
|
32
|
+
parser.on('-b', '--base-dir <path>', "The base directory to download to [default: #{File.expand_path('~')}/Downloads/]") { |base_dir| options[:base_dir] = base_dir }
|
33
|
+
parser.on('-g', '--use-git', TrueClass, 'Use git instead of https to clone the repositories') { |_errors| options[:use_git] = true }
|
34
|
+
[parser, options]
|
35
|
+
end
|
36
|
+
|
37
|
+
# This method reeks of :reek:UtilityFunction, :reek:TooManyStatements
|
38
|
+
def add_repo_flag_parameters(parser, options)
|
39
|
+
parser.separator ''
|
40
|
+
parser.separator 'Cloning Parameters:'
|
41
|
+
parser.on('-u', '--user <names>', 'Github username(s) to rip') { |user| options[:user] = user }
|
42
|
+
parser.on('-o', '--org <names>', 'Github organisation(s) to rip') { |org| options[:org] = org }
|
43
|
+
parser.on('-U', '--user-repos', TrueClass, 'Rip all of the repositories for the named user(s)') { |_dry_run| options[:user_repos] = true }
|
44
|
+
parser.on('-M', '--org-member-repos', TrueClass, 'Rip all of the repositories for all organisation the user(s) is a member of') { |_quiet| options[:org_members_repos] = true }
|
45
|
+
parser.on('-A', '--all-repos', TrueClass, 'Same as running -U -M') { |_silent| options[:all_repos] = true }
|
46
|
+
parser.on('-O', '--org-repos', TrueClass, 'Rip all of the repositories for the named organisation(s)') { |_errors| options[:org_repos] = true }
|
47
|
+
[parser, options]
|
48
|
+
end
|
49
|
+
|
50
|
+
# This method reeks of :reek:UtilityFunction, :reek:TooManyStatements
|
51
|
+
def add_flag_parameters(parser, options)
|
52
|
+
parser.separator ''
|
53
|
+
parser.separator 'Flags:'
|
54
|
+
parser.on('-d', '--dry-run', 'Show a list of repositories that WOULD be ripped') { |_dry_run| options[:dry_run] = true }
|
55
|
+
parser.on('-f', '--full', 'Show status of all repositories in post run report') { |_errors| options[:full_report] = true }
|
56
|
+
parser.on('-q', '--quiet', 'Suppress the showing of the post run report') { |_quiet| options[:quiet] = true }
|
57
|
+
parser.on('-s', '--silent', 'Suppress all output') { |_silent| options[:silent] = true }
|
58
|
+
[parser, options]
|
59
|
+
end
|
60
|
+
|
61
|
+
def create_parser
|
62
|
+
options = DEFAULT_VALUES.dup
|
63
|
+
|
64
|
+
parser = init_default_parser
|
65
|
+
parser, options = add_parameters(parser, options)
|
66
|
+
parser, options = add_repo_flag_parameters(parser, options)
|
67
|
+
parser, options = add_flag_parameters(parser, options)
|
68
|
+
|
69
|
+
[parser, options]
|
70
|
+
end
|
71
|
+
|
72
|
+
def process_parser(parser, options)
|
73
|
+
begin
|
74
|
+
parser.parse!
|
75
|
+
missing = MANDATORY_PARAMETERS.reject { |param| options.include?(param) }
|
76
|
+
raise OptionParser::MissingArgument.new(missing.join(', ')) unless missing.empty?
|
77
|
+
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument => exception
|
78
|
+
exit_with_error(parser, exception)
|
79
|
+
end
|
80
|
+
#
|
81
|
+
# The parser will use :"some-symbol" so we convert it to :some_symbol
|
82
|
+
#
|
83
|
+
options.transform_keys(&:to_s).transform_keys { |key| key.gsub('-', '_') }.transform_keys(&:to_sym)
|
84
|
+
end
|
85
|
+
|
86
|
+
def exit_with_error(parser, exception = nil)
|
87
|
+
puts exception if exception
|
88
|
+
puts parser.help
|
89
|
+
exit
|
90
|
+
end
|
91
|
+
|
92
|
+
def process_arguments
|
93
|
+
parser, options = create_parser
|
94
|
+
options = process_parser(parser, options)
|
95
|
+
|
96
|
+
exit_with_error(parser) if options.empty?
|
97
|
+
options
|
98
|
+
end
|
99
|
+
|
100
|
+
def main
|
101
|
+
options = process_arguments
|
102
|
+
|
103
|
+
begin
|
104
|
+
GithubRipper.rip(options)
|
105
|
+
rescue StandardError => exception
|
106
|
+
puts exception
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
main
|