github_records_archiver 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20d21627161e4f5e187d555f3a2f12b925a633c6c8c3c2ca3bf28b6aee26ed18
4
- data.tar.gz: '0805b746a6b6495fddf921a9638245286ec6d89da41dfcf6c30117f514ea8fb7'
3
+ metadata.gz: 04613c74d230d25723b56ff88a3e34c840d8a8ae56bc7543a386f0bb91fc7ca4
4
+ data.tar.gz: 0eff9d4f24718e4db5107ccfb6ffd8c45f5783f31de16b51edea0324cda20803
5
5
  SHA512:
6
- metadata.gz: 448ef59368b08c0ffb7e221ecad0b0674ed5cbfb85a1dceec9f2ba3bbd5103d00694032decbf896f6febd34fb7ddae2e6324e539e4da70482e2a6df0170e6b8a
7
- data.tar.gz: 2eaec591f98b3359ee9f3769c2797f71db0b94c85480be4f5eb29b80903fa7eccfb357c12b5616347f09fb335982aa5305ae83cd69cfe17a6fa96feef665220d
6
+ metadata.gz: 0704f880ce05d5f0990f36a02bd7962452e072255d88f53d00f5878b035908a7171cf155bdb4284b6cdc8ca5d94ff89f8fbd98fd55bbe6fffb9f6b6226ff860a
7
+ data.tar.gz: 328a9351d1161903ffe67f0b3bbcacb8f1830baf87bd72a157c5306d7ab8af75c84d7df49d689d34c9745fe58c843b479573ba038ab726cd9febd4d89d824867
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  /.env
2
2
  /archive
3
- Gemfile.lock
4
- spec/examples.txt
5
- *.gem
3
+ /coverage/
4
+ /Gemfile.lock
5
+ /spec/examples.txt
6
+ /*.gem
data/.rubocop.yml CHANGED
@@ -2,7 +2,8 @@ AllCops:
2
2
  Exclude:
3
3
  - archive/**/*
4
4
  - vendor/**/*
5
-
5
+ - bin/github-records-archiver
6
+
6
7
  Style/Documentation:
7
8
  Enabled: false
8
9
 
data/Gemfile CHANGED
@@ -3,3 +3,5 @@ source 'https://rubygems.org'
3
3
  git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
5
  gemspec
6
+
7
+ gem 'coveralls', require: false
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GitHub Records Archiver
2
2
 
3
- [![Build Status](https://travis-ci.org/benbalter/github-records-archiver.svg?branch=master)](https://travis-ci.org/benbalter/github-records-archiver) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
3
+ [![Build Status](https://travis-ci.org/benbalter/github_records_archiver.svg?branch=master)](https://travis-ci.org/benbalter/github_records_archiver) [![Gem Version](https://badge.fury.io/rb/github_records_archiver.svg)](http://badge.fury.io/rb/github_records_archiver) [![Coverage Status](https://coveralls.io/repos/github/benbalter/github_records_archiver/badge.svg)](https://coveralls.io/github/benbalter/github_records_archiver) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](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 `public_repo` and `repo` scope.
17
+ 2. A GitHub [personal access token](https://github.com/settings/tokens/new) with `repo` scope.
18
18
 
19
19
  ## Setup
20
20
 
21
- 1. `git clone https://github.com/benbalter/github-records-archiver`
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
- ## Usage
23
+ ```shell
24
+ gem install github_records_archiver
25
+ ```
27
26
 
28
- `bin/archive [ORGANIZATION]`
27
+ ## Basic usage
29
28
 
30
- You'll want to set the following environmental variable:
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
- * `GITHUB_TOKEN` - Your personal access token
33
-
34
- You *may* set the following environmental variables:
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 repository.
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
- ENV['GITHUB_ARCHIVE_DIR'] || File.expand_path('./archive', Dir.pwd)
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
- if Dir.exist? repo_dir # Repo already exists, just pull new objects
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 # Clone Git content from scratch
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
- raise GitError, output if status.exitstatus != 0
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
@@ -1,3 +1,3 @@
1
1
  module GitHubRecordsArchiver
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
@@ -13,8 +13,7 @@ module GitHubRecordsArchiver
13
13
  end
14
14
 
15
15
  def repo_dir
16
- name = "#{repository.full_name}/wiki"
17
- @repo_dir ||= File.expand_path name, GitHubRecordsArchiver.dest_dir
16
+ @repo_dir ||= File.join repository.repo_dir, 'wiki'
18
17
  end
19
18
 
20
19
  private
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.1.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
- - archive
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/archive
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."