gitbak 0.2.0 → 0.3.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.
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ <!-- \{{{1 -->
2
+
3
+ File : README
4
+ Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ Date : 2012-12-29
6
+
7
+ Copyright : Copyright (C) 2012 Felix C. Stegerman
8
+ Version : v0.3.0
9
+
10
+ <!-- }}}1 -->
11
+
12
+ ## Description
13
+ <!-- \{{{1 -->
14
+
15
+ gitbak - bitbucket/github/gist backup
16
+
17
+ GitBak allows you to mirror Bitbucket/GitHub/Gist repositories
18
+ easily; you only need to specify paths, users, and authentication in
19
+ ~/.gitbak and it does the rest.
20
+
21
+ <!-- }}}1 -->
22
+
23
+ ## Usage
24
+ <!-- \{{{1 -->
25
+
26
+ $ gitbak --help # read documentation
27
+ $ vim ~/.gitbak # configure
28
+ $ gitbak -v # mirror
29
+
30
+ <!-- }}}1 -->
31
+
32
+ ## Installing
33
+ <!-- \{{{1 -->
34
+
35
+ $ gem install gitbak # rubygems
36
+
37
+ Get it at https://github.com/obfusk/gitbak. Depends: git, ruby.
38
+
39
+ <!-- }}}1 -->
40
+
41
+ ## TODO
42
+ <!-- \{{{1 -->
43
+
44
+ Some things that may be useful/implemented at some point.
45
+
46
+ * ask password again on typo (^D) or auth fail
47
+ * tests?
48
+ * better error handling?
49
+
50
+ * custom services (should be easy to add already)
51
+ * metadata (issues, wikis, ...)
52
+ * teams/organisations
53
+ * starred repos/gists
54
+ * filtering
55
+ * oauth?
56
+
57
+ * specify ssh key(s)?
58
+ * https clone auth?
59
+
60
+ <!-- }}}1 -->
61
+
62
+ ## License
63
+ <!-- \{{{1 -->
64
+
65
+ GPLv2 [1].
66
+
67
+ <!-- }}}1 -->
68
+
69
+ ## References
70
+ <!-- \{{{1 -->
71
+
72
+ [1] GNU General Public License, version 2
73
+ --- http://www.opensource.org/licenses/GPL-2.0
74
+
75
+ <!-- }}}1 -->
76
+
77
+ <!-- vim: set tw=70 sw=2 sts=2 et fdm=marker : -->
data/bin/gitbak CHANGED
@@ -1,118 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'gitbak'
4
- require 'optparse'
3
+ require 'gitbak/exec'
5
4
 
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)
5
+ GitBak::Executable.main
117
6
 
118
7
  # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,139 @@
