github_backup 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []