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.
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+
6
+ This changelog was automatically generated using [Caretaker](https://github.com/DevelopersToolbox/caretaker) by [Wolf Software](https://github.com/WolfSoftware)
7
+
8
+ ### [v0.1.0](https://github.com/DevelopersToolbox/github-ripper/releases/v0.1.0)
9
+
10
+ > Released on March, 12th 2021
11
+
12
+ - The initial commit [`[head]`](https://github.com/DevelopersToolbox/github-ripper/commit/)
13
+
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in slackit.gemspec
6
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,25 @@
1
+ The MIT License (MIT)
2
+ =====================
3
+
4
+ Copyright © `2009-2021` `Wolf Software Limited`
5
+
6
+ Permission is hereby granted, free of charge, to any person
7
+ obtaining a copy of this software and associated documentation
8
+ files (the “Software”), to deal in the Software without
9
+ restriction, including without limitation the rights to use,
10
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the
12
+ Software is furnished to do so, subject to the following
13
+ conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,139 @@
1
+ <h1 align="center">
2
+ <a href="https://github.com/WolfSoftware">
3
+ <img src="https://raw.githubusercontent.com/WolfSoftware/branding/master/images/general/banners/64/black-and-white.png" alt="Wolf Software Logo" />
4
+ </a>
5
+ <br>
6
+ GitHub Ripper - Bulk Cloning Tool
7
+ </h1>
8
+
9
+ <p align="center">
10
+ <a href="https://travis-ci.com/DevelopersToolbox/github-ripper">
11
+ <img src="https://img.shields.io/travis/com/DevelopersToolbox/github-ripper/master?style=for-the-badge&logo=travis" alt="Build Status">
12
+ </a>
13
+ <a href="https://github.com/DevelopersToolbox/github-ripper/releases/latest">
14
+ <img src="https://img.shields.io/github/v/release/DevelopersToolbox/github-ripper?color=blue&style=for-the-badge&logo=github&logoColor=white&label=Latest%20Release" alt="Release">
15
+ </a>
16
+ <a href="https://github.com/DevelopersToolbox/github-ripper/releases/latest">
17
+ <img src="https://img.shields.io/github/commits-since/DevelopersToolbox/github-ripper/latest.svg?color=blue&style=for-the-badge&logo=github&logoColor=white" alt="Commits since release">
18
+ </a>
19
+ <a href="LICENSE.md">
20
+ <img src="https://img.shields.io/badge/license-MIT-blue?style=for-the-badge&logo=read-the-docs&logoColor=white" alt="Software License">
21
+ </a>
22
+ <br>
23
+ <a href=".github/CODE_OF_CONDUCT.md">
24
+ <img src="https://img.shields.io/badge/Code%20of%20Conduct-blue?style=for-the-badge&logo=read-the-docs&logoColor=white" />
25
+ </a>
26
+ <a href=".github/CONTRIBUTING.md">
27
+ <img src="https://img.shields.io/badge/Contributing-blue?style=for-the-badge&logo=read-the-docs&logoColor=white" />
28
+ </a>
29
+ <a href=".github/SECURITY.md">
30
+ <img src="https://img.shields.io/badge/Report%20Security%20Concern-blue?style=for-the-badge&logo=read-the-docs&logoColor=white" />
31
+ </a>
32
+ <a href=".github/SUPPORT.md">
33
+ <img src="https://img.shields.io/badge/Get%20Support-blue?style=for-the-badge&logo=read-the-docs&logoColor=white" />
34
+ </a>
35
+ </p>
36
+
37
+ ## Overview
38
+
39
+ Ghrip is a command line tool for ripping (cloning) repositories from GitHub in bulk. It allows you to clone all repositories owned by a named user or users, or from an organisation or organisations.
40
+
41
+ To make the cloning as fast as possible we make extensive use of the [Parallel](https://rubygems.org/gems/parallel) gem to run as many clones as possible in parallel.
42
+
43
+ ### Tokens
44
+
45
+ There is no requirement to supply a token when using ghrip, however there are numerous benefits. including but not limited to:
46
+
47
+ 1. Less impact from connection throttling (Unauthenticated connections are throttled more heavily by GitHub)
48
+ 2. Ability to clone private repositories
49
+ 3. Simple to clone all of your own repositories (no need to specify a username)
50
+
51
+ You could for example clone ALL the repositories that you own with a single command.
52
+
53
+ ```
54
+ # ghrip -t <Your token goes here> -A
55
+ ```
56
+
57
+ > If you just wanted your personal repositories you could use -U instead of -A, or -M if you just wanted the repositories for organisations that you are a member of.
58
+
59
+ If you do not supply token you will be limited to just cloning public repositories.
60
+
61
+ ### No Passwords?
62
+
63
+ We intentionally do **NOT** handle username + password combinations when talking to GitHub, this removes some security risks and also removes the issue of having to handle MFA logins. It allows you to create a `read-only` token for cloning only to further improve your security position.
64
+
65
+ ### Example
66
+
67
+ The following is an example of how to rip all of the **public** repositories owned by Wolf Software.
68
+
69
+ ```shell
70
+ # ghrip -o WolfSoftware
71
+ ```
72
+
73
+ ## Usage
74
+
75
+ ```shell
76
+ Usage: ghrip
77
+ -h, --help Display this screen
78
+
79
+ Parameters:
80
+ -t, --token <token> GitHub personal access token (PAT)
81
+ -b, --base-dir <path> The base directory to download to
82
+ -g, --use-git Use git instead of https to clone the repositories
83
+
84
+ Cloning Parameters:
85
+ -u, --user <names> Github username(s) to rip
86
+ -o, --org <names> Github organisation(s) to rip
87
+ -U, --user-repos Rip all of the repositories for the named user(s)
88
+ -M, --org-member-repos Rip all of the repositories for all organisation the user(s) is a member of
89
+ -A, --all-repos Same as running -U -M
90
+ -O, --org-repos Rip all of the repositories for the named organisation(s)
91
+
92
+ Flags:
93
+ -d, --dry-run Show a list of repositories that WOULD be ripped
94
+ -f, --full Show status of all repositories in post run report
95
+ -q, --quiet Suppress the showing of the post run report
96
+ -s, --silent Suppress all output
97
+ ```
98
+
99
+ > If you just supply a username and nothing else it will default to just the users own repositories (same as -U)
100
+
101
+ ### Default Values
102
+
103
+ | Option Name | Default Value | Purpose |
104
+ | ---------------- | -------------- | ------- |
105
+ | -t, --token | No default | The token used to authenticate yourself (not required but helps remove/mitigate throttling issues) |
106
+ | -b, --base-dir | ~/Downloads/ | The directory to place the cloned repos in |
107
+ | -g, --use-git | False | Use git instead of https to clone the repos, this requires your public key to be configured on GitHub |
108
+ | -u, --user | No default | The user(s) you wish to rip the repo of (if not supplied defaults to yourself **IF** a token is used) |
109
+ | -o, --org | No default | The organisation(s) you want to rip the repos of |
110
+ | -U, --user-repos | False | Download all repositories belonging to the named user(s) |
111
+ | -M, --org-member-repos | False | Download all repositories belonging to any organisation the named user(s) is a member of |
112
+ | -A, --all-repos | False | Works the same as --user-repo and --org-member-repos combined |
113
+ | -O, --org-repos | False | Download all repositories belonging to the named organisation(s) |
114
+ | -d, --dry-run | False | Display the list of repositories that **would** be downloaded |
115
+ | -f, --full | False | Show all repositories in the post run report instead of just errors |
116
+ | -q, --quiet | False | Suppress the showing of the post run report |
117
+ | -s, --silent | False | Suppress all output |
118
+
119
+ > users and orgs can be either a single entry or comma-separated list of entries.
120
+
121
+ ### Error Handling
122
+
123
+ All errors raised from github-core-lister are caught and re-thrown as StandardErrors in order to simplify the catching.
124
+
125
+ ## Contributors
126
+
127
+ <p>
128
+ <a href="https://github.com/TGWolf">
129
+ <img src="https://img.shields.io/badge/Wolf-black?style=for-the-badge" />
130
+ </a>
131
+ </p>
132
+
133
+ ## Show Support
134
+
135
+ <p>
136
+ <a href="https://ko-fi.com/wolfsoftware">
137
+ <img src="https://img.shields.io/badge/Ko%20Fi-blue?style=for-the-badge&logo=ko-fi&logoColor=white" />
138
+ </a>
139
+ </p>
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/VERSION.txt ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'github-ripper'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/ghrip ADDED
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'github-ripper'
4
+ require 'json'
5
+ require 'optparse'
6
+ require 'pp'
7
+
8
+ MANDATORY_PARAMETERS = [].freeze
9
+ DEFAULT_VALUES = {}.freeze
10
+
11
+ def init_default_parser
12
+ parser = OptionParser.new
13
+
14
+ parser.banner = "Usage: #{$PROGRAM_NAME}"
15
+
16
+ parser.on('-h', '--help', 'Display this screen') do
17
+ puts parser
18
+ exit(1)
19
+ end
20
+ parser
21
+ end
22
+
23
+ # This method reeks of :reek:UtilityFunction, :reek:TooManyStatements
24
+ def add_parameters(parser, options)
25
+ parser.separator ''
26
+ parser.separator 'Parameters:'
27
+ parser.on('-t', '--token <token>', 'GitHub personal access token (PAT)') { |token| options[:token] = token }
28
+ parser.on('-b', '--base-dir <path>', "The base directory to download to [default: #{File.expand_path('~')}/Downloads/]") { |base_dir| options[:base_dir] = base_dir }
29
+ parser.on('-g', '--use-git', TrueClass, 'Use git instead of https to clone the repositories') { |_errors| options[:use_git] = true }
30
+ [parser, options]
31
+ end
32
+
33
+ # This method reeks of :reek:UtilityFunction, :reek:TooManyStatements
34
+ def add_repo_flag_parameters(parser, options)
35
+ parser.separator ''
36
+ parser.separator 'Cloning Parameters:'
37
+ parser.on('-u', '--user <names>', 'Github username(s) to rip') { |user| options[:user] = user }
38
+ parser.on('-o', '--org <names>', 'Github organisation(s) to rip') { |org| options[:org] = org }
39
+ parser.on('-U', '--user-repos', TrueClass, 'Rip all of the repositories for the named user(s)') { |_dry_run| options[:user_repos] = true }
40
+ 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 }
41
+ parser.on('-A', '--all-repos', TrueClass, 'Same as running -U -M') { |_silent| options[:all_repos] = true }
42
+ parser.on('-O', '--org-repos', TrueClass, 'Rip all of the repositories for the named organisation(s)') { |_errors| options[:org_repos] = true }
43
+ [parser, options]
44
+ end
45
+
46
+ # This method reeks of :reek:UtilityFunction, :reek:TooManyStatements
47
+ def add_flag_parameters(parser, options)
48
+ parser.separator ''
49
+ parser.separator 'Flags:'
50
+ parser.on('-d', '--dry-run', 'Show a list of repositories that WOULD be ripped') { |_dry_run| options[:dry_run] = true }
51
+ parser.on('-f', '--full', 'Show status of all repositories in post run report') { |_errors| options[:full_report] = true }
52
+ parser.on('-q', '--quiet', 'Suppress the showing of the post run report') { |_quiet| options[:quiet] = true }
53
+ parser.on('-s', '--silent', 'Suppress all output') { |_silent| options[:silent] = true }
54
+ [parser, options]
55
+ end
56
+
57
+ def create_parser
58
+ options = DEFAULT_VALUES.dup
59
+
60
+ parser = init_default_parser
61
+ parser, options = add_parameters(parser, options)
62
+ parser, options = add_repo_flag_parameters(parser, options)
63
+ parser, options = add_flag_parameters(parser, options)
64
+
65
+ [parser, options]
66
+ end
67
+
68
+ def process_parser(parser, options)
69
+ begin
70
+ parser.parse!
71
+ missing = MANDATORY_PARAMETERS.reject { |param| options.include?(param) }
72
+ raise OptionParser::MissingArgument.new(missing.join(', ')) unless missing.empty?
73
+ rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument => exception
74
+ exit_with_error(parser, exception)
75
+ end
76
+ #
77
+ # The parser will use :"some-symbol" so we convert it to :some_symbol
78
+ #
79
+ options.transform_keys(&:to_s).transform_keys { |key| key.gsub('-', '_') }.transform_keys(&:to_sym)
80
+ end
81
+
82
+ def exit_with_error(parser, exception = nil)
83
+ puts exception if exception
84
+ puts parser.help
85
+ exit
86
+ end
87
+
88
+ def process_arguments
89
+ parser, options = create_parser
90
+ options = process_parser(parser, options)
91
+
92
+ exit_with_error(parser) if options.empty?
93
+ options
94
+ end
95
+
96
+ def main
97
+ options = process_arguments
98
+
99
+ begin
100
+ GithubRipper.rip(options)
101
+ rescue StandardError => exception
102
+ puts exception
103
+ end
104
+ end
105
+
106
+ main
@@ -0,0 +1,49 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'github-ripper/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'github-ripper'
8
+ spec.version = GithubRipper::VERSION
9
+ spec.authors = ['Tim Gurney aka Wolf']
10
+ spec.email = ['wolf@tgwolf.com']
11
+
12
+ spec.summary = 'Ghrip is a command line tool for ripping (cloning) repositories from GitHub in bulk.'
13
+ spec.description = 'Ghrip is a command line tool for ripping (cloning) repositories from GitHub in bulk. It allows you to clone all repositories owned by a named user or users, or from an organisation or organisations.'
14
+ spec.homepage = 'https://github.com/DevelopersToolbox/github-ripper'
15
+ spec.license = 'MIT'
16
+
17
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/DevelopersToolbox/github-ripper'
21
+ spec.metadata['changelog_uri'] = 'https://github.com/DevelopersToolbox/github-ripper/blob/master/CHANGELOG.md'
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ # rubocop:disable Layout/ExtraSpacing, Layout/SpaceAroundOperators
26
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
27
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+ end
29
+ # rubocop:enable Layout/ExtraSpacing, Layout/SpaceAroundOperators
30
+
31
+ spec.bindir = 'exe'
32
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ['lib']
34
+
35
+ spec.add_development_dependency 'bundler', '~> 2'
36
+ spec.add_development_dependency 'github-lister-core', '~> 0.1'
37
+ spec.add_development_dependency 'parallel', '~> 1.2'
38
+ spec.add_development_dependency 'rainbow', '~> 3'
39
+ spec.add_development_dependency 'rake', '~> 12.3.3'
40
+ spec.add_development_dependency 'rspec', '~> 3.0'
41
+ spec.add_development_dependency 'ruby-progressbar', '~> 1'
42
+ spec.add_development_dependency 'terminal-table', '~> 3'
43
+
44
+ spec.add_runtime_dependency 'github-lister-core', '~> 0.1'
45
+ spec.add_runtime_dependency 'parallel', '~> 1.2'
46
+ spec.add_runtime_dependency 'rainbow', '~> 3'
47
+ spec.add_runtime_dependency 'ruby-progressbar', '~> 1'
48
+ spec.add_runtime_dependency 'terminal-table', '~> 3'
49
+ end
@@ -0,0 +1,36 @@
1
+ require 'fileutils'
2
+ require 'github-lister-core'
3
+ require 'json'
4
+ require 'open3'
5
+ require 'rainbow'
6
+ require 'terminal-table'
7
+
8
+ require_relative 'github-ripper/function-maps'
9
+ require_relative 'github-ripper/git'
10
+ require_relative 'github-ripper/report'
11
+ require_relative 'github-ripper/repos'
12
+ require_relative 'github-ripper/table'
13
+ require_relative 'github-ripper/utils'
14
+ require_relative 'github-ripper/version'
15
+ require_relative 'github-ripper/wrapper'
16
+
17
+ #
18
+ # Class level docs
19
+ #
20
+ class GithubRipper
21
+ class << self
22
+ #
23
+ # Do something ...
24
+ #
25
+ def rip(options = {})
26
+ # The following is to force github-lister-core to only retun repo slugs
27
+ options[:use_slugs] = true
28
+ options[:base_dir] = "#{File.expand_path('~')}/Downloads" unless get_option(options, :base_dir)
29
+
30
+ repos = get_repo_list(options)
31
+ results = rip_repos(options, repos)
32
+ results, repo_count, error_count = process_results(results, options)
33
+ draw_report(results, repo_count, error_count, options)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,31 @@
1
+ #
2
+ # Class docs
3
+ #
4
+ class GithubRipper
5
+ #
6
+ # Lookup the github-lister-core function from the options given
7
+ #
8
+ FUNCTION_MAP = {
9
+ :user_repos => 'user_repos',
10
+ :org_repos => 'org_repos',
11
+ :org_members_repos => 'org_members_repos',
12
+ :all_repos => 'all_repos'
13
+ }.freeze
14
+
15
+ class << self
16
+ #
17
+ # Everything below here is private
18
+ #
19
+
20
+ private
21
+
22
+ def function_map_lookup(options)
23
+ return FUNCTION_MAP[:user_repos] if flag_set?(options, :user_repos) || get_option(options, :user)
24
+ return FUNCTION_MAP[:org_members_repos] if flag_set?(options, :org_members_repos)
25
+ return FUNCTION_MAP[:all_repos] if flag_set?(options, :all_repos)
26
+ return FUNCTION_MAP[:org_repos] if flag_set?(options, :org_repos) || get_option(options, :org)
27
+
28
+ nil
29
+ end
30
+ end
31
+ end