1
+ require 'gitbak/eval'
2
+ require 'gitbak/misc'
3
+ require 'gitbak/services'
4
+
5
+ # --
6
+
7
+ # gitbak namespace
8
+ module GitBak
9
+
10
+ # configuration
11
+ module Config # {{{1
12
+
13
+ # configuration error
14
+ class ConfigError < GitBak::Error; end
15
+
16
+ # --
17
+
18
+ # description
19
+ INFO = 'gitbak - bitbucket/github/gist backup'
20
+
21
+ # configuration example
22
+ CONFIG_EX = <<-END.gsub(/^ {6}/, '') # {{{2
23
+ === Example Configuration ===
24
+
25
+ $ cat >> ~/.gitbak
26
+ dir = '/path/to/mirrors/dir'
27
+
28
+ GitBak.configure do |auth, repos|
29
+ %w{ user1 user2 }.each do |u|
30
+ repos.bitbucket "\#{dir}/\#{u}/bitbucket", u, auth: true
31
+ repos.github "\#{dir}/\#{u}/github" , u, auth: true
32
+ repos.gist "\#{dir}/\#{u}/gist" , u, auth: true
33
+ end
34
+ end
35
+ ^D
36
+
37
+
38
+ === Configuration Methods ===
39
+
40
+ auth.<service> user[, password]
41
+ repos.<service> dir, user[, options]
42
+
43
+
44
+ The (default) services are: bitbucket, github, gist.
45
+ If a password is not specified, gitbak will prompt for it.
46
+
47
+
48
+ === Optional Repository Options ===
49
+
50
+ :auth can be true (same user) or 'username'.
51
+ :method defaults to :ssh.
52
+ END
53
+ # }}}2
54
+
55
+ # --
56
+
57
+ # configuration base class
58
+ class ServiceCfg # {{{2
59
+ # data
60
+ attr_reader :_data
61
+
62
+ # init
63
+ def initialize
64
+ @_data = {}
65
+ end
66
+
67
+ # pass on to _service or super
68
+ def method_missing (meth, *args, &block)
69
+ if GitBak::Services::SERVICES.include? meth
70
+ _service meth, *args
71
+ else
72
+ super
73
+ end
74
+ end
75
+ end # }}}2
76
+
77
+ # authentication configuration
78
+ class AuthCfg < ServiceCfg # {{{2
79
+ # set service auth
80
+ def _service (name, user, pass = nil)
81
+ (@_data[name] ||= {})[user] = { user: user, pass: pass }
82
+ end
83
+ end # }}}2
84
+
85
+ # repository configuration
86
+ class ReposCfg < ServiceCfg # {{{2
87
+ # set service repo
88
+ def _service (name, dir, user, opts = {})
89
+ c = opts.merge dir: dir, user: user
90
+ c[:auth] = c[:user] if c[:auth] == true
91
+ (@_data[name] ||= []) << c
92
+ end
93
+ end # }}}2
94
+
95
+ # authentication and repository configuration
96
+ class Cfg # {{{2
97
+ # data
98
+ attr_reader :auth, :repos
99
+
100
+ # init
101
+ def initialize
102
+ @auth = AuthCfg.new
103
+ @repos = ReposCfg.new
104
+ end
105
+
106
+ # get data
107
+ def data # {{{3
108
+ auth = @auth._data
109
+ repos = @repos._data
110
+
111
+ { auth: auth, repos: repos }
112
+ end # }}}3
113
+ end # }}}2
114
+
115
+ # --
116
+
117
+ # load configuration file
118
+ def self.load (file) # {{{2
119
+ cfg = eval File.read(file), GitBak::Eval.new.binding, file # ???
120
+
121
+ raise ConfigError, "[#{file}] isn't a GitBak::Config::Cfg " \
122
+ "(#{cfg.class} instead)." \
123
+ unless Cfg === cfg
124
+
125
+ cfg
126
+ end # }}}2
127
+
128
+ end # }}}1
129
+
130
+ # configure
131
+ def self.configure (&block)
132
+ cfg = Config::Cfg.new
133
+ block[cfg.auth, cfg.repos]
134
+ cfg
135
+ end
136
+
137
+ end
138
+
139
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,6 @@
1
+ module GitBak
2
+ # evaluation wrapper
3
+ class Eval
4
+ public :binding
5
+ end
6
+ end
@@ -0,0 +1,85 @@
1
+ require 'gitbak'
2
+ require 'gitbak/config'
3
+
4
+ require 'optparse'
5
+
6
+ # --
7
+
8
+ # gitbak namespace
9
+ module GitBak
10
+ # command-line executable
11
+ module Executable
12
+
13
+ # command-line usage
14
+ USAGE = 'gitbak [<option(s)>]'
15
+
16
+ # parse command line options; die on failure
17
+ def self.parse_options (args) # {{{1
18
+ args_ = args.dup
19
+ options = { cfgfile: "#{Dir.home}/.gitbak", verbose: false,
20
+ noact: false }
21
+
22
+ op = OptionParser.new do |opts| # {{{2
23
+ opts.banner = USAGE
24
+
25
+ opts.on('-c', '--config-file FILE',
26
+ 'Configuration file') do |x|
27
+ options[:cfgfile] = x
28
+ end
29
+
30
+ opts.on('-v', '--[no-]verbose', 'Run verbosely') do |x|
31
+ options[:verbose] = x
32
+ end
33
+
34
+ opts.on('-n', '--no-act', 'List w/o mirroring') do |x|
35
+ options[:noact] = !x
36
+ end
37
+
38
+ opts.on_tail('-h', '--help', 'Show this message') do
39
+ puts GitBak::Config::INFO, '', opts
40
+ exit
41
+ end
42
+
43
+ opts.on_tail('-e', '--example',
44
+ 'Show example configuration') do
45
+ puts GitBak::Config::CONFIG_EX
46
+ exit
47
+ end
48
+
49
+ opts.on_tail('--version', 'Show version') do
50
+ puts "gitbak v#{GitBak::VERSION}"
51
+ exit
52
+ end
53
+ end # }}}2
54
+
55
+ begin
56
+ op.parse! args_
57
+ rescue OptionParser::ParseError => e
58
+ GitBak::Misc.die! "#{e}\n\n#{op}"
59
+ end
60
+
61
+ GitBak::Misc.die! "usage: #{USAGE}" unless args_.length == 0
62
+
63
+ options
64
+ end # }}}1
65
+
66
+ # parse configuration file; die on failure
67
+ def self.parse_cfgfile (file)
68
+ GitBak::Misc.die! "configuration file (#{file}) not found" \
69
+ unless GitBak::Misc.exists? file
70
+
71
+ GitBak::Config.load file
72
+ end
73
+
74
+ # run!
75
+ def self.main (args = nil)
76
+ options = parse_options (args or ARGV)
77
+ cfg = parse_cfgfile options[:cfgfile]
78
+
79
+ GitBak.main options[:verbose], options[:noact], cfg.data
80
+ end
81
+
82
+ end
83
+ end
84
+
85
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,60 @@
1
+ require 'io/console'
2
+
3
+ # --
4
+
5
+ # gitbak namespace
6
+ module GitBak
7
+
8
+ # base error class
9
+ class Error < RuntimeError; end
10
+
11
+ # miscellaneous
12
+ module Misc
13
+
14
+ # command execution failure
15
+ class SysError < GitBak::Error; end
16
+
17
+ # --
18
+
19
+ # deep copy using Marshal
20
+ def self.deepdup (obj)
21
+ Marshal.load(Marshal.dump obj)
22
+ end
23
+
24
+ # print msg to stderr and exit
25
+ def self.die! (msg)
26
+ STDERR.puts msg
27
+ exit 1
28
+ end
29
+
30
+ # does file/dir or symlink exists?
31
+ def self.exists? (path)
32
+ File.exists?(path) or File.symlink?(path)
33
+ end
34
+
35
+ # prompt for line; optionally hide input
36
+ def self.prompt (prompt, hide = false) # {{{1
37
+ STDOUT.print prompt
38
+ STDOUT.flush
39
+
40
+ if hide
41
+ line = STDIN.noecho { |i| i.gets }
42
+ STDOUT.puts
43
+ else
44
+ line = STDIN.gets
45
+ end
46
+
47
+ line and line.chomp
48
+ end # }}}1
49
+
50
+ # execute command; raises SysError on failure; optionally verbose
51
+ def self.sys (verbose, cmd, *args)
52
+ puts "$ #{ ([cmd] + args).join ' ' }" if verbose
53
+ system [cmd, cmd], *args or raise SysError,
54
+ "failed to run command #{ ([cmd] + args) } (#$?)"
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,114 @@
1
+ require 'gitbak/misc'
2
+
3
+ require 'json'
4
+ require 'open-uri'
5
+
6
+ # --
7
+
8
+ # gitbak namespace
9
+ module GitBak
10
+ # git services
11
+ module Services
12
+
13
+ # authentication error
14
+ class AuthError < GitBak::Error; end
15
+
16
+ # --
17
+
18
+ # avaliable services
19
+ SERVICES = %w{ bitbucket github gist }.map(&:to_sym)
20
+
21
+ # use another service's authentication
22
+ USE_AUTH = { gist: :github }
23
+
24
+ # API urls
25
+ APIS = {
26
+ bitbucket: ->(user) { "api.bitbucket.org/1.0/users/#{user}" },
27
+ github: ->(user) { "api.github.com/users/#{user}/repos" },
28
+ gist: ->(user) { "api.github.com/users/#{user}/gists" },
29
+ }
30
+
31
+ # remote urls
32
+ REMOTES = # {{{1
33
+ {
34
+ bitbucket: {
35
+ ssh: ->(u, r) { "git@bitbucket.org:#{u}/#{r}.git" },
36
+ https: ->(u, r) { "https://bitbucket.org/#{u}/#{r}.git" },
37
+ },
38
+ github: {
39
+ ssh: ->(u, r) { "git@github.com:#{u}/#{r}.git" },
40
+ https: ->(u, r) { "https://github.com/#{u}/#{r}.git" },
41
+ },
42
+ gist: {
43
+ ssh: ->(id) { "git@gist.github.com:#{id}.git" },
44
+ https: ->(id) { "https://gist.github.com/#{id}.git" },
45
+ },
46
+ } # }}}1
47
+
48
+ # long keyword^wsymbol ;-)
49
+ AUTH = :http_basic_authentication
50
+
51
+ # --
52
+
53
+ # get data from API
54
+ def self.api_get (service, user, auth) # {{{1
55
+ url = "https://#{APIS[service][user]}"
56
+ opts = auth ? { AUTH => [auth[:user], auth[:pass]] } : {}
57
+
58
+ begin
59
+ data = open url, opts
60
+ rescue OpenURI::HTTPError => e
61
+ if e.io.status[0] == '401'
62
+ raise AuthError,
63
+ "401 for #{auth[:user]} on #{service}/#{user}"
64
+ else
65
+ raise
66
+ end
67
+ end
68
+
69
+ data
70
+ end # }}}1
71
+
72
+ # get repositories from service; uses api_get if service in APIS,
73
+ # api_get_<service> otherwise
74
+ # -> [{name:,remote:,description:},...]
75
+ def self.repositories (service, cfg, auth)
76
+ rem = REMOTES[service][cfg.fetch(:method, :ssh).to_sym]
77
+ args = [service, cfg[:user], auth]
78
+ data = APIS[service] ? api_get(*args) :
79
+ send("api_get_#{service}", *args)
80
+ send service, cfg, data, rem
81
+ end
82
+
83
+ # --
84
+
85
+ # turn bitbucket API data into a list of repositories
86
+ def self.bitbucket (cfg, data, rem)
87
+ data_ = JSON.load data
88
+ repos = data_['repositories'].select { |r| r['scm'] == 'git' }
89
+ repos.map do |r|
90
+ { name: r['name'], remote: rem[cfg[:user], r['name']],
91
+ description: r['description'] }
92
+ end
93
+ end
94
+
95
+ # turn github API data into a list of repositories
96
+ def self.github (cfg, data, rem)
97
+ JSON.load(data).map do |r|
98
+ { name: r['name'], remote: rem[cfg[:user], r['name']],
99
+ description: r['description'] }
100
+ end
101
+ end
102
+
103
+ # turn gist API data into a list of repositories
104
+ def self.gist (cfg, data, rem)
105
+ JSON.load(data).map do |r|
106
+ { name: r['id'], remote: rem[r['id']],
107
+ description: r['description'] }
108
+ end
109
+ end
110
+
111
+ end
112
+ end
113
+
114
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -1,4 +1,7 @@
1
1
  module GitBak
