gitbak 0.2.0

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