gitbak 0.2.0 → 0.3.0

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