2
- VERSION = '0.2.0'
3
- DATE = '2012-12-27'
2
+ # version and ...
3
+ VERSION = '0.3.0'
4
+
5
+ # ... date (for gemspec) ;-)
6
+ DATE = '2012-12-29'
4
7
  end
data/lib/gitbak.rb CHANGED
@@ -1,173 +1,116 @@
1
+ require 'gitbak/misc'
2
+ require 'gitbak/services'
1
3
  require 'gitbak/version'
2
4
 
3
5
  require 'fileutils'
4
- require 'io/console'
5
- require 'json'
6
- require 'open-uri'
7
6
 
8
7
  # --
9
8
 
9
+ # gitbak namespace
10
10
  module GitBak
11
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
12
+ # extract name from remote; e.g. "git@server:foo/bar.git" and
13
+ # "https://server/foo/bar.git" become "bar"
14
+ def self.repo_name (remote)
15
+ remote.sub(%r!^.*[/:]!, '').sub(/\.git$/, '')
16
+ end
65
17
 
66
- # --
18
+ # clone (from remote) or update repository (in dir); optionally
19
+ # verbose
20
+ def self.mirror_repo (verbose, remote, dir) # {{{1
21
+ name = repo_name remote
22
+ name_ = name + '.git'
23
+ repo_dir = "#{dir}/#{name_}"
67
24
 
