github-backup 0.10.0 → 0.11.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
- SHA1:
3
- metadata.gz: 5369c3068e82f9ef8466455c2216ccd68d6c5b50
4
- data.tar.gz: 06f0366d478b651c8ec6a160d0502af6e2e58b41
2
+ SHA256:
3
+ metadata.gz: 7cbe362a6dd02d1e857cb3fabec20bbe633a71540a1c155fb98d1d73fc7d956a
4
+ data.tar.gz: 159a68f3e3b2b66f720475cbb86de2c582d02e837751c90a2708f79047907769
5
5
  SHA512:
6
- metadata.gz: 6723ef4b7331f84535277994b9f9bb627d9a91764f0945477a521d0f0ad6e4bf1c885b5c41bc98d591ddc30ffb2522a2e8eeb363e05f1167cdf4989baf0db073
7
- data.tar.gz: 9996e98b8d954e0b96f17f6a15c28920194cc177692820fd82787f0e9770c4cffe9d826323da74c0c67e056266b2f9b1a51ac7d425cd97dd45742766f93cd032
6
+ metadata.gz: 56ba187b5dbae8b4a2321d1ed70d7ce8a3cd680257587da202f4a67297222534c1549dfabe0c494109fc6d1c9404ace3d918a43e9c56f6eb5a0c83033178016d
7
+ data.tar.gz: b7a171b16b6bf0d825df73be89baba07c7d26c27165264cdb7e345f248b480afc4f33a0b8e1ea9bce84948e2f31b3c62dcf06230564b6336e0f0127ebf2afddf
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.5.0
4
+ - 2.4.0
5
+ sudo: false
@@ -1,18 +1,21 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- github-backup (0.10.0)
4
+ github-backup (0.11.0)
5
5
  octokit (~> 3.7)
6
6
 
7
7
  GEM
8
8
  remote: http://rubygems.org/
9
9
  specs:
10
- addressable (2.3.6)
11
- faraday (0.9.0)
10
+ addressable (2.3.8)
11
+ fakefs (0.6.7)
12
+ faraday (0.9.2)
12
13
  multipart-post (>= 1.2, < 3)
14
+ minitest (5.5.1)
13
15
  multipart-post (2.0.0)
14
- octokit (3.7.0)
16
+ octokit (3.8.0)
15
17
  sawyer (~> 0.6.0, >= 0.5.3)
18
+ rake (12.3.1)
16
19
  sawyer (0.6.0)
17
20
  addressable (~> 2.3.5)
18
21
  faraday (~> 0.8, < 0.10)
@@ -21,4 +24,10 @@ PLATFORMS
21
24
  ruby
22
25
 
23
26
  DEPENDENCIES
27
+ fakefs (~> 0.6.7)
24
28
  github-backup!
