github_backup 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9acdffdd4f2ecf5f4f0aba30a1f71dcf84c7304e6d6c28d4a1968975c97655f8
4
+ data.tar.gz: db0e9a35201bc78717193ad5983a2ad91b72cc55a360c40fffd75ad679dd4a21
5
+ SHA512:
6
+ metadata.gz: 1f49b1f08d8a9e43d58426bbe5ff5e0966f49c4be61cbaec58295cdc10b317a44d9663134a54b59783aceefafbf8b2f02522b6f1293a0a46f44c8b21209cff14
7
+ data.tar.gz: fedbb55ac7c2f06944c570087777a9d3b646038b954d1036febb45f568075459663b610adc65ee630fd2633a588edc4482a4c9f1503e3d64a898648a815f7d79
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ github_backup
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.6.2
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 github_backup.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,55 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ github_backup (0.1.0)
5
+ http
6
+ link_header
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ addressable (2.6.0)
12
+ public_suffix (>= 2.0.2, < 4.0)
13
+ diff-lcs (1.3)
14
+ domain_name (0.5.20180417)
15
+ unf (>= 0.0.5, < 1.0.0)
16
+ http (4.1.1)
17
+ addressable (~> 2.3)
18
+ http-cookie (~> 1.0)
19
+ http-form_data (~> 2.0)
20
+ http_parser.rb (~> 0.6.0)
21
+ http-cookie (1.0.3)
22
+ domain_name (~> 0.5)
23
+ http-form_data (2.1.1)
24
+ http_parser.rb (0.6.0)
25
+ link_header (0.0.8)
26
+ public_suffix (3.0.3)
27
+ rake (10.5.0)
28
+ rspec (3.8.0)
29
+ rspec-core (~> 3.8.0)
30
+ rspec-expectations (~> 3.8.0)
31
+ rspec-mocks (~> 3.8.0)
32
+ rspec-core (3.8.0)
33
+ rspec-support (~> 3.8.0)
34
+ rspec-expectations (3.8.2)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.8.0)
37
+ rspec-mocks (3.8.0)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.8.0)
40
+ rspec-support (3.8.0)
41
+ unf (0.1.4)
42
+ unf_ext
43
+ unf_ext (0.0.7.5)
44
+
45
+ PLATFORMS
46
+ ruby
47
+
48
+ DEPENDENCIES
49
+ bundler (~> 1.17)
50
+ github_backup!
51
+ rake (~> 10.0)
52
+ rspec (~> 3.0)
53
+
54
+ BUNDLED WITH
55
+ 1.17.2
data/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # GithubBackup
2
+
3
+ Backup all of your github repos by using the Github API to clone and update them.
4
+
5
+ ## Installation
6
+
7
+ $ gem install github_backup
8
+
9
+ ## Usage
10
+
11
+ Edit: `~/.config/github-backup.json`
12
+
13
+ Example config
14
+
15
+ ```json
16
+ {
17
+ "repos_path": "~/Repos",
18
+ "backup_path": "~/Documents/RepoBundles",
19
+ "token": "...github_token..."
20
+ }
21
+ ```
22
+
23
+ $ github-backup
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/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "github_backup"
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/github-backup ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.join(__dir__, '..', 'lib')
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'shellwords'
7
+ require 'thread'
8
+ require 'github_backup'
9
+ require 'github_backup/git'
10
+ include GithubBackup::Helpers
11
+
12
+ config = GithubBackup::Config.new(File.join(__dir__, '..', 'default_config.json'))
13
+
14
+ config['repos_path'] ||= File.join(Dir.home, 'Repos')
15
+ config['backup_path'] ||= File.join(Dir.home, 'Documents', 'RepoBundles')
16
+
17
+ config.save
18
+ github = GithubBackup::Api.new(config['token'])
19
+
20
+ puts "Fetching repos..."
21
+ page_no = 0
22
+ puts "page #{page_no += 1}"
23
+ repos = github.get('/user/repos')
24
+ while page = github.next
25
+ puts "page #{page_no += 1}"
26
+ repos.concat page
27
+ end
28
+ puts "Done!"
29
+
30
+ clone_urls = repos.map { |repo| repo['ssh_url'] }
31
+
32
+ @threads = []
33
+
34
+ clone_urls.each do |url|
35
+ user = /github\.com.([^\/]+)/.match(url)[1]
36
+ project_name = File.basename(url.split('/').last, '.git')
37
+ path = File.expand_path(File.join(config['repos_path'], user, project_name))
38
+ FileUtils.mkdir_p(File.dirname(path))
39
+
40
+ @threads << Thread.new do |thread|
41
+ unless File.exist?(path)
42
+ cmd = "git clone #{url} #{Shellwords.shellescape(path)}"
43
+ puts cmd
44
+ puts `#{cmd}`
45
+ else
46
+ puts "Updating existing repo: #{path}"
47
+ git_updater = GithubBackup::Git.new
48
+ Dir.chdir(path)
49
+ git_updater.update
50
+ backup_path = File.expand_path(File.join(config['backup_path'], user))
51
+ FileUtils.mkdir_p(backup_path)
52
+ bundle = "#{project_name}.gitbundle"
53
+ bundle_path = File.join(backup_path, bundle)
54
+ puts `git bundle create #{Shellwords.shellescape(bundle_path)} --all --remotes`
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+ @threads.each(&:join)
@@ -0,0 +1,31 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "github_backup/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "github_backup"
8
+ spec.version = GithubBackup::VERSION
9
+ spec.authors = ["Alex Clink"]
10
+ spec.email = ["code@alexclink.com"]
11
+
12
+ spec.summary = "Create backups of your github repos"
13
+ spec.description = "Automatically clones your github repos and makes git bundles"
14
+ spec.homepage = "http://alexclink.com/gems/github-backup"
15
+
16
+ # Specify which files should be added to the gem when it is released.
17
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.17"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "rspec", "~> 3.0"
28
+
29
+ spec.add_dependency "link_header"
30
+ spec.add_dependency "http"
31
+ end
@@ -0,0 +1,8 @@
1
+ module GithubBackup
2
+ class Error < StandardError; end
3
+ end
4
+
5
+ require "github_backup/version"
6
+ require 'github_backup/api'
7
+ require 'github_backup/config'
8
+ require 'github_backup/helpers'
@@ -0,0 +1,40 @@
1
+ require 'link_header'
2
+ require 'json'
3
+ require 'uri'
4
+ require 'http'
5
+
6
+ module GithubBackup
7
+ class Api
8
+ URL = 'https://api.github.com'
9
+
10
+ def initialize(token, version = 'v3')
11
+ @token = token
12
+ @version = version
13
+ end
14
+
15
+ def accept
16
+ "application/vnd.github.#{@version}+json"
17
+ end
18
+
19
+ def get(url)
20
+ @response = HTTP[
21
+ 'User-Agent' => 'sleepinginsomniac - cloner',
22
+ 'Authorization' => "token #{@token}",
23
+ 'Accept' => accept
24
+ ].get(URI.join(URL, url))
25
+ JSON.parse(@response)
26
+ end
27
+
28
+ def next
29
+ links = LinkHeader.parse(@response.headers['Link']).links
30
+ next_link = links.find do |link|
31
+ Hash[link.attr_pairs]['rel'] == 'next'
32
+ end
33
+ if next_link
34
+ get(next_link.href)
35
+ else
36
+ false
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,88 @@
1
+ require 'json'
2
+ require 'fileutils'
3
+ require 'github_backup/json_store'
4
+
5
+ module GithubBackup
6
+ class Config
7
+ attr_accessor :override
8
+
9
+ def initialize(default_path)
10
+ @override = {}
11
+ @default_path = default_path
12
+ puts "Config: #{path}"
13
+ end
14
+
15
+ def defaults
16
+ if @default_path
17
+ @defaults ||= JSONStore.new(@default_path)
18
+ else
19
+ {}
20
+ end
21
+ end
22
+
23
+ def values
24
+ @values ||= JSONStore.new(path)
25
+ end
26
+
27
+ def data
28
+ @data ||= JSONStore.new(data_path)
29
+ end
30
+
31
+ # Check if the config exists
32
+ def exists?
33
+ values.exists?
34
+ end
35
+
36
+ # The user's home directory
37
+ def home
38
+ ENV['HOME'] || File.expand_path('~')
39
+ end
40
+
41
+ # The user's preferred config dir
42
+ def dir
43
+ ENV['XDG_CONFIG_HOME'] || File.join(home, '.config')
44
+ end
45
+
46
+ def data_dir
47
+ ENV['XDG_DATA_HOME'] || File.join(home, '.local', 'share')
48
+ end
49
+
50
+ def data_path
51
+ File.join(data_dir, "#{APP}.json")
52
+ end
53
+
54
+ # the path to the config file
55
+ def path
56
+ File.join(dir, "#{APP}.json")
57
+ end
58
+
59
+ # Read a value from the config ex:
60
+ # config[key] # => value
61
+ def [](key)
62
+ @override[key] || values[key] || defaults[key]
63
+ end
64
+
65
+ # Set a value in the config ex:
66
+ # config[key] = value
67
+ def []=(key, value)
68
+ values[key] = value
69
+ values.save
70
+ end
71
+
72
+ # Write config to disk at the user's config dir.
73
+ # Doesn't save overrides
74
+ def save
75
+ values.save
76
+ end
77
+
78
+ # Removes the saved config
79
+ def delete!
80
+ values.delete!
81
+ end
82
+
83
+ # writes the config json to stdout
84
+ def to_s
85
+ values.to_s
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,59 @@
1
+ module GithubBackup
2
+ class Git
3
+ def remotes(_remotes = nil)
4
+ unless _remotes
5
+ _remotes = `git remote`.split(/\s+/)
6
+ end
7
+ _remotes
8
+ end
9
+
10
+ def current_branch
11
+ `git rev-parse --abbrev-ref HEAD`.chomp
12
+ end
13
+
14
+ def tracked_branches(remote)
15
+ info = `git remote show #{remote}`
16
+ .lines
17
+ .select { |l| l =~ /merges with remote/i }
18
+ .map{ |l| l.strip.split(/\s+merges with remote\s+/i) }
19
+ end
20
+
21
+ def commits_behind(remote, lbranch, rbranch)
22
+ `git rev-list --count refs/heads/#{lbranch}..refs/remotes/#{remote}/#{rbranch}`.chomp.to_i
23
+ end
24
+
25
+ def commits_ahead(remote, lbranch, rbranch)
26
+ `git rev-list --count refs/remotes/#{remote}/#{rbranch}..refs/heads/#{lbranch}`.chomp.to_i
27
+ end
28
+
29
+ # ===========
30
+ # = updater =
31
+ # ===========
32
+ def update
33
+ remotes.each do |remote|
34
+ puts `git remote update #{remote}`
35
+ tracked_branches(remote).each do |branches|
36
+ lbranch, rbranch = branches
37
+ behind = commits_behind(remote, lbranch, rbranch)
38
+ ahead = commits_ahead(remote, lbranch, rbranch)
39
+ if behind > 0
40
+ if ahead > 0
41
+ puts "branch #{lbranch} is #{behind} commit(s) behind and " \
42
+ "#{ahead} commit(s) ahead of #{remote}/#{rbranch}. " \
43
+ "could not be fast-forwarded"
44
+ elsif lbranch == current_branch
45
+ puts "branch #{lbranch} was #{behind} commit(s) behind of " \
46
+ "#{remote}/#{rbranch}. fast-forward merge"
47
+ puts "git merge -q refs/remotes/#{remote}/#{rbranch}"
48
+ else
49
+ puts "branch #{lbranch} was #{behind} commit(s) behind of " \
50
+ "#{remote}/#{rbranch}. resetting local branch to remote"
51
+ puts "git branch -f #{lbranch} -t refs/remotes/#{remote}/#{rbranch} >/dev/null"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+
3
+ module GithubBackup
4
+ module Helpers
5
+ def inside(dir)
6
+ current = Dir.pwd
7
+ chdir_p dir
8
+ yield
9
+ Dir.chdir(current)
10
+ end
11
+
12
+ def chdir_p(path)
13
+ FileUtils.mkdir_p path
14
+ Dir.chdir path
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,50 @@
1
+ require 'json'
2
+ require 'fileutils'
3
+
4
+ module GithubBackup
5
+ class JSONStore
6
+ def initialize(path)
7
+ @path = path
8
+ FileUtils.mkdir_p(File.dirname(@path))
9
+ end
10
+
11
+ def data
12
+ @data || load!
13
+ end
14
+
15
+ def load!
16
+ @data = exists? ? JSON.parse(File.read(@path)) : {}
17
+ @data['_read_at'] = Time.now
18
+ @data
19
+ end
20
+
21
+ def exists?
22
+ File.exist?(@path)
23
+ end
24
+
25
+ def [](key)
26
+ data[key]
27
+ end
28
+
29
+ def []=(key, value)
30
+ data[key] = value
31
+ end
32
+
33
+ def save
34
+ return false unless @data
35
+ @data['_saved_at'] = Time.now
36
+ File.open(@path, 'w') do |file|
37
+ file.write(to_s)
38
+ end
39
+ true
40
+ end
41
+
42
+ def delete!
43
+ File.unlink(@path) if File.exist?(@path)
44
+ end
45
+
46
+ def to_s
47
+ JSON.pretty_generate(data)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,4 @@
1
+ module GithubBackup
2
+ VERSION = "0.1.0"
3
+ APP = 'github-backup'
4
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: github_backup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alex Clink
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-04-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: link_header
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: http
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Automatically clones your github repos and makes git bundles
84
+ email:
85
+ - code@alexclink.com
86
+ executables:
87
+ - github-backup
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".ruby-gemset"
94
+ - ".ruby-version"
95
+ - Gemfile
96
+ - Gemfile.lock
97
+ - README.md
98
+ - Rakefile
99
+ - bin/console
100
+ - bin/setup
101
+ - exe/github-backup
102
+ - github_backup.gemspec
103
+ - lib/github_backup.rb
104
+ - lib/github_backup/api.rb
105
+ - lib/github_backup/config.rb
106
+ - lib/github_backup/git.rb
107
+ - lib/github_backup/helpers.rb
108
+ - lib/github_backup/json_store.rb
109
+ - lib/github_backup/version.rb
110
+ homepage: http://alexclink.com/gems/github-backup
111
+ licenses: []
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubygems_version: 3.0.3
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: Create backups of your github repos
132
+ test_files: []