68
- def api_get (service, user, auth)
69
- opts = auth ? { http_basic_authentication:
70
- [auth[:user], auth[:pass]] } : {}
25
+ FileUtils.mkdir_p dir
71
26
 
72
- JSON.load open("https://#{APIS[service][user]}", opts)
27
+ if Misc.exists? repo_dir
28
+ puts "$ cd #{repo_dir}" if verbose
29
+ FileUtils.cd(repo_dir) do
30
+ Misc.sys verbose, *%w{ git remote update }
31
+ end
32
+ else
33
+ puts "$ cd #{dir}" if verbose
34
+ FileUtils.cd(dir) do
35
+ Misc.sys verbose,
36
+ *( %w{ git clone --mirror -n } + [remote, name_] )
37
+ end
73
38
  end
39
+ end # }}}1
74
40
 
75
- def repo_name (remote)
76
- remote.sub(%r!^.*[/:]!, '').sub(/\.git$/, '')
77
- end
41
+ # --
78
42
 
79
- def mirror (remote, dir, verbose) # {{{1
80
- name = repo_name remote
81
- name_ = name + '.git'
82
- repo_dir = "#{dir}/#{name_}"
43
+ # check auth; ask passwords
44
+ def self.process_config (config) # {{{1
45
+ config_ = Misc.deepdup config
83
46
 