29
+ minitest (~> 5.5.0)
30
+ rake
31
+
32
+ BUNDLED WITH
33
+ 1.16.2
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
- ## github-backup
1
+ # github-backup
2
+
3
+ [![Build Status](https://travis-ci.org/ddollar/github-backup.svg?branch=master)](https://travis-ci.org/ddollar/github-backup)
4
+ [![Code Climate](https://codeclimate.com/github/ddollar/github-backup/badges/gpa.svg)](https://codeclimate.com/github/ddollar/github-backup)
2
5
 
3
6
  Back up your Github repositories locally.
4
7
 
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.test_files = FileList['spec/lib/github-backup/*_spec.rb']
5
+ t.verbose = true
6
+ end
7
+
8
+ task :default => :test
@@ -7,15 +7,27 @@ require 'optparse'
7
7
 
8
8
  options = {}
9
9
  optionparser = OptionParser.new do |opts|
10
- opts.banner = "Usage: github-backup [options] username local_backup_root"
11
-
10
+ opts.banner = "Usage: github-backup [options] username [local_backup_root]"
11
+
12
12
  opts.on("-t", "--token TOKEN", "Github API Token") do |t|
13
13
  options[:token] = t
14
14
  end
15
+
16
+ opts.on("--gists", "Backup username's gists") do |g|
17
+ options[:gists] = true
18
+ end
19
+
20
+ opts.on("--starred-gists", "Backup authenticated username's starred gists") do |g|
21
+ options[:starred_gists] = true
22
+ end
23
+
24
+ opts.on("--wikis", "Backup username's wikis") do |g|
25
+ options[:wikis] = true
26
+ end
15
27
  end
16
28
  optionparser.parse!
17
29
 
18
- unless ARGV.length == 2
30
+ unless ARGV.length >= 1
19
31
  puts optionparser.help
20
32
  exit 1
21
33
  end
@@ -18,4 +18,8 @@ Gem::Specification.new do |s|
18
18
  s.executables = "github-backup"
19
19
 
20
20
  s.add_dependency "octokit", "~> 3.7"
21
+
22
+ s.add_development_dependency "fakefs", "~> 0.6.7"
23
+ s.add_development_dependency "rake"
24
+ s.add_development_dependency "minitest", "~> 5.5.0"
21
25
  end
@@ -1,12 +1,15 @@
1
1
  module GithubBackup
2
2
  class Backup
3
- attr_reader :debug, :username, :client, :config
3
+ attr_reader :debug, :username, :client, :gists, :starred_gists, :wikis, :config
4
4
 
5
5
  def initialize(username, options = {})
6
- @username = username
7
- @debug = false
8
- @config = Config.new(options)
9
- @client = Octokit::Client.new(:access_token => config.token)
6
+ @username = username
7
+ @debug = false
8
+ @gists = options.delete(:gists)
9
+ @starred_gists = options.delete(:starred_gists)
10
+ @wikis = options.delete(:wikis)
11
+ @config = Config.new(options)
12
+ @client = Octokit::Client.new(:access_token => config.token)
10
13
  end
11
14
 
12
15
  def execute
@@ -20,85 +23,37 @@ module GithubBackup
20
23
 
21
24
  private
22
25
 
23
- def backup_root
24
- config.backup_root
25
- end
26
-
27
- def backup_directory_for(repository)
28
- File.join(backup_root, repository.full_name) + '.git'
29
- end
30
-
31
26
  def backup_all
32
- FileUtils::mkdir_p(backup_root)
33
- repositories.each do |repository|
34
- puts "Backing up: #{repository.full_name}"
35
- backup_repository repository
36
- end
37
- end
38
-
39
- def repositories
40
- repos = []
41
- repos.concat(get_repositories_first_page)
27
+ make_backup_root
28
+ repo_collection.repos(username).each { |r| r.backup(config.backup_root) }
42
29
 
43
- # Iterate over paginated response
44
- last_response = client.last_response
45
- unless last_response.rels[:next].nil?
46
- loop do
47
- last_response = last_response.rels[:next].get
48
- repos.concat(last_response.data)
49
- break if last_response.rels[:next].nil?
50
- end
30
+ if gists
31
+ repo_collection.gists(username).
32
+ each { |r| r.backup(config.backup_root) }
51
33
  end
52
34
 
53
- repos
54
- end
55
-
56
- def get_repositories_first_page
57
- if username_is_authenticated_user?
58
- client.repos
59
- elsif username_is_organisation?
60
- client.org_repos(username)
61
- else
62
- client.repos(username)
35
+ if starred_gists
36
+ repo_collection.starred_gists(username).
37
+ each { |r| r.backup(config.backup_root) }
63
38
  end
64
- end
65
39
 
66
- def backup_repository(repository)
67
- if File.exists?(backup_directory_for(repository))
68
- backup_repository_incremental(repository)
69
- else
70
- backup_repository_initial(repository)
40
+ if wikis
41
+ repo_collection.wikis(username).
42
+ each { |r| r.backup(config.backup_root) }
71
43
  end
72
44
  end
73
45
 
74
- def backup_repository_initial(repository)
75
- FileUtils::cd(backup_root) do
76
- shell("git clone --mirror -n #{repository.ssh_url} #{repository.full_name}.git")
77
- end
78
- end
79
-
80
- def backup_repository_incremental(repository)
81
- FileUtils::cd(backup_directory_for(repository)) do
82
- shell("git remote update")
83
- end
46
+ def repo_collection
47
+ @repo_collection ||= GithubBackup::GithubRepositoryCollection.new(client)
84
48
  end
85
49
 
86
- def username_is_organisation?
87
- client.user(username).type == 'Organization'
88
- end
89
-
90
- def username_is_authenticated_user?
91
- return false unless client.token_authenticated?
92
- username == client.user.login
50
+ def backup_directory_for(repository)
51
+ File.join(config.backup_root, repository.backup_path)
93
52
  end
94
53
 
95
- def shell(command)
96
- puts "EXECUTING: #{command}" if debug
97
- IO.popen(command, 'r') do |io|
98
- output = io.read
99
- puts "OUTPUT:" if debug
100
- puts output if debug
101
- end
54
+ def make_backup_root
55
+ # TODO: Handle errors
56
+ FileUtils.mkdir_p(config.backup_root)
102
57
  end
103
58
  end
104
59
  end
@@ -8,9 +8,15 @@ module GithubBackup
8
8
  attr_reader :backup_root, :gitconfig_path, :token
9
9
 
10
10
  def initialize(options = {})
11
- @backup_root = options.fetch(:backup_root) { raise ArgumentError, 'A backup_root option is required' }
12
- @gitconfig_path = options.fetch(:gitconfig_path) { DEFAULTS[:gitconfig_path] }
13
- @token = options.fetch(:token) { default_token }
11
+ @backup_root = options.fetch(:backup_root, nil) || Dir.pwd
12
+ @gitconfig_path = options.fetch(:gitconfig_path, nil) || DEFAULTS[:gitconfig_path]
13
+ @token = options.fetch(:token) { default_token }
14
+ end
15
+
16
+ def ==(other)
17
+ backup_root == other.backup_root &&
18
+ gitconfig_path == other.gitconfig_path &&
19
+ token == other.token
14
20
  end
15
21
 
16
22
  private
@@ -26,7 +32,7 @@ module GithubBackup
26
32
  config = {}
27
33
  group = nil
28
34
 
29
- return config unless File.exists?(gitconfig_path)
35
+ return config unless File.exist?(gitconfig_path)
30
36
 
31
37
  File.foreach(gitconfig_path) do |line|
32
38
  line.strip!
@@ -0,0 +1,16 @@
1
+ require_relative './repository'
2
+ module GithubBackup
3
+ class Gist < Repository
4
+ def clone_url
5
+ sawyer_resource.git_pull_url
6
+ end
7
+
8
+ def backup_path
9
+ if sawyer_resource.owner
10
+ "#{ sawyer_resource.owner.login }/#{ sawyer_resource.id }.git"
11
+ else
12
+ "anonymous/#{ sawyer_resource.id }.git"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,88 @@
1
+ module GithubBackup
2
+ class GithubRepositoryCollection
3
+ attr_reader :client
4
+
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ def repos(username)
10
+ first_page =
11
+ if username_is_authenticated_user?(username)
12
+ client.repos
13
+ elsif username_is_organisation?(username)
14
+ client.org_repos(username)
15
+ else
16
+ client.repos(username)
17
+ end
18
+
19
+ all(first_page).map { |r| GithubBackup::Repository.new(r) }
20
+ end
21
+
22
+ def gists(username)
23
+ first_page =
24
+ if username_is_authenticated_user?(username)
25
+ client.gists
26
+ else
27
+ client.gists(username)
28
+ end
29
+
30
+ all(first_page).map { |r| GithubBackup::Gist.new(r) }
31
+ end
32
+
33
+ def starred_gists(username)
34
+ first_page =
35
+ if username_is_authenticated_user?(username)
36
+ client.starred_gists
37
+ else
38
+ [] # Can only list authenticated user's gists at the moment
39
+ end
40
+
41
+ return first_page if first_page.empty?
42
+ all(first_page).map { |r| GithubBackup::Gist.new(r) }
43
+ end
44
+
45
+ def wikis(username)
46
+ first_page =
47
+ if username_is_authenticated_user?(username)
48
+ client.repos
49
+ elsif username_is_organisation?(username)
50
+ client.org_repos(username)
51
+ else
52
+ client.repos(username)
53
+ end
54
+
55
+ all(first_page).
56
+ select(&:has_wiki?).
57
+ map { |r| GithubBackup::Wiki.new(r) }
58
+ end
59
+
60
+ private
61
+
62
+ def all(first_page)
63
+ repos = []
64
+ repos.concat(first_page)
65
+
66
+ # Iterate over paginated response
67
+ last_response = client.last_response
68
+ unless last_response.rels[:next].nil?
69
+ loop do
70
+ last_response = last_response.rels[:next].get
71
+ repos.concat(last_response.data)
72
+ break if last_response.rels[:next].nil?
73
+ end
74
+ end
75
+
76
+ repos
77
+ end
78
+
79
+ def username_is_organisation?(username)
80
+ client.user(username).type == 'Organization'
81
+ end
82
+
83
+ def username_is_authenticated_user?(username)
84
+ return false unless client.token_authenticated?
85
+ username == client.user.login
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,42 @@
1
+ module GithubBackup
2
+ class Repository
3
+ attr_reader :sawyer_resource, :shell
4
+
5
+ def initialize(sawyer_resource, opts = {})
6
+ @sawyer_resource = sawyer_resource
7
+ @shell = opts[:shell] || Shell.new
8
+ end
9
+
10
+ def backup(backup_root)
11
+ full_backup_path = File.join(backup_root, backup_path)
12
+
13
+ puts "Backing up #{ full_backup_path }"
14
+
15
+ if File.exist?(full_backup_path)
16
+ backup_repository_incremental(full_backup_path)
17
+ else
18
+ backup_repository_initial(full_backup_path)
19
+ end
20
+ end
21
+
22
+ def clone_url
23
+ sawyer_resource.ssh_url
24
+ end
25
+
26
+ def backup_path
27
+ "#{ sawyer_resource.full_name }.git"
28
+ end
29
+
30
+ private
31
+
32
+ def backup_repository_initial(path)
33
+ shell.run("git clone --mirror -n #{ clone_url } #{ path }")
34
+ end
35
+
36
+ def backup_repository_incremental(path)
37
+ FileUtils.cd(path) do
38
+ shell.run('git remote update')
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ module GithubBackup
2
+ class Shell
3
+ attr_reader :debug
4
+
5
+ def initialize(opts = {})
6
+ @debug = opts[:debug] || false
7
+ end
8
+
9
+ def run(command)
10
+ puts "EXECUTING: #{command}" if debug
11
+ IO.popen(command, 'r') do |io|
12
+ output = io.read
13
+ puts "OUTPUT:" if debug
14
+ puts output if debug
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module GithubBackup
2
- VERSION = "0.10.0"
2
+ VERSION = "0.11.0"
3
3
  end
@@ -0,0 +1,12 @@
1
+ require_relative './repository'
2
+ module GithubBackup
3
+ class Wiki < Repository
4
+ def clone_url
5
+ "https://github.com/#{ sawyer_resource.full_name }.wiki.git"
6
+ end
7
+
8
+ def backup_path
9
+ "#{ sawyer_resource.full_name }.wiki.git"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,2 @@
1
+ [github]
2
+ token=SUPER_S3CR3T
@@ -0,0 +1,84 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe GithubBackup::Backup do
4
+
5
+ describe :new do
6
+
7
+ # username
8
+ it 'requires a username' do
9
+ proc { GithubBackup::Backup.new }.
10
+ must_raise ArgumentError
11
+ end
12
+
13
+ it 'initializes an Octokit client' do
14
+ backup = GithubBackup::Backup.new('ddollar', :backup_root => '/tmp')
15
+ backup.client.must_be_instance_of ::Octokit::Client
16
+ end
17
+
18
+ it 'configures the backup' do
19
+ opts = { :backup_root => '/tmp', :token => 'S3CR3T' }
20
+ config = GithubBackup::Config.new(opts)
21
+ backup = GithubBackup::Backup.new('ddollar', opts)
22
+ backup.config.must_equal config
23
+ end
24
+
25
+ # :token
26
+ describe 'with a token' do
27
+
28
+ it 'will configure the client with an OAuth access token' do
29
+ opts = { :backup_root => '/tmp', :token => 'S3CR3T' }
30
+ backup = GithubBackup::Backup.new('ddollar', opts)
31
+ backup.client.access_token.must_equal opts[:token]
32
+ end
33
+
34
+ end
35
+
36
+ describe 'without a token' do
37
+
38
+ it 'will not configure the client with an OAuth access token' do
39
+ opts = { :backup_root => '/tmp' }
40
+ backup = GithubBackup::Backup.new('ddollar', opts)
41
+ backup.client.access_token.must_be_nil
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+ describe :username do
49
+
50
+ it 'returns the username of the GitHub account to back up' do
51
+ backup = GithubBackup::Backup.new('ddollar', :backup_root => '/tmp')
52
+ backup.username.must_equal 'ddollar'
53
+ end
54
+
55
+ end
56
+
57
+ describe :client do
58
+
59
+ it 'returns the Octokit client' do
60
+ backup = GithubBackup::Backup.new('ddollar', :backup_root => '/tmp')
61
+ backup.client.must_be_instance_of ::Octokit::Client
62
+ end
63
+
64
+ end
65
+
66
+ describe :config do
67
+
68
+ it 'returns the backup configuration' do
69
+ backup = GithubBackup::Backup.new('ddollar', :backup_root => '/tmp')
70
+ backup.config.must_be_instance_of GithubBackup::Config
71
+ end
72
+
73
+ end
74
+
75
+ describe :debug do
76
+
77
+ it 'returns false' do
78
+ backup = GithubBackup::Backup.new('ddollar', :backup_root => '/tmp')
79
+ backup.debug.must_equal false
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,115 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe GithubBackup::Config do
4
+
5
+ describe :new do
6
+
7
+ # :backup_root
8
+ it 'sets the default backup_root to the current directory' do
9
+ config = GithubBackup::Config.new
10
+ config.backup_root.must_equal Dir.pwd
11
+ end
12
+
13
+ it 'sets the default backup_root if the option is nil' do
14
+ config = GithubBackup::Config.new(:backup_root => nil)
15
+ config.backup_root.must_equal Dir.pwd
16
+ end
17
+
18
+ it 'sets the backup_root through an option' do
19
+ config = GithubBackup::Config.new(:backup_root => '/tmp')
20
+ config.backup_root.must_equal '/tmp'
21
+ end
22
+
23
+ # :gitconfig_path
24
+ it 'sets the default .gitconfig file path' do
25
+ config = GithubBackup::Config.new(:backup_root => '/tmp')
26
+ config.gitconfig_path.must_equal "#{ ENV['HOME'] }/.gitconfig"
27
+ end
28
+
29
+ it 'sets the default gitconfig_path if the option is nil' do
30
+ config = GithubBackup::Config.new(:gitconfig_path => nil)
31
+ config.gitconfig_path.must_equal "#{ ENV['HOME'] }/.gitconfig"
32
+ end
33
+
34
+ it 'sets a .gitconfig path through an option' do
35
+ config = GithubBackup::Config.new(:backup_root => '/tmp',
36
+ :gitconfig_path => '/tmp/.gitconfig')
37
+ config.gitconfig_path.must_equal '/tmp/.gitconfig'
38
+ end
39
+
40
+ # :token
41
+ it 'sets a default value of nil for the GitHub OAuth token option' do
42
+ config = GithubBackup::Config.new(:backup_root => '/tmp')
43
+ config.token.must_be_nil
44
+ end
45
+
46
+ it 'sets the GitHub OAuth token from an option key' do
47
+ config = GithubBackup::Config.new(:backup_root => '/tmp',
48
+ :token => 'S3CR3T')
49
+ config.token.must_equal 'S3CR3T'
50
+ end
51
+
52
+ it 'sets the GitHub OAuth token from .gitconfig' do
53
+ gitconfig_path = File.expand_path('../../../fixtures/gitconfig', __FILE__)
54
+ config = GithubBackup::Config.new(:backup_root => '/tmp',
55
+ :gitconfig_path => gitconfig_path)
56
+ config.token.must_equal 'SUPER_S3CR3T'
57
+ end
58
+
59
+ describe 'attributes' do
60
+
61
+ let(:config) do
62
+ GithubBackup::Config.new(:backup_root => '/tmp/backup',
63
+ :gitconfig_path => '/tmp/gitconfig',
64
+ :token => 'S3CR3T')
65
+ end
66
+
67
+ describe :backup_root do
68
+
69
+ it 'returns the directory where the repositories will be backed up' do
70
+ config.backup_root.must_equal '/tmp/backup'
71
+ end
72
+
73
+ end
74
+
75
+ describe :gitconfig_path do
76
+
77
+ it 'returns the path to a gitconfig file' do
78
+ config.gitconfig_path.must_equal '/tmp/gitconfig'
79
+ end
80
+
81
+ end
82
+
83
+ describe :token do
84
+
85
+ it 'returns the GitHub OAuth access token' do
86
+ config.token.must_equal 'S3CR3T'
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+
93
+ describe :== do
94
+
95
+ it 'is equal if its attributes are identical' do
96
+ opts = { :backup_root => '/tmp/backup',
97
+ :gitconfig_path => '/tmp/gitconfig',
98
+ :token => 'S3CR3T'}
99
+ backup = GithubBackup::Config.new(opts)
100
+ backup.dup.must_equal backup
101
+ end
102
+
103
+ it 'is not equal if any of the attributes vary' do
104
+ opts = { :backup_root => '/tmp/backup',
105
+ :gitconfig_path => '/tmp/gitconfig',
106
+ :token => 'S3CR3T'}
107
+ backup1 = GithubBackup::Config.new(opts)
108
+ backup2 = GithubBackup::Config.new(opts.merge(:token => 'PASSWORD'))
109
+ backup1.wont_equal backup2
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,41 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe GithubBackup::Gist do
4
+
5
+ it 'acts lie a Repository' do
6
+ GithubBackup::Gist.new(sawyer_anonymous_gist).
7
+ must_be_kind_of GithubBackup::Repository
8
+ end
9
+
10
+ describe '#clone_url' do
11
+
12
+ it 'returns the repository clone URL' do
13
+ repo = GithubBackup::Gist.new(sawyer_anonymous_gist)
14
+ repo.clone_url.must_equal 'https://gist.github.com/380919418d982afbc4fc.git'
15
+ end
16
+
17
+ end
18
+
19
+ describe '#backup_path' do
20
+
21
+ describe 'an anonymous gist' do
22
+
23
+ it 'returns the repository backup path' do
24
+ repo = GithubBackup::Gist.new(sawyer_anonymous_gist)
25
+ repo.backup_path.must_equal 'anonymous/380919418d982afbc4fc.git'
26
+ end
27
+
28
+ end
29
+
30
+ describe 'a public gist' do
31
+
32
+ it 'returns the repository backup path' do
33
+ repo = GithubBackup::Gist.new(sawyer_public_gist)
34
+ repo.backup_path.must_equal 'garethrees/dae4757f11749b6edaf2.git'
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,85 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe GithubBackup::Repository do
4
+
5
+ describe '.new' do
6
+
7
+ it 'accepts a sawyer::Resource' do
8
+ repo = GithubBackup::Repository.new(sawyer_repo)
9
+ repo.sawyer_resource.must_equal sawyer_repo
10
+ end
11
+
12
+ it 'creates a default GithubBackup::Shell instance' do
13
+ repo = GithubBackup::Repository.new(sawyer_repo)
14
+ repo.shell.must_be_instance_of GithubBackup::Shell
15
+ end
16
+
17
+ end
18
+
19
+ describe '#shell' do
20
+
21
+ it 'returns the shell instance' do
22
+ shell = GithubBackup::Shell.new
23
+ repo = GithubBackup::Repository.new(sawyer_repo, shell: shell)
24
+ repo.shell.must_be_same_as shell
25
+ end
26
+
27
+ end
28
+
29
+ describe '#backup' do
30
+
31
+ it 'clones the repository if it has not yet been backed up' do
32
+ cmd = 'git clone --mirror -n ' \
33
+ 'git@github.com:ddollar/github-backup.git ' \
34
+ '/ddollar/github-backup.git'
35
+ shell = Minitest::Mock.new
36
+ shell.expect(:run, true, [cmd])
37
+
38
+ FakeFS do
39
+ repo = GithubBackup::Repository.new(sawyer_repo, shell: shell)
40
+ repo.backup(Dir.pwd)
41
+ end
42
+
43
+ FakeFS::FileSystem.clear
44
+
45
+ shell.verify
46
+ end
47
+
48
+ # TODO: This doesn't check that we change in to the backup_path before
49
+ # executing `git remote update`
50
+ it 'updates the repository if it has already been backed up' do
51
+ shell = Minitest::Mock.new
52
+ shell.expect(:run, true, ['git remote update'])
53
+
54
+ FakeFS do
55
+ repo = GithubBackup::Repository.new(sawyer_repo, shell: shell)
56
+ FileUtils.mkdir_p(File.join(Dir.pwd, repo.backup_path))
57
+ repo.backup(Dir.pwd)
58
+ end
59
+
60
+ FakeFS::FileSystem.clear
61
+
62
+ shell.verify
63
+ end
64
+
65
+ end
66
+
67
+ describe '#clone_url' do
68
+
69
+ it 'returns the repository clone URL' do
70
+ repo = GithubBackup::Repository.new(sawyer_repo)
71
+ repo.clone_url.must_equal 'git@github.com:ddollar/github-backup.git'
72
+ end
73
+
74
+ end
75
+
76
+ describe '#backup_path' do
77
+
78
+ it 'returns the repository backup path' do
79
+ repo = GithubBackup::Repository.new(sawyer_repo)
80
+ repo.backup_path.must_equal 'ddollar/github-backup.git'
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,28 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe GithubBackup::Wiki do
4
+
5
+ it 'acts lie a Repository' do
6
+ GithubBackup::Wiki.new(sawyer_repo).
7
+ must_be_kind_of GithubBackup::Repository
8
+ end
9
+
10
+ describe '#clone_url' do
11
+
12
+ it 'returns the repository clone URL' do
13
+ repo = GithubBackup::Wiki.new(sawyer_repo)
14
+ repo.clone_url.must_equal 'https://github.com/ddollar/github-backup.wiki.git'
15
+ end
16
+
17
+ end
18
+
19
+ describe '#backup_path' do
20
+
21
+ it 'returns the repository backup path' do
22
+ repo = GithubBackup::Wiki.new(sawyer_repo)
23
+ repo.backup_path.must_equal 'ddollar/github-backup.wiki.git'
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,267 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'github-backup'
4
+ require 'minitest/autorun'
5
+ require 'fakefs/safe'
6
+ require File.join(File.dirname(__FILE__), 'support', 'deep_struct')
7
+
8
+ def sawyer_repo
9
+ DeepStruct.new(
10
+ {:id=>251112,
11
+ :name=>"github-backup",
12
+ :full_name=>"ddollar/github-backup",
13
+ :owner=>
14
+ {:login=>"ddollar",
15
+ :id=>3308,
16
+ :avatar_url=>"https://avatars.githubusercontent.com/u/3308?v=3",
17
+ :gravatar_id=>"",
18
+ :url=>"https://api.github.com/users/ddollar",
19
+ :html_url=>"https://github.com/ddollar",
20
+ :followers_url=>"https://api.github.com/users/ddollar/followers",
21
+ :following_url=>
22
+ "https://api.github.com/users/ddollar/following{/other_user}",
23
+ :gists_url=>"https://api.github.com/users/ddollar/gists{/gist_id}",
24
+ :starred_url=>"https://api.github.com/users/ddollar/starred{/owner}{/repo}",
25
+ :subscriptions_url=>"https://api.github.com/users/ddollar/subscriptions",
26
+ :organizations_url=>"https://api.github.com/users/ddollar/orgs",
27
+ :repos_url=>"https://api.github.com/users/ddollar/repos",
28
+ :events_url=>"https://api.github.com/users/ddollar/events{/privacy}",
29
+ :received_events_url=>
30
+ "https://api.github.com/users/ddollar/received_events",
31
+ :type=>"User",
32
+ :site_admin=>false},
33
+ :private=>false,
34
+ :html_url=>"https://github.com/ddollar/github-backup",
35
+ :description=>"Back up your Github repositories",
36
+ :fork=>false,
37
+ :url=>"https://api.github.com/repos/ddollar/github-backup",
38
+ :forks_url=>"https://api.github.com/repos/ddollar/github-backup/forks",
39
+ :keys_url=>"https://api.github.com/repos/ddollar/github-backup/keys{/key_id}",
40
+ :collaborators_url=>
41
+ "https://api.github.com/repos/ddollar/github-backup/collaborators{/collaborator}",
42
+ :teams_url=>"https://api.github.com/repos/ddollar/github-backup/teams",
43
+ :hooks_url=>"https://api.github.com/repos/ddollar/github-backup/hooks",
44
+ :issue_events_url=>
45
+ "https://api.github.com/repos/ddollar/github-backup/issues/events{/number}",
46
+ :events_url=>"https://api.github.com/repos/ddollar/github-backup/events",
47
+ :assignees_url=>
48
+ "https://api.github.com/repos/ddollar/github-backup/assignees{/user}",
49
+ :branches_url=>
50
+ "https://api.github.com/repos/ddollar/github-backup/branches{/branch}",
51
+ :tags_url=>"https://api.github.com/repos/ddollar/github-backup/tags",
52
+ :blobs_url=>
53
+ "https://api.github.com/repos/ddollar/github-backup/git/blobs{/sha}",
54
+ :git_tags_url=>
55
+ "https://api.github.com/repos/ddollar/github-backup/git/tags{/sha}",
56
+ :git_refs_url=>
57
+ "https://api.github.com/repos/ddollar/github-backup/git/refs{/sha}",
58
+ :trees_url=>
59
+ "https://api.github.com/repos/ddollar/github-backup/git/trees{/sha}",
60
+ :statuses_url=>
61
+ "https://api.github.com/repos/ddollar/github-backup/statuses/{sha}",
62
+ :languages_url=>
63
+ "https://api.github.com/repos/ddollar/github-backup/languages",
64
+ :stargazers_url=>
65
+ "https://api.github.com/repos/ddollar/github-backup/stargazers",
66
+ :contributors_url=>
67
+ "https://api.github.com/repos/ddollar/github-backup/contributors",
68
+ :subscribers_url=>
69
+ "https://api.github.com/repos/ddollar/github-backup/subscribers",
70
+ :subscription_url=>
71
+ "https://api.github.com/repos/ddollar/github-backup/subscription",
72
+ :commits_url=>
73
+ "https://api.github.com/repos/ddollar/github-backup/commits{/sha}",
74
+ :git_commits_url=>
75
+ "https://api.github.com/repos/ddollar/github-backup/git/commits{/sha}",
76
+ :comments_url=>
77
+ "https://api.github.com/repos/ddollar/github-backup/comments{/number}",
78
+ :issue_comment_url=>
79
+ "https://api.github.com/repos/ddollar/github-backup/issues/comments{/number}",
80
+ :contents_url=>
81
+ "https://api.github.com/repos/ddollar/github-backup/contents/{+path}",
82
+ :compare_url=>
83
+ "https://api.github.com/repos/ddollar/github-backup/compare/{base}...{head}",
84
+ :merges_url=>"https://api.github.com/repos/ddollar/github-backup/merges",
85
+ :archive_url=>
86
+ "https://api.github.com/repos/ddollar/github-backup/{archive_format}{/ref}",
87
+ :downloads_url=>
88
+ "https://api.github.com/repos/ddollar/github-backup/downloads",
89
+ :issues_url=>
90
+ "https://api.github.com/repos/ddollar/github-backup/issues{/number}",
91
+ :pulls_url=>
92
+ "https://api.github.com/repos/ddollar/github-backup/pulls{/number}",
93
+ :milestones_url=>
94
+ "https://api.github.com/repos/ddollar/github-backup/milestones{/number}",
95
+ :notifications_url=>
96
+ "https://api.github.com/repos/ddollar/github-backup/notifications{?since,all,participating}",
97
+ :labels_url=>
98
+ "https://api.github.com/repos/ddollar/github-backup/labels{/name}",
99
+ :releases_url=>
100
+ "https://api.github.com/repos/ddollar/github-backup/releases{/id}",
101
+ :created_at=>"2009-07-14 17:52:13 UTC",
102
+ :updated_at=>"2015-08-27 14:54:56 UTC",
103
+ :pushed_at=>"2015-06-13 15:58:00 UTC",
104
+ :git_url=>"git://github.com/ddollar/github-backup.git",
105
+ :ssh_url=>"git@github.com:ddollar/github-backup.git",
106
+ :clone_url=>"https://github.com/ddollar/github-backup.git",
107
+ :svn_url=>"https://github.com/ddollar/github-backup",
108
+ :homepage=>"",
109
+ :size=>306,
110
+ :stargazers_count=>66,
111
+ :watchers_count=>66,
112
+ :language=>"Ruby",
113
+ :has_issues=>true,
114
+ :has_downloads=>true,
115
+ :has_wiki=>true,
116
+ :has_pages=>false,
117
+ :forks_count=>18,
118
+ :mirror_url=>nil,
119
+ :open_issues_count=>0,
120
+ :forks=>18,
121
+ :open_issues=>0,
122
+ :watchers=>66,
123
+ :default_branch=>"master",
124
+ :permissions=>{:admin=>false, :push=>true, :pull=>true},
125
+ :network_count=>18,
126
+ :subscribers_count=>9})
127
+ end
128
+
129
+ def sawyer_anonymous_gist
130
+ DeepStruct.new(
131
+ {:url=>"https://api.github.com/gists/380919418d982afbc4fc",
132
+ :forks_url=>"https://api.github.com/gists/380919418d982afbc4fc/forks",
133
+ :commits_url=>"https://api.github.com/gists/380919418d982afbc4fc/commits",
134
+ :id=>"380919418d982afbc4fc",
135
+ :git_pull_url=>"https://gist.github.com/380919418d982afbc4fc.git",
136
+ :git_push_url=>"https://gist.github.com/380919418d982afbc4fc.git",
137
+ :html_url=>"https://gist.github.com/380919418d982afbc4fc",
138
+ :files=>
139
+ {:"anon.txt"=>
140
+ {:filename=>"anon.txt",
141
+ :type=>"text/plain",
142
+ :language=>"Text",
143
+ :raw_url=>
144
+ "https://gist.githubusercontent.com/anonymous/380919418d982afbc4fc/raw/6991197cadbb876aabebdaa9ead52962857f0443/anon.txt",
145
+ :size=>19,
146
+ :truncated=>false,
147
+ :content=>"Nothing to see here"}},
148
+ :public=>true,
149
+ :created_at=>'2015-09-07 22:23:58 UTC',
150
+ :updated_at=>'2015-09-07 22:23:58 UTC',
151
+ :description=>"Anonymous Gist",
152
+ :comments=>0,
153
+ :user=>nil,
154
+ :comments_url=>"https://api.github.com/gists/380919418d982afbc4fc/comments",
155
+ :forks=>[],
156
+ :history=>
157
+ [{:user=>
158
+ {:login=>"invalid-email-address",
159
+ :id=>148100,
160
+ :avatar_url=>"https://avatars.githubusercontent.com/u/148100?v=3",
161
+ :gravatar_id=>"",
162
+ :url=>"https://api.github.com/users/invalid-email-address",
163
+ :html_url=>"https://github.com/invalid-email-address",
164
+ :followers_url=>
165
+ "https://api.github.com/users/invalid-email-address/followers",
166
+ :following_url=>
167
+ "https://api.github.com/users/invalid-email-address/following{/other_user}",
168
+ :gists_url=>
169
+ "https://api.github.com/users/invalid-email-address/gists{/gist_id}",
170
+ :starred_url=>
171
+ "https://api.github.com/users/invalid-email-address/starred{/owner}{/repo}",
172
+ :subscriptions_url=>
173
+ "https://api.github.com/users/invalid-email-address/subscriptions",
174
+ :organizations_url=>
175
+ "https://api.github.com/users/invalid-email-address/orgs",
176
+ :repos_url=>"https://api.github.com/users/invalid-email-address/repos",
177
+ :events_url=>
178
+ "https://api.github.com/users/invalid-email-address/events{/privacy}",
179
+ :received_events_url=>
180
+ "https://api.github.com/users/invalid-email-address/received_events",
181
+ :type=>"User",
182
+ :site_admin=>false},
183
+ :version=>"496b2ba699998442fbf864e9e7dcf47165b2f478",
184
+ :committed_at=>'2015-09-07 22:23:58 UTC',
185
+ :change_status=>{:total=>1, :additions=>1, :deletions=>0},
186
+ :url=>
187
+ "https://api.github.com/gists/380919418d982afbc4fc/496b2ba699998442fbf864e9e7dcf47165b2f478"}]}
188
+ )
189
+ end
190
+
191
+ def sawyer_public_gist
192
+ DeepStruct.new(
193
+ {:url=>"https://api.github.com/gists/dae4757f11749b6edaf2",
194
+ :forks_url=>"https://api.github.com/gists/dae4757f11749b6edaf2/forks",
195
+ :commits_url=>"https://api.github.com/gists/dae4757f11749b6edaf2/commits",
196
+ :id=>"dae4757f11749b6edaf2",
197
+ :git_pull_url=>"https://gist.github.com/dae4757f11749b6edaf2.git",
198
+ :git_push_url=>"https://gist.github.com/dae4757f11749b6edaf2.git",
199
+ :html_url=>"https://gist.github.com/dae4757f11749b6edaf2",
200
+ :files=>
201
+ {:"test.txt"=>
202
+ {:filename=>"test.txt",
203
+ :type=>"text/plain",
204
+ :language=>"Text",
205
+ :raw_url=>
206
+ "https://gist.githubusercontent.com/garethrees/dae4757f11749b6edaf2/raw/3fde4e20239e009fb18787483735352a778229da/test.txt",
207
+ :size=>5,
208
+ :truncated=>false,
209
+ :content=>"1,2,3"}},
210
+ :public=>true,
211
+ :created_at=>'2015-09-07 22:34:59 UTC',
212
+ :updated_at=>'2015-09-07 22:34:59 UTC',
213
+ :description=>"Test Gist",
214
+ :comments=>0,
215
+ :user=>nil,
216
+ :comments_url=>"https://api.github.com/gists/dae4757f11749b6edaf2/comments",
217
+ :owner=>
218
+ {:login=>"garethrees",
219
+ :id=>282788,
220
+ :avatar_url=>"https://avatars.githubusercontent.com/u/282788?v=3",
221
+ :gravatar_id=>"",
222
+ :url=>"https://api.github.com/users/garethrees",
223
+ :html_url=>"https://github.com/garethrees",
224
+ :followers_url=>"https://api.github.com/users/garethrees/followers",
225
+ :following_url=>
226
+ "https://api.github.com/users/garethrees/following{/other_user}",
227
+ :gists_url=>"https://api.github.com/users/garethrees/gists{/gist_id}",
228
+ :starred_url=>
229
+ "https://api.github.com/users/garethrees/starred{/owner}{/repo}",
230
+ :subscriptions_url=>"https://api.github.com/users/garethrees/subscriptions",
231
+ :organizations_url=>"https://api.github.com/users/garethrees/orgs",
232
+ :repos_url=>"https://api.github.com/users/garethrees/repos",
233
+ :events_url=>"https://api.github.com/users/garethrees/events{/privacy}",
234
+ :received_events_url=>
235
+ "https://api.github.com/users/garethrees/received_events",
236
+ :type=>"User",
237
+ :site_admin=>false},
238
+ :forks=>[],
239
+ :history=>
240
+ [{:user=>
241
+ {:login=>"garethrees",
242
+ :id=>282788,
243
+ :avatar_url=>"https://avatars.githubusercontent.com/u/282788?v=3",
244
+ :gravatar_id=>"",
245
+ :url=>"https://api.github.com/users/garethrees",
246
+ :html_url=>"https://github.com/garethrees",
247
+ :followers_url=>"https://api.github.com/users/garethrees/followers",
248
+ :following_url=>
249
+ "https://api.github.com/users/garethrees/following{/other_user}",
250
+ :gists_url=>"https://api.github.com/users/garethrees/gists{/gist_id}",
251
+ :starred_url=>
252
+ "https://api.github.com/users/garethrees/starred{/owner}{/repo}",
253
+ :subscriptions_url=>
254
+ "https://api.github.com/users/garethrees/subscriptions",
255
+ :organizations_url=>"https://api.github.com/users/garethrees/orgs",
256
+ :repos_url=>"https://api.github.com/users/garethrees/repos",
257
+ :events_url=>"https://api.github.com/users/garethrees/events{/privacy}",
258
+ :received_events_url=>
259
+ "https://api.github.com/users/garethrees/received_events",
260
+ :type=>"User",
261
+ :site_admin=>false},
262
+ :version=>"99e263bff14f9565b844f7913b6b505513308a5f",
263
+ :committed_at=>'2015-09-07 22:34:59 UTC',
264
+ :change_status=>{:total=>1, :additions=>1, :deletions=>0},
265
+ :url=>
266
+ "https://api.github.com/gists/dae4757f11749b6edaf2/99e263bff14f9565b844f7913b6b505513308a5f"}]})
267
+ end
@@ -0,0 +1,22 @@
1
+ require 'ostruct'
2
+
3
+ class DeepStruct < OpenStruct
4
+ def initialize(hash=nil)
5
+ @table = {}
6
+ @hash_table = {}
7
+
8
+ if hash
9
+ hash.each do |k,v|
10
+ @table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
11
+ @hash_table[k.to_sym] = v
12
+
13
+ new_ostruct_member(k)
14
+ end
15
+ end
16
+ end
17
+
18
+ def to_h
19
+ @hash_table
20
+ end
21
+
22
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: github-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Dollar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-04 00:00:00.000000000 Z
11
+ date: 2018-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: octokit
@@ -24,6 +24,48 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: fakefs
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.6.7
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.6.7
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 5.5.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 5.5.0
27
69
  description: Library and command line tool for backing up GitHub repositories
28
70
  email: david@dollar.io
29
71
  executables:
@@ -33,17 +75,32 @@ extra_rdoc_files: []
33
75
  files:
34
76
  - ".document"
35
77
  - ".gitignore"
78
+ - ".travis.yml"
36
79
  - Gemfile
37
80
  - Gemfile.lock
38
81
  - LICENSE
39
82
  - Makefile
40
83
  - README.md
84
+ - Rakefile
41
85
  - bin/github-backup
42
86
  - github-backup.gemspec
43
87
  - lib/github-backup.rb
44
88
  - lib/github-backup/backup.rb
45
89
  - lib/github-backup/config.rb
90
+ - lib/github-backup/gist.rb
91
+ - lib/github-backup/github_repository_collection.rb
92
+ - lib/github-backup/repository.rb
93
+ - lib/github-backup/shell.rb
46
94
  - lib/github-backup/version.rb
95
+ - lib/github-backup/wiki.rb
96
+ - spec/fixtures/gitconfig
97
+ - spec/lib/github-backup/backup_spec.rb
98
+ - spec/lib/github-backup/config_spec.rb
99
+ - spec/lib/github-backup/gist_spec.rb
100
+ - spec/lib/github-backup/repository_spec.rb
101
+ - spec/lib/github-backup/wiki_spec.rb
102
+ - spec/spec_helper.rb
103
+ - spec/support/deep_struct.rb
47
104
  homepage: http://github.com/ddollar/github-backup
48
105
  licenses:
49
106
  - MIT
@@ -64,9 +121,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
121
  version: '0'
65
122
  requirements: []
66
123
  rubyforge_project:
67
- rubygems_version: 2.2.2
124
+ rubygems_version: 2.7.3
68
125
  signing_key:
69
126
  specification_version: 4
70
127
  summary: Backup your Github repositories
71
128
  test_files: []
72
- has_rdoc: