github_records_archiver 0.1.0 → 0.2.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 +4 -4
- data/.coveralls.yml +1 -0
- data/.gitignore +4 -3
- data/.rubocop.yml +2 -1
- data/Gemfile +2 -0
- data/README.md +55 -21
- data/bin/github-records-archiver +77 -0
- data/github_records_archiver.gemspec +1 -0
- data/lib/github_records_archiver.rb +24 -8
- data/lib/github_records_archiver/git_repository.rb +11 -3
- data/lib/github_records_archiver/repository.rb +4 -6
- data/lib/github_records_archiver/version.rb +1 -1
- data/lib/github_records_archiver/wiki.rb +1 -2
- metadata +18 -3
- data/bin/archive +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04613c74d230d25723b56ff88a3e34c840d8a8ae56bc7543a386f0bb91fc7ca4
|
4
|
+
data.tar.gz: 0eff9d4f24718e4db5107ccfb6ffd8c45f5783f31de16b51edea0324cda20803
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0704f880ce05d5f0990f36a02bd7962452e072255d88f53d00f5878b035908a7171cf155bdb4284b6cdc8ca5d94ff89f8fbd98fd55bbe6fffb9f6b6226ff860a
|
7
|
+
data.tar.gz: 328a9351d1161903ffe67f0b3bbcacb8f1830baf87bd72a157c5306d7ab8af75c84d7df49d689d34c9745fe58c843b479573ba038ab726cd9febd4d89d824867
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# GitHub Records Archiver
|
2
2
|
|
3
|
-
[](https://travis-ci.org/benbalter/github_records_archiver) [](http://badge.fury.io/rb/github_records_archiver) [](https://coveralls.io/github/benbalter/github_records_archiver) [](http://makeapullrequest.com)
|
4
4
|
|
5
5
|
Backs up a GitHub organization's repositories and all their associated information for archival purposes.
|
6
6
|
|
@@ -14,38 +14,72 @@ Backs up a GitHub organization's repositories and all their associated informati
|
|
14
14
|
## Requirements
|
15
15
|
|
16
16
|
1. Ruby
|
17
|
-
2. A GitHub [personal access token](https://github.com/settings/tokens/new) with `
|
17
|
+
2. A GitHub [personal access token](https://github.com/settings/tokens/new) with `repo` scope.
|
18
18
|
|
19
19
|
## Setup
|
20
20
|
|
21
|
-
|
22
|
-
2. `cd github-records-archiver`
|
23
|
-
3. `gem install bundler`
|
24
|
-
4. `bundle install`
|
21
|
+
If you have Ruby installed, simply run:
|
25
22
|
|
26
|
-
|
23
|
+
```shell
|
24
|
+
gem install github_records_archiver
|
25
|
+
```
|
27
26
|
|
28
|
-
|
27
|
+
## Basic usage
|
29
28
|
|
30
|
-
|
29
|
+
```shell
|
30
|
+
$ github_records_archiver archive ORGANIZATION --token PERSONAL_ACCESS_TOKEN`
|
31
|
+
```
|
32
|
+
Alternatively, you could pass the personal access token as the `GITHUB_TOKEN` environmental variable:
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
* `GITHUB_ARCHIVE_DIR` to specify the output directory. It will default to `./archive`.
|
37
|
-
* `GITHUB_ORGANIZATION` - The organization to archive if none is passed as an argument.
|
38
|
-
|
39
|
-
These can be passed as `GITHUB_TOKEN=123ABC GITHUB_ORGANIZATION=whitehouse bin/archive`.
|
40
|
-
|
41
|
-
You can also add the values to a `.env` file in the project's root directory, which will be automatically set as environmental variables.
|
34
|
+
```shell
|
35
|
+
$ GITHUB_TOKEN=1234 github_records_archiver archive ORGANIZATION`
|
36
|
+
```
|
42
37
|
|
43
38
|
## Output
|
44
39
|
|
45
|
-
The script will create an `archive` directory, with one folder for each
|
40
|
+
The script will create an `archive` directory, with one folder for each organization.
|
41
|
+
|
42
|
+
Within each organization folder, there will be one folder per repository.
|
46
43
|
|
47
|
-
Within each folder will be the repository content as a git repository.
|
44
|
+
Within each repository folder will be the repository content as a git repository.
|
48
45
|
|
49
46
|
If the repository has a Wiki, the wiki will be cloned as a `wiki` subfolder, as a Git repository.
|
50
47
|
|
51
48
|
If the repository has issues or pull requests, it will create an `issues` sub-folder with each issue and its associated comments stored as both markdown (human readable) and JSON (machine readable).
|
49
|
+
|
50
|
+
Example output:
|
51
|
+
|
52
|
+
```
|
53
|
+
├─ archive
|
54
|
+
├─── organization
|
55
|
+
├──── repository
|
56
|
+
├────── README.md
|
57
|
+
├────── LICENSE.txt
|
58
|
+
├──── wiki
|
59
|
+
├────── wiki-page.md
|
60
|
+
├──── issues
|
61
|
+
├────── 1.md
|
62
|
+
├────── 1.json
|
63
|
+
├─── another organization
|
64
|
+
├──── another-repository
|
65
|
+
├────── README.md
|
66
|
+
├────── LICENSE.txt
|
67
|
+
├──── wiki
|
68
|
+
├────── wiki-page.md
|
69
|
+
├──── issues
|
70
|
+
├────── 1.md
|
71
|
+
├────── 1.json
|
72
|
+
```
|
73
|
+
|
74
|
+
## Advanced usage
|
75
|
+
|
76
|
+
You may set the following flags:
|
77
|
+
|
78
|
+
* `--dest-dir` - the destination archive directory, defaults to `./archive`
|
79
|
+
* `--verbose` - verbose output while archiving
|
80
|
+
|
81
|
+
Additionally, the following commands are also available:
|
82
|
+
|
83
|
+
* `delete [ORGANIZATION]` - delete the entire archive directory or an organization's archive
|
84
|
+
* `help` - display help information
|
85
|
+
* `version` - display the GitHub Record Archiver version
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'parallel'
|
5
|
+
require_relative '../lib/github_records_archiver'
|
6
|
+
|
7
|
+
class GitHubRecordsArchiverCLI < Thor
|
8
|
+
package_name 'GitHub Records Archiver'
|
9
|
+
class_option :dest_dir, type: :string, required: false,
|
10
|
+
desc: "The destination directory for the archive", default: GitHubRecordsArchiver.dest_dir
|
11
|
+
class_option :token, type: :string, required: false, desc: "A GitHub personal access token with repo scope"
|
12
|
+
class_option :verbose, type: :boolean, desc: 'Display verbose output while archiving', default: false
|
13
|
+
|
14
|
+
desc 'version', 'Outputs the GitHubRecordsArchiver version'
|
15
|
+
def version
|
16
|
+
say "GitHub Records Archiver v#{GitHubRecordsArchiver::VERSION}"
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "delete [ORGANIZATION]", "Deletes all archives, or the archive for an organization"
|
20
|
+
def delete(org_name = nil)
|
21
|
+
path = GitHubRecordsArchiver.dest_dir
|
22
|
+
path = File.join path, org_name if org_name
|
23
|
+
FileUtils.rm_rf(path) if yes? "Are you sure? Remove #{path}?"
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'archive ORGANIZATION', 'Create or update archive for the given organization'
|
27
|
+
def archive(org_name)
|
28
|
+
start_time # Memoize start time for comparison
|
29
|
+
@org_name = org_name
|
30
|
+
|
31
|
+
GitHubRecordsArchiver.shell = shell
|
32
|
+
%i(token dest_dir verbose).each do |option|
|
33
|
+
next unless options[option]
|
34
|
+
GitHubRecordsArchiver.public_send "#{option}=".to_sym, options[option]
|
35
|
+
end
|
36
|
+
|
37
|
+
say "Starting archive for @#{org.name} in #{org.archive_dir}"
|
38
|
+
shell.indent(2) do
|
39
|
+
archive_teams
|
40
|
+
archive_repos
|
41
|
+
end
|
42
|
+
say "Done in #{Time.now - start_time} seconds.", :green
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def start_time
|
48
|
+
@start_time ||= Time.now
|
49
|
+
end
|
50
|
+
|
51
|
+
def organization
|
52
|
+
@organization ||= GitHubRecordsArchiver::Organization.new @org_name
|
53
|
+
end
|
54
|
+
alias_method :org, :organization
|
55
|
+
|
56
|
+
def archive_teams
|
57
|
+
say_status "Teams found:", org.teams.count, :white
|
58
|
+
Parallel.each(org.teams, progress: 'Archiving teams', &:archive)
|
59
|
+
end
|
60
|
+
|
61
|
+
def archive_repos
|
62
|
+
say_status "Repositories found:", org.repos.count, :white
|
63
|
+
|
64
|
+
Parallel.each(org.repos, progress: 'Archiving repos') do |repo|
|
65
|
+
begin
|
66
|
+
repo.clone
|
67
|
+
repo.wiki.clone if repo.has_wiki?
|
68
|
+
Parallel.each(repo.issues, &:archive)
|
69
|
+
rescue GitHubRecordsArchiver::GitError => e
|
70
|
+
say "Failed to archive #{repo.name}", :red
|
71
|
+
say e.message, :red
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
GitHubRecordsArchiverCLI.start(ARGV)
|
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_dependency 'octokit', '~> 4.0'
|
29
29
|
spec.add_dependency 'parallel', '~> 1.10'
|
30
30
|
spec.add_dependency 'ruby-progressbar', '~> 1.0'
|
31
|
+
spec.add_dependency 'thor', '~> 0.19'
|
31
32
|
spec.add_development_dependency 'addressable', '~> 2.5'
|
32
33
|
spec.add_development_dependency 'bundler', '~> 1.16'
|
33
34
|
spec.add_development_dependency 'pry', '~> 0.10'
|
@@ -1,18 +1,14 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
2
|
|
3
|
-
# stdlib
|
4
3
|
require 'yaml'
|
5
4
|
require 'json'
|
6
5
|
require 'logger'
|
7
6
|
require 'fileutils'
|
8
7
|
require 'open3'
|
9
|
-
|
10
|
-
# gems
|
8
|
+
require 'thor'
|
11
9
|
require 'octokit'
|
12
|
-
require 'dotenv'
|
10
|
+
require 'dotenv/load'
|
13
11
|
|
14
|
-
# Configuration
|
15
|
-
Dotenv.load
|
16
12
|
Octokit.auto_paginate = true
|
17
13
|
|
18
14
|
module GitHubRecordsArchiver
|
@@ -28,8 +24,10 @@ module GitHubRecordsArchiver
|
|
28
24
|
autoload :Wiki, 'github_records_archiver/wiki'
|
29
25
|
|
30
26
|
class << self
|
27
|
+
attr_writer :token, :dest_dir, :verbose, :shell
|
28
|
+
|
31
29
|
def token
|
32
|
-
ENV['GITHUB_TOKEN']
|
30
|
+
@token ||= ENV['GITHUB_TOKEN']
|
33
31
|
end
|
34
32
|
|
35
33
|
def client
|
@@ -37,7 +35,25 @@ module GitHubRecordsArchiver
|
|
37
35
|
end
|
38
36
|
|
39
37
|
def dest_dir
|
40
|
-
|
38
|
+
@dest_dir ||= File.expand_path('./archive', Dir.pwd)
|
39
|
+
end
|
40
|
+
|
41
|
+
def verbose
|
42
|
+
@verbose ||= false
|
43
|
+
end
|
44
|
+
alias verbose? verbose
|
45
|
+
|
46
|
+
def shell
|
47
|
+
@shell ||= Thor::Base.shell.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def verbose_status(status, message, color = :white)
|
51
|
+
return unless verbose?
|
52
|
+
shell.say_status status, remove_token(message), color
|
53
|
+
end
|
54
|
+
|
55
|
+
def remove_token(string)
|
56
|
+
string.gsub(GitHubRecordsArchiver.token, '<GITHUB_TOKEN>')
|
41
57
|
end
|
42
58
|
end
|
43
59
|
end
|
@@ -3,11 +3,13 @@ module GitHubRecordsArchiver
|
|
3
3
|
|
4
4
|
class GitRepository
|
5
5
|
def clone
|
6
|
-
|
6
|
+
# Repo already exists, just pull new objects
|
7
|
+
if Dir.exist? File.join(repo_dir, '.git')
|
7
8
|
Dir.chdir repo_dir do
|
8
9
|
git 'pull'
|
9
10
|
end
|
10
|
-
else
|
11
|
+
else
|
12
|
+
# Clone Git content from scratch
|
11
13
|
git 'clone', clone_url, repo_dir
|
12
14
|
end
|
13
15
|
end
|
@@ -43,8 +45,14 @@ module GitHubRecordsArchiver
|
|
43
45
|
# Run a git command, piping output to stdout
|
44
46
|
def git(*args)
|
45
47
|
output, status = Open3.capture2e('git', *args)
|
48
|
+
cmd = "git #{args.join(' ')}"
|
49
|
+
cmd << " in #{Dir.pwd}" if args == ['pull']
|
50
|
+
GitHubRecordsArchiver.verbose_status 'Git command:', cmd
|
46
51
|
return false if empty_repo?(output) || wiki_does_not_exist?(output)
|
47
|
-
|
52
|
+
if status.exitstatus != 0
|
53
|
+
output = GitHubRecordsArchiver.remove_token(output)
|
54
|
+
GitHubRecordsArchiver.shell.say_status 'Git Error', output, :red
|
55
|
+
end
|
48
56
|
output
|
49
57
|
end
|
50
58
|
end
|
@@ -40,15 +40,13 @@ module GitHubRecordsArchiver
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
private
|
44
|
-
|
45
|
-
def repo_dir
|
46
|
-
@repo_dir ||= File.expand_path data[:name], GitHubRecordsArchiver.dest_dir
|
47
|
-
end
|
48
|
-
|
49
43
|
def clone_url
|
50
44
|
replacement = "https://#{GitHubRecordsArchiver.token}:x-oauth-basic@"
|
51
45
|
data[:clone_url].gsub(%r{https?://}, replacement)
|
52
46
|
end
|
47
|
+
|
48
|
+
def repo_dir
|
49
|
+
@repo_dir ||= File.expand_path full_name, GitHubRecordsArchiver.dest_dir
|
50
|
+
end
|
53
51
|
end
|
54
52
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: github_records_archiver
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Balter
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: thor
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.19'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.19'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: addressable
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -168,10 +182,11 @@ description:
|
|
168
182
|
email:
|
169
183
|
- ben.balter@github.com
|
170
184
|
executables:
|
171
|
-
-
|
185
|
+
- github-records-archiver
|
172
186
|
extensions: []
|
173
187
|
extra_rdoc_files: []
|
174
188
|
files:
|
189
|
+
- ".coveralls.yml"
|
175
190
|
- ".github/CODEOWNERS"
|
176
191
|
- ".github/config.yml"
|
177
192
|
- ".github/no-response.yml"
|
@@ -185,7 +200,7 @@ files:
|
|
185
200
|
- LICENSE.md
|
186
201
|
- README.md
|
187
202
|
- Rakefile
|
188
|
-
- bin/
|
203
|
+
- bin/github-records-archiver
|
189
204
|
- docs/CODE_OF_CONDUCT.md
|
190
205
|
- docs/CONTRIBUTING.md
|
191
206
|
- github_records_archiver.gemspec
|
data/bin/archive
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# Backs up a GitHub organization's repositories and
|
3
|
-
# all their associated information for archival purposes.
|
4
|
-
# Usage: ruby archive.rb
|
5
|
-
|
6
|
-
require './lib/github_records_archiver'
|
7
|
-
require 'parallel'
|
8
|
-
|
9
|
-
def logger
|
10
|
-
@logger ||= Logger.new(STDOUT)
|
11
|
-
end
|
12
|
-
|
13
|
-
def log(msg)
|
14
|
-
logger.info(msg)
|
15
|
-
end
|
16
|
-
|
17
|
-
def error(msg)
|
18
|
-
logger.error(msg)
|
19
|
-
end
|
20
|
-
|
21
|
-
archiver = GitHubRecordsArchiver
|
22
|
-
pwd = Dir.pwd
|
23
|
-
start = Time.now
|
24
|
-
org_name = ARGV[0] || ENV['GITHUB_ORGANIZATION']
|
25
|
-
org = archiver::Organization.new org_name
|
26
|
-
|
27
|
-
log "Starting archive for @#{org.name} in #{org.archive_dir}"
|
28
|
-
|
29
|
-
log "Found #{org.teams.count} teams"
|
30
|
-
Parallel.each(org.teams, progress: 'Archiving teams', &:archive)
|
31
|
-
|
32
|
-
log "Found #{org.repos.count} repos"
|
33
|
-
Parallel.each(org.repos, progress: 'Archiving repos') do |repo|
|
34
|
-
begin
|
35
|
-
repo.clone
|
36
|
-
repo.wiki.clone if repo.has_wiki?
|
37
|
-
Parallel.each(repo.issues, &:archive)
|
38
|
-
rescue GitHubRecordsArchiver::GitError => e
|
39
|
-
error "Failed to archive #{repo.name}"
|
40
|
-
error e.message
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
Dir.chdir pwd
|
45
|
-
log "Done in #{Time.now - start} seconds."
|