84
- FileUtils.mkdir_p dir
47
+ config_[:repos].each do |service, cfgs|
48
+ auth = config_[:auth][service] ||= {}
49
+ cfgs.each do |cfg|
50
+ user = cfg[:auth]
51
+ auth[user] = { user: user, pass: nil } if user && !auth[user]
52
+ end
53
+ end
85
54
 
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
55
+ config_[:auth].each do |service, auth|
56
+ next if GitBak::Services::USE_AUTH[service]
57
+ auth.each_value do |x|
58
+ p = "#{service} password for #{x[:user]}: "
59
+ x[:pass] ||= Misc.prompt p, true # TODO
95
60
  end
96
- end # }}}1O
61
+ end
97
62
 
98
- # --
63
+ [config_[:auth], config_[:repos]]
64
+ end # }}}1
99
65
 
100
- def repos_bitbucket (x, auth) # {{{1
101
- rem = REMOTE[:bitbucket, x]
66
+ # fetch repository lists; optionally verbose
67
+ def self.fetch (verbose, auth, repos) # {{{1
68
+ repos.map do |service, cfgs|
69
+ au = auth[Services::USE_AUTH.fetch service, service]
70
+ cfgs.map do |cfg|
71
+ puts "listing #{service}/#{cfg[:user]} ..." if verbose
102
72
 
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'] }
73
+ begin
74
+ rs = Services.repositories service, cfg, au[cfg[:auth]]
75
+ rescue Services::AuthError => e
76
+ Misc.die! "authentication failure: #{e}"
107
77
  end
108
- end # }}}1
109
-
110
- def repos_github (x, auth) # {{{1
111
- rem = REMOTE[:github, x]
112
78
 
113
- api_get(:github, x[:user], auth).map do |r|
114
- { remote: rem[x[:user], r['name']],
115
- description: r['description'], name: r['name'] }
79
+ [service, cfg[:user], cfg[:dir], rs]
116
80
  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
81
+ end .flatten 1
82
+ end # }}}1
83
+
84
+ # mirror repositories; optionally verbose
85
+ def self.mirror verbose, repos # {{{1
86
+ repos.each do |s, usr, dir, rs|
87
+ rs.each do |r|
88
+ name, desc = r[:name], r[:description]
89
+ puts "==> #{s} | #{usr} | #{name} | #{desc} <==" if verbose
90
+ mirror_repo verbose, r[:remote], dir
141
91
  puts if verbose
142
92
  end
93
+ end
94
+ end # }}}1
95
+
96
+ # print summary
97
+ def self.summary repos # {{{1
98
+ puts '', '=== Summary ===', ''
99
+ repos.each do |service, usr, dir, rs|
100
+ printf " %-15s for %-20s: %10s repositories\n",
101
+ service, usr, rs.length
102
+ end
103
+ puts
104
+ end # }}}1
143
105
 
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
106
+ # --
170
107
 
108
+ # run!
109
+ def self.main (verbose, noact, config)
110
+ auth, repos = process_config config
111
+ repositories = fetch verbose, auth, repos
112
+ mirror verbose, repositories unless noact
113
+ summary repositories if verbose
171
114
  end
172
115
 
173
116
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitbak
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-27 00:00:00.000000000 Z
12
+ date: 2012-12-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
16
- requirement: &17892680 !ruby/object:Gem::Requirement
16
+ requirement: &9773800 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,8 +21,8 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *17892680
25
- description: ! 'GitBak allows you to mirror GitHub/Bitbucket/Gist repositories
24
+ version_requirements: *9773800
25
+ description: ! 'GitBak allows you to mirror Bitbucket/GitHub/Gist repositories
26
26
 
27
27
  easily; you only need to specify paths, users, and authentication
28
28
 
@@ -36,9 +36,14 @@ executables:
36
36
  extensions: []
37
37
  extra_rdoc_files: []
38
38
  files:
39
- - README
39
+ - README.md
40
40
  - bin/gitbak
41
+ - lib/gitbak/exec.rb
42
+ - lib/gitbak/config.rb
41
43
  - lib/gitbak/version.rb
44
+ - lib/gitbak/misc.rb
45
+ - lib/gitbak/eval.rb
46
+ - lib/gitbak/services.rb
42
47
  - lib/gitbak.rb
43
48
  homepage: https://github.com/obfusk/gitbak
44
49
  licenses:
@@ -66,3 +71,4 @@ signing_key:
66
71
  specification_version: 3
67
72
  summary: bitbucket/github/gist backup
68
73
  test_files: []
74
+ has_rdoc:
data/README DELETED
@@ -1,62 +0,0 @@
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 :