gitbak 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.
Files changed (5) hide show
  1. data/README +62 -0
  2. data/bin/gitbak +118 -0
  3. data/lib/gitbak.rb +175 -0
  4. data/lib/gitbak/version.rb +4 -0
  5. metadata +68 -0
data/README ADDED
@@ -0,0 +1,62 @@
1
+ -- {{{1
2
+
3
+ File : README
4
+ Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ Date : 2012-12-27
6
+
7
+ Copyright : Copyright (C) 2012 Felix C. Stegerman
8
+ Version : v0.2.0
9
+
10
+ -- }}}1
11
+
12
+ === Description === {{{1
13
+
14
+ gitbak - bitbucket/github/gist backup
15
+
16
+ GitBak allows you to mirror GitHub/Bitbucket/Gist repositories
17
+ easily; you only need to specify paths, users, and authentication in
18
+ ~/.gitbak and it does the rest.
19
+ }}}1
20
+
21
+ === Usage === {{{1
22
+
23
+ $ gitbak --help # read documentation
24
+ $ vim ~/.gitbak # configure
25
+ $ gitbak # mirror
26
+ }}}1
27
+
28
+ === Installing === {{{1
29
+
30
+ $ gem install gitbak # rubygems
31
+
32
+ Get it at https://github.com/obfusk/gitbak. Depends: git, ruby.
33
+ }}}1
34
+
35
+ === TODO === {{{1
36
+
37
+ Some things that may be useful/implemented at some point.
38
+
39
+ * custom services (should be easy already)
40
+ * metadata (issues, wikis, ...)
41
+ * teams/organisations
42
+ * filtering
43
+ * starred repos/gists
44
+ * specify ssh key(s)
45
+ * oauth ?!
46
+ * https clone auth
47
+ }}}1
48
+
49
+ === License === {{{1
50
+
51
+ GPLv2 [1].
52
+ }}}1
53
+
54
+ === References === {{{1
55
+
56
+ [1] GNU General Public License, version 2
57
+ http://www.opensource.org/licenses/GPL-2.0
58
+ }}}1
59
+
60
+ --
61
+
62
+ vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'gitbak'
4
+ require 'optparse'
5
+
6
+ # --
7
+
8
+ usage = 'gitbak [<option(s)>]'
9
+
10
+ info = <<-END.gsub(/^ {2}/, '') # {{{1
11
+ gitbak - bitbucket/github/gist backup
12
+
13
+ === Example Configuration ===
14
+
15
+ $ cat >> ~/.gitbak
16
+ dir = '/path/to/mirrors/dir'
17
+
18
+ %w{ user1 user2 }.each do |u|
19
+ GitBak::Cfg.auth :bitbucket, u
20
+ GitBak::Cfg.auth :github , u
21
+
22
+ GitBak::Cfg.bitbucket "\#{dir}/\#{u}/bitbucket", u, auth: true
23
+ GitBak::Cfg.github "\#{dir}/\#{u}/github" , u, auth: true
24
+ GitBak::Cfg.gist "\#{dir}/\#{u}/gist" , u, auth: true
25
+ end
26
+ ^D
27
+
28
+
29
+ === Configuration Methods ===
30
+
31
+ GitBak::Cfg.auth service, user[, password]
32
+ GitBak::Cfg.<service> dir, user[, options]
33
+
34
+ * password is optional; gitbak prompts for unspecified passwords.
35
+ * The services are: bitbucket, github, gist.
36
+ * Service options:
37
+ :auth is optional; can be true (same user) or 'username'.
38
+ :method is optional; defaults to :ssh.
39
+ * Each GitBak::Cfg.<service> call specifies a new configuration.
40
+ END
41
+ # }}}1
42
+
43
+ options = { cfgfile: "#{Dir.home}/.gitbak", verbose: false }
44
+
45
+ # --
46
+
47
+ OptionParser.new do |opts| # {{{1
48
+ opts.banner = usage
49
+
50
+ opts.on('-c', '--config-file FILE', 'Configuration file') do |f|
51
+ options[:cfgfile] = f
52
+ end
53
+
54
+ opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
55
+ options[:verbose] = v
56
+ end
57
+
58
+ opts.on_tail('-h', '--help', 'Show this message') do
59
+ puts opts, '', info
60
+ exit
61
+ end
62
+
63
+ opts.on_tail('--version', 'Show version') do
64
+ puts "gitbak v#{GitBak::VERSION}"
65
+ exit
66
+ end
67
+ end.parse! # }}}1
68
+
69
+ GitBak.die "usage: #{usage}" unless ARGV.length == 0
70
+
71
+ # --
72
+
73
+ module GitBak # {{{1
74
+ module Cfg
75
+ CFG__ = { bitbucket: [], github: [], gist: [], auth: {} }
76
+
77
+ def self.auth (service, user, pass = nil)
78
+ (CFG__[:auth][service] ||= {})[user] = \
79
+ { user: user, pass: pass }
80
+ end
81
+
82
+ def self._service (name)
83
+ meta = class << self; self; end
84
+ meta.send(:define_method, name) do |dir, user, opts = {}|
85
+ CFG__[name] << opts.merge(dir: dir, user: user)
86
+ end
87
+ end
88
+
89
+ SERVICES.each do |name|
90
+ _service name.downcase.to_sym
91
+ end
92
+ end
93
+ end # }}}1
94
+
95
+ # --
96
+
97
+ GitBak.die "Configuration file (#{options[:cfgfile]}) not found." \
98
+ unless GitBak.exists? options[:cfgfile]
99
+
100
+ load options[:cfgfile]
101
+
102
+ # --
103
+
104
+ GitBak::SERVICES.each do |name| # {{{1
105
+ key = name.downcase.to_sym
106
+ GitBak::Cfg::CFG__[:auth].fetch(key, []).each do |k, v|
107
+ p = "#{name} password for #{v[:user]}: "
108
+ v[:pass] ||= GitBak.prompt p, true
109
+ end
110
+ end # }}}1
111
+
112
+ GitBak::Cfg::CFG__[:auth][:gist] ||= GitBak::Cfg::CFG__[:auth][:github]
113
+
114
+ # --
115
+
116
+ GitBak.main GitBak::Cfg::CFG__.merge(options)
117
+
118
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,175 @@
1
+ require 'gitbak/version'
2
+
3
+ require 'fileutils'
4
+ require 'io/console'
5
+ require 'json'
6
+ require 'open-uri'
7
+
8
+ # --
9
+
10
+ module GitBak
11
+
12
+ SERVICES = %w{ Bitbucket GitHub Gist }
13
+
14
+ APIS = {
15
+ bitbucket: ->(user) { "api.bitbucket.org/1.0/users/#{user}" },
16
+ github: ->(user) { "api.github.com/users/#{user}/repos" },
17
+ gist: ->(user) { "api.github.com/users/#{user}/gists" },
18
+ }
19
+
20
+ REMOTES = { # {{{1
21
+ bitbucket: {
22
+ ssh: ->(u, r) { "git@bitbucket.org:#{u}/#{r}.git" },
23
+ https: ->(u, r) { "https://bitbucket.org/#{u}/#{r}.git" },
24
+ },
25
+ github: {
26
+ ssh: ->(u, r) { "git@github.com:#{u}/#{r}.git" },
27
+ https: ->(u, r) { "https://github.com/#{u}/#{r}.git" },
28
+ },
29
+ gist: {
30
+ ssh: ->(id) { "git@gist.github.com:#{id}.git" },
31
+ https: ->(id) { "https://gist.github.com/#{id}.git" },
32
+ },
33
+ } # }}}1
34
+
35
+ REMOTE = ->(s, x) { REMOTES[s][x.fetch(:method, :ssh).to_sym] }
36
+
37
+ class << self
38
+
39
+ def die (msg)
40
+ STDERR.puts msg
41
+ exit 1
42
+ end
43
+
44
+ def exists? (path)
45
+ File.exists?(path) or File.symlink?(path)
46
+ end
47
+
48
+ def sys (verbose, cmd, *args)
49
+ puts " $ #{([cmd] + args).join ' '}" if verbose
50
+ system [cmd, cmd], *args or raise 'OOPS' # TODO
51
+ end
52
+
53
+ def prompt (prompt, hide = false) # {{{1
54
+ STDOUT.print prompt
55
+ STDOUT.flush
56
+
57
+ if hide
58
+ line = STDIN.noecho { |i| i.readline }
59
+ STDOUT.puts
60
+ line
61
+ else
62
+ STDIN.readline
63
+ end .chomp
64
+ end # }}}1
65
+
66
+ # --
67
+
68
+ def api_get (service, user, auth)
69
+ opts = auth ? { http_basic_authentication:
70
+ [auth[:user], auth[:pass]] } : {}
71
+
72
+ JSON.load open("https://#{APIS[service][user]}", opts)
73
+ end
74
+
75
+ def repo_name (remote)
76
+ remote.sub(%r!^.*[/:]!, '').sub(/\.git$/, '')
77
+ end
78
+
79
+ def mirror (remote, dir, verbose) # {{{1
80
+ name = repo_name remote
81
+ name_ = name + '.git'
82
+ repo_dir = "#{dir}/#{name_}"
83
+
84
+ FileUtils.mkdir_p dir
85
+
86
+ if exists? repo_dir
87
+ FileUtils.cd(repo_dir) do
88
+ sys verbose, *%w{ git remote update }
89
+ end
90
+ else
91
+ FileUtils.cd(dir) do
92
+ sys verbose,
93
+ *( %w{ git clone --mirror -n } + [remote, name_] )
94
+ end
95
+ end
96
+ end # }}}1O
97
+
98
+ # --
99
+
100
+ def repos_bitbucket (x, auth) # {{{1
101
+ rem = REMOTE[:bitbucket, x]
102
+
103
+ api_get(:bitbucket, x[:user], auth)['repositories'] \
104
+ .select { |r| r['scm'] == 'git' } .map do |r|
105
+ { remote: rem[x[:user], r['name']],
106
+ description: r['description'], name: r['name'] }
107
+ end
108
+ end # }}}1
109
+
110
+ def repos_github (x, auth) # {{{1
111
+ rem = REMOTE[:github, x]
112
+
113
+ api_get(:github, x[:user], auth).map do |r|
114
+ { remote: rem[x[:user], r['name']],
115
+ description: r['description'], name: r['name'] }
116
+ end
117
+ end # }}}1
118
+
119
+ def repos_gist (x, auth) # {{{1
120
+ rem = REMOTE[:gist, x]
121
+
122
+ api_get(:gist, x[:user], auth).map do |r|
123
+ { remote: rem[r['id']],
124
+ description: r['description'], name: r['id'] }
125
+ end
126
+ end # }}}1
127
+
128
+ # --
129
+
130
+ def mirror_service (service, x, auth, verbose) # {{{1
131
+ puts "#{service} for #{x[:user]} ..."
132
+
133
+ auth_ = x[:auth] && auth[x[ x[:auth] == true ? :user : :auth ]]
134
+ repos = send "repos_#{service}", x, auth_
135
+
136
+ repos.each do |r|
137
+ d = r[:description]
138
+ puts "==> #{service} | #{x[:user]} | #{r[:name]} | #{d} <==" \
139
+ if verbose
140
+ mirror r[:remote], x[:dir], verbose
141
+ puts if verbose
142
+ end
143
+
144
+ repos.length
145
+ end # }}}1
146
+
147
+ # --
148
+
149
+ def main (config) # {{{1
150
+ sum = {}
151
+
152
+ SERVICES.map { |s| s.downcase.to_sym } .each do |s|
153
+ sum[s] = {}
154
+ config[s].each do |x|
155
+ sum[s][x[:user]] = mirror_service s, x,
156
+ config[:auth][s], config[:verbose]
157
+ end
158
+ end
159
+
160
+ if config[:verbose]
161
+ puts '', "=== Summary ===", ''
162
+ sum.each_pair do |s, x|
163
+ x.each_pair do |u, n|
164
+ printf " %-15s for %-20s: %10s repositories\n", s, u, n
165
+ end
166
+ end
167
+ puts
168
+ end
169
+ end # }}}1
170
+
171
+ end
172
+
173
+ end
174
+
175
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,4 @@
1
+ module GitBak
2
+ VERSION = '0.2.0'
3
+ DATE = '2012-12-27'
4
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gitbak
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Felix C. Stegerman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json
16
+ requirement: &17892680 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *17892680
25
+ description: ! 'GitBak allows you to mirror GitHub/Bitbucket/Gist repositories
26
+
27
+ easily; you only need to specify paths, users, and authentication
28
+
29
+ in ~/.gitbak and it does the rest.
30
+
31
+ '
32
+ email:
33
+ - flx@obfusk.net
34
+ executables:
35
+ - gitbak
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - README
40
+ - bin/gitbak
41
+ - lib/gitbak/version.rb
42
+ - lib/gitbak.rb
43
+ homepage: https://github.com/obfusk/gitbak
44
+ licenses:
45
+ - GPLv2
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: 1.9.1
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubyforge_project:
64
+ rubygems_version: 1.8.11
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: bitbucket/github/gist backup
68
+ test_files: []