gitbak 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3e3a5e41025ca2744190c8babdc5bac3247a007b
4
+ data.tar.gz: 65f8655194b5bf74dbc6d5e488e166f6870bc700
5
+ SHA512:
6
+ metadata.gz: ddee8e238c06d24f9db78c23c2b896d886b9ab29bda530d449f6a6e6b6698cded0b86919aafb7bbde3e079bf710b06d6a1788a8b651815a41584221856e4ffcb
7
+ data.tar.gz: 92627c7bd07aeaf46896e92d40a0567815d5c216f577ca6571c45311654dd9f3ada2cc2230ea348c8377085b418b93c414de693f935762a0bb13aed8c7cb6552
data/README.md CHANGED
@@ -1,16 +1,15 @@
1
- <!-- \{{{1 -->
1
+ []: {{{1
2
2
 
3
3
  File : README
4
4
  Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
- Date : 2013-04-21
5
+ Date : 2014-02-19
6
6
 
7
- Copyright : Copyright (C) 2013 Felix C. Stegerman
8
- Version : v0.4.3
7
+ Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ Version : v0.5.0
9
9
 
10
- <!-- }}}1 -->
10
+ []: }}}1
11
11
 
12
12
  ## Description
13
- <!-- \{{{1 -->
14
13
 
15
14
  gitbak - bitbucket/github/gist backup
16
15
 
@@ -19,70 +18,63 @@
19
18
 
20
19
  When run, gitbak:
21
20
 
22
- * asks for unspecified passwords;
21
+ * asks for passwords;
23
22
  * lists repositories using APIs - authenticating if necessary;
24
23
  * clones/updates repositories;
25
24
  * shows a summary (if verbose)
26
25
 
27
- <!-- }}}1 -->
28
-
29
26
  ## Usage
30
- <!-- \{{{1 -->
31
-
32
- $ gitbak --help # show options
33
- $ vim ~/.gitbak # configure
34
- $ gitbak -v # mirror
35
- $ time gitbak -v 2>&1 | tee log # w/ logfile
36
-
37
- You may want to run gitbak as a cron job. TODO
38
27
 
39
- <!-- }}}1 -->
28
+ ```bash
29
+ $ gitbak --help # show options
30
+ $ vim ~/.gitbak # configure
31
+ $ gitbak --no-act # dry run
32
+ $ gitbak -v # mirror
33
+ $ time gitbak -v 2>&1 | tee "$(date +%Y%m%d)".log # w/ logfile
34
+ ```
40
35
 
41
36
  ## Installing
42
- <!-- \{{{1 -->
43
37
 
44
- $ gem install gitbak # rubygems
38
+ ```bash
39
+ $ gem install gitbak # rubygems
40
+ ```
45
41
 
46
42
  Get it at https://github.com/obfusk/gitbak. Depends: git, ruby.
47
43
 
48
- <!-- }}}1 -->
49
-
50
44
  ## Configuration
51
- <!-- \{{{1 -->
52
45
 
53
46
  ### Example
54
47
 
55
48
  ```ruby
56
49
  # ~/.gitbak
57
-
58
50
  dir = "#{ Dir.home }/__mirror__/#{ Time.new.strftime '%Y%m%d' }"
59
-
60
- GitBak.configure do |auth, services|
61
- %w{ bob alice }.each do |u|
62
- services.bitbucket "#{dir}/#{u}/bitbucket", u, auth: true
63
- services.github "#{dir}/#{u}/github" , u, auth: true
64
- services.gist "#{dir}/#{u}/gist" , u, auth: true
65
- end
51
+ %w{ bob alice }.each do |u|
52
+ bitbucket "#{dir}/#{u}/bitbucket", user: u
53
+ github "#{dir}/#{u}/github" , user: u, token: true
54
+ gist "#{dir}/#{u}/gist" , user: u, auth: :github
66
55
  end
67
56
  ```
68
57
 
69
58
  ### Methods
70
59
 
71
- auth.<service> user[, password]
72
- services.<service> dir, user[, options]
73
-
74
- The (default) services are: bitbucket, github, gist. GitBak will
75
- prompt for unspecified passwords.
76
-
77
- #### Repository Options
60
+ ```ruby
61
+ bitbucket repo #, options...
62
+ github repo #, options...
63
+ gist repo #, options...
64
+ ```
78
65
 
79
- :auth can be true (same user) or 'username'.
80
- :method defaults to :ssh.
66
+ ### Options
81
67
 
82
- <!-- }}}1 -->
68
+ ```ruby
69
+ user: "username" # mandatory
70
+ token: true # use token instead of user/pass (default: false)
71
+ auth: false # use authentication (default: true);
72
+ use :github w/ gist to re-use github auth
73
+ method: :https # clone method (default: :ssh);
74
+ NB: https auth not implemented yet
75
+ ```
83
76
 
84
77
  ## TODO
85
- <!-- \{{{1 -->
86
78
 
87
79
  Some things that may be useful/implemented at some point.
88
80
 
@@ -90,7 +82,7 @@ end
90
82
  * tests?
91
83
  * better error handling?
92
84
 
93
- <!-- -->
85
+ #
94
86
 
95
87
  * custom services (should be easy to add already)
96
88
  * metadata (issues, wikis, ...)
@@ -99,26 +91,18 @@ end
99
91
  * filtering
100
92
  * oauth?
101
93
 
102
- <!-- -->
94
+ #
103
95
 
104
96
  * specify ssh key(s)?
105
97
  * https clone auth?
106
98
 
107
- <!-- }}}1 -->
108
-
109
99
  ## License
110
- <!-- \{{{1 -->
111
100
 
112
- GPLv2 [1].
113
-
114
- <!-- }}}1 -->
101
+ GPLv3+ [1].
115
102
 
116
103
  ## References
117
- <!-- \{{{1 -->
118
-
119
- [1] GNU General Public License, version 2
120
- --- http://www.opensource.org/licenses/GPL-2.0
121
104
 
122
- <!-- }}}1 -->
105
+ [1] GNU General Public License, version 3
106
+ --- http://www.gnu.org/licenses/gpl-3.0.html
123
107
 
124
- <!-- vim: set tw=70 sw=2 sts=2 et fdm=marker : -->
108
+ []: ! ( vim: set tw=70 sw=2 sts=2 et fdm=marker : )
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
 
12
12
  When run, gitbak:
13
13
 
14
- * asks for unspecified passwords;
14
+ * asks for passwords;
15
15
  * lists repositories using APIs - authenticating if necessary;
16
16
  * clones/updates repositories;
17
17
  * shows a summary (if verbose)
@@ -23,13 +23,11 @@ Gem::Specification.new do |s|
23
23
  s.authors = [ 'Felix C. Stegerman' ]
24
24
  s.email = %w{ flx@obfusk.net }
25
25
 
26
- s.license = 'GPLv2'
26
+ s.license = 'GPLv3+'
27
27
 
28
28
  s.executables = %w{ gitbak }
29
- s.files = %w{ .yardopts README.md bin/gitbak gitbak.gemspec }\
29
+ s.files = %w{ .yardopts README.md gitbak.gemspec } \
30
30
  + Dir['lib/**/*.rb']
31
31
 
32
- s.add_runtime_dependency 'json'
33
-
34
32
  s.required_ruby_version = '>= 1.9.1'
35
33
  end
@@ -2,41 +2,47 @@
2
2
  #
3
3
  # File : gitbak.rb
4
4
  # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
- # Date : 2013-01-03
5
+ # Date : 2014-02-19
6
6
  #
7
- # Copyright : Copyright (C) 2013 Felix C. Stegerman
8
- # Licence : GPLv2
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : GPLv3+
9
9
  #
10
10
  # -- ; }}}1
11
11
 
12
12
  require 'gitbak/misc'
13
- require 'gitbak/services'
14
- require 'gitbak/version'
15
13
 
16
14
  require 'fileutils'
15
+ require 'json'
16
+ require 'open-uri'
17
17
 
18
18
  # --
19
19
 
20
20
  # gitbak namespace
21
21
  module GitBak
22
22
 
23
+ module Services; end
24
+
25
+ # authentication error
26
+ class AuthError < Error; end
27
+
28
+ SERVICES = {}
29
+
30
+ # --
31
+
23
32
  # extract name from remote; e.g. "git@server:foo/bar.git" and
24
33
  # "https://server/foo/bar.git" become "bar"
25
- def self.repo_name (remote)
34
+ def self.repo_name(remote)
26
35
  remote.sub(%r!^.*[/:]!, '').sub(/\.git$/, '')
27
36
  end
28
37
 
29
- # clone (from remote) or update repository (in dir); optionally
38
+ # clone (from remote) or update repository (in dir), optionally
30
39
  # verbose
31
- def self.mirror_repo (verbose, noact, remote, dir) # {{{1
40
+ def self.mirror_repo(verbose, noact, remote, dir) # {{{1
32
41
  name = repo_name remote
33
42
  name_ = name + '.git'
34
43
  repo_dir = "#{dir}/#{name_}"
35
-
36
- sys = ->(args) { Misc.sys *args, verbose: verbose, noact: noact }
37
-
44
+ sys = -> args { Misc.sys *args, verbose: verbose, noact: noact }
38
45
  FileUtils.mkdir_p dir
39
-
40
46
  if Misc.exists? repo_dir
41
47
  puts "$ cd #{repo_dir}" if verbose or noact
42
48
  FileUtils.cd(repo_dir) do
@@ -52,79 +58,82 @@ module GitBak
52
58
 
53
59
  # --
54
60
 
55
- # check auth; ask passwords
56
- def self.process_config (config) # {{{1
57
- config_ = Misc.deepdup config
58
-
59
- config_[:repos].each do |service, cfgs|
60
- auth = config_[:auth][service] ||= {}
61
- cfgs.each do |cfg|
62
- user = cfg[:auth]
63
- auth[user] = { user: user, pass: nil } if user && !auth[user]
61
+ # get data from API, optionally w/ auth
62
+ # @raise AuthError on 401
63
+ def self.api_get(url, opts, info, auth, f) # {{{1
64
+ g = f || -> x { x }
65
+ o = auth ? { http_basic_authentication:
66
+ [auth[:user],auth[:pass]] } : {}
67
+ begin
68
+ g[open url, o.merge(opts || {})]
69
+ rescue OpenURI::HTTPError => e
70
+ if e.io.status[0] == '401'
71
+ raise AuthError, "401 for #{auth[:user]} #{info}"
72
+ else
73
+ raise
64
74
  end
65
75
  end
76
+ end # }}}1
66
77
 
67
- config_[:auth].each do |service, auth|
68
- next if GitBak::Services::USE_AUTH[service]
69
- auth.each_value do |x|
70
- p = "#{service} password for #{x[:user]}: "
71
- x[:pass] ||= Misc.prompt p, true # TODO
72
- end
73
- end
78
+ # get paginated data from API
79
+ def self.paginate(url, opts, info, auth, f, pag, join)
80
+ pages = []; g = -> { api_get url, opts, info, auth, f }
81
+ while url; pages << data = g[]; url = pag[data]; end
82
+ join[pages]
83
+ end
74
84
 
75
- [config_[:auth], config_[:repos]]
76
- end # }}}1
85
+ # concatenate json
86
+ def self.cat_json(pages)
87
+ pages.map { |x| JSON.load x } .flatten 1
88
+ end
89
+
90
+ # --
77
91
 
78
- # fetch repository lists; optionally verbose
79
- def self.fetch (verbose, auth, repos) # {{{1
80
- repos.map do |service, cfgs|
81
- au = auth[Services::USE_AUTH.fetch service, service]
82
- cfgs.map do |cfg|
83
- puts "listing #{service}/#{cfg[:user]} ..." if verbose
92
+ # show info about mirroring
93
+ def self.show_mirror(*args)
94
+ puts "==> #{args*' | '} <=="
95
+ end
84
96
 
85
- begin
86
- rs = Services.repositories service, cfg, au[cfg[:auth]]
87
- rescue Services::AuthError => e
88
- Misc.die! "authentication failure: #{e}"
89
- end
97
+ # show info about number of repos
98
+ def self.show_summary(info, n)
99
+ printf " %3d | %s\n", n, info
100
+ end
90
101
 
91
- [service, cfg[:user], cfg[:dir], rs]
92
- end
93
- end .flatten 1
94
- end # }}}1
102
+ # --
95
103
 
96
- # mirror repositories; optionally verbose
97
- def self.mirror (verbose, noact, repos) # {{{1
98
- repos.each do |s, usr, dir, rs|
99
- rs.each do |r|
100
- name, desc = r[:name], r[:description]
101
- puts "==> #{s} | #{usr} | #{name} | #{desc} <==" if verbose
102
- mirror_repo verbose, noact, r[:remote], dir
103
- puts if verbose
104
- end
104
+ # configure
105
+ def self.configure(file) # {{{1
106
+ c = {}; x = Object.new
107
+ x.instance_eval { def self._binding; binding; end }
108
+ SERVICES.each_pair do |k,v|
109
+ x.define_singleton_method(k) { |*a| v.configure c, *a }
105
110
  end
111
+ x._binding.eval File.read(file), file; c
106
112
  end # }}}1
107
113
 
108
- # print summary
109
- def self.summary (repos) # {{{1
110
- puts '', '=== Summary ===', ''
111
- repos.each do |service, usr, dir, rs|
112
- printf " %-15s for %-20s: %10s repositories\n",
113
- service, usr, rs.length
114
+ # run!
115
+ def self.main(verbose, noact, config) # {{{1
116
+ SERVICES.each_pair do |k,v|
117
+ begin
118
+ v.fetch config, verbose
119
+ rescue AuthError => e
120
+ Misc.die! "authentication failure: #{e}"
121
+ end
114
122
  end
115
123
  puts
124
+ SERVICES.each_pair do |k,v|
125
+ v.mirror config, verbose, noact
126
+ end
127
+ puts
128
+ if verbose
129
+ puts '=== Summary ==='; puts
130
+ SERVICES.each_pair do |k,v|
131
+ v.summarise config
132
+ end
133
+ puts
134
+ end
116
135
  end # }}}1
117
136
 
118
- # --
119
-
120
- # run!
121
- def self.main (verbose, noact, config)
122
- auth, repos = process_config config
123
- repositories = fetch verbose, auth, repos
124
- mirror verbose, noact, repositories
125
- summary repositories if verbose
126
- end
127
-
128
137
  end
129
138
 
130
139
  # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -2,15 +2,17 @@
2
2
  #
3
3
  # File : gitbak/exec.rb
4
4
  # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
- # Date : 2013-01-03
5
+ # Date : 2014-02-19
6
6
  #
7
- # Copyright : Copyright (C) 2013 Felix C. Stegerman
8
- # Licence : GPLv2
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : GPLv3+
9
9
  #
10
10
  # -- ; }}}1
11
11
 
12
12
  require 'gitbak'
13
- require 'gitbak/config'
13
+ require 'gitbak/misc'
14
+ require 'gitbak/services'
15
+ require 'gitbak/version'
14
16
 
15
17
  require 'optparse'
16
18
 
@@ -31,63 +33,50 @@ module GitBak
31
33
  # --
32
34
 
33
35
  # parse command line options; die on failure
34
- def self.parse_options (args) # {{{1
36
+ def self.parse_options(args) # {{{1
35
37
  args_ = args.dup
36
38
  options = { cfgfile: "#{Dir.home}/.gitbak", verbose: false,
37
39
  noact: false }
38
-
39
- op = OptionParser.new do |opts| # {{{2
40
+ op = OptionParser.new do |opts|
40
41
  opts.banner = USAGE
41
-
42
42
  opts.on('-c', '--config-file FILE',
43
43
  'Configuration file') do |x|
44
44
  options[:cfgfile] = x
45
45
  end
46
-
47
46
  opts.on('-v', '--[no-]verbose', 'Run verbosely') do |x|
48
47
  options[:verbose] = x
49
48
  end
50
-
51
49
  opts.on('-n', '--no-act', 'List w/o mirroring') do |x|
52
50
  options[:noact] = !x
53
51
  end
54
-
55
52
  opts.on_tail('-h', '--help', 'Show this message') do
56
- puts INFO, '', opts
57
- exit
53
+ puts INFO, '', opts; exit
58
54
  end
59
-
60
55
  opts.on_tail('--version', 'Show version') do
61
- puts "gitbak v#{GitBak::VERSION}"
62
- exit
56
+ puts "gitbak v#{GitBak::VERSION}"; exit
63
57
  end
64
- end # }}}2
65
-
58
+ end
66
59
  begin
67
60
  op.parse! args_
68
61
  rescue OptionParser::ParseError => e
69
62
  GitBak::Misc.die! "#{e}\n\n#{op}"
70
63
  end
71
-
72
64
  GitBak::Misc.die! "usage: #{USAGE}" unless args_.length == 0
73
-
74
65
  options
75
66
  end # }}}1
76
67
 
77
68
  # parse configuration file; die on failure
78
- def self.parse_cfgfile (file)
69
+ def self.configure(file)
79
70
  GitBak::Misc.die! "configuration file (#{file}) not found" \
80
71
  unless GitBak::Misc.exists? file
81
-
82
- GitBak::Config.load file
72
+ GitBak.configure file
83
73
  end
84
74
 
85
75
  # run!
86
- def self.main (args = nil)
87
- options = parse_options (args or ARGV)
88
- cfg = parse_cfgfile options[:cfgfile]
89
-
90
- GitBak.main options[:verbose], options[:noact], cfg.data
76
+ def self.main(args = nil)
77
+ options = parse_options(args || ARGV)
78
+ cfg = configure options[:cfgfile]
79
+ GitBak.main options[:verbose], options[:noact], cfg
91
80
  end
92
81
 
93
82
  end
@@ -2,10 +2,10 @@
2
2
  #
3
3
  # File : gitbak/misc.rb
4
4
  # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
- # Date : 2013-01-03
5
+ # Date : 2014-02-19
6
6
  #
7
- # Copyright : Copyright (C) 2013 Felix C. Stegerman
8
- # Licence : GPLv2
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : GPLv3+
9
9
  #
10
10
  # -- ; }}}1
11
11
 
@@ -27,36 +27,22 @@ module GitBak
27
27
 
28
28
  # --
29
29
 
30
- # deep copy using Marshal
31
- def self.deepdup (obj)
32
- Marshal.load(Marshal.dump obj)
33
- end
34
-
35
30
  # print msg to stderr and exit
36
31
  def self.die! (msg)
37
- STDERR.puts msg
38
- exit 1
32
+ STDERR.puts msg; exit 1
39
33
  end
40
34
 
41
35
  # does file/dir or symlink exists?
42
36
  def self.exists? (path)
43
- File.exists?(path) or File.symlink?(path)
37
+ File.exists?(path) || File.symlink?(path)
44
38
  end
45
39
 
46
40
  # prompt for line; optionally hide input
47
- def self.prompt (prompt, hide = false) # {{{1
48
- STDOUT.print prompt
49
- STDOUT.flush
50
-
51
- if hide
52
- line = STDIN.noecho { |i| i.gets }
53
- STDOUT.puts
54
- else
55
- line = STDIN.gets
56
- end
57
-
58
- line and line.chomp
59
- end # }}}1
41
+ def self.prompt (prompt, hide = false)
42
+ STDOUT.print prompt; STDOUT.flush
43
+ (line = hide ? STDIN.noecho { |i| i.gets } .tap { STDOUT.puts }
44
+ : STDIN.gets) && line.chomp
45
+ end
60
46
 
61
47
  # execute command
62
48
  # @raise SysError on failure
@@ -69,10 +55,8 @@ module GitBak
69
55
  # @see sys_
70
56
  def self.sys (cmd, *args) # {{{1
71
57
  opts = Hash === args.last ? args.pop : {}
72
-
73
58
  puts "$ #{ ([cmd] + args).join ' ' }" \
74
59
  if opts[:verbose] or opts[:noact]
75
-
76
60
  if opts[:noact]
77
61
  puts '(not actually doing anything)'
78
62
  else
@@ -2,156 +2,15 @@
2
2
  #
3
3
  # File : gitbak/services.rb
4
4
  # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
- # Date : 2013-04-21
5
+ # Date : 2014-02-19
6
6
  #
7
- # Copyright : Copyright (C) 2013 Felix C. Stegerman
8
- # Licence : GPLv2
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : GPLv3+
9
9
  #
10
10
  # -- ; }}}1
11
11
 
12
- require 'gitbak/misc'
13
-
14
- require 'json'
15
- require 'open-uri'
16
-
17
- # --
18
-
19
- # gitbak namespace
20
- module GitBak
21
-
22
- # git hosting services
23
- module Services
24
-
25
- # authentication error
26
- class AuthError < GitBak::Error; end
27
-
28
- # --
29
-
30
- # avaliable services
31
- SERVICES = %w{ bitbucket github gist }.map(&:to_sym)
32
-
33
- # use another service's authentication
34
- USE_AUTH = { gist: :github }
35
-
36
- # API urls
37
- APIS = {
38
- bitbucket: ->(user) { "api.bitbucket.org/1.0/users/#{user}" },
39
- github: ->(user) { "api.github.com/users/#{user}/repos" },
40
- gist: ->(user) { "api.github.com/users/#{user}/gists" },
41
- }
42
-
43
- # github pagination
44
- GH_PAGES = ->(data; l, n, r) {
45
- (l = data.meta['link']) &&
46
- (n = l.split(',').grep(/rel="next"/).first) &&
47
- (r = l.match(%r{<(https://[^>]*)>})) && r[1]
48
- }
49
-
50
- # pagination
51
- PAGES = { github: GH_PAGES, gist: GH_PAGES }
52
-
53
- # JSON pages concat
54
- JCAT = ->(pages) { pages.map { |x| JSON.load x } .flatten 1 }
55
-
56
- # remote urls
57
- REMOTES = # {{{1
58
- {
59
- bitbucket: {
60
- ssh: ->(u, r) { "git@bitbucket.org:#{u}/#{r}.git" },
61
- https: ->(u, r) { "https://bitbucket.org/#{u}/#{r}.git" },
62
- },
63
- github: {
64
- ssh: ->(u, r) { "git@github.com:#{u}/#{r}.git" },
65
- https: ->(u, r) { "https://github.com/#{u}/#{r}.git" },
66
- },
67
- gist: {
68
- ssh: ->(id) { "git@gist.github.com:#{id}.git" },
69
- https: ->(id) { "https://gist.github.com/#{id}.git" },
70
- },
71
- } # }}}1
72
-
73
- # long keyword^wsymbol ;-)
74
- AUTH = :http_basic_authentication
75
-
76
- # --
77
-
78
- # get data from API
79
- # @raise AuthError on 401
80
- def self.api_get_ (url, auth, service, user) # {{{1
81
- opts = auth ? { AUTH => [auth[:user], auth[:pass]] } : {}
82
-
83
- begin
84
- data = open url, opts
85
- rescue OpenURI::HTTPError => e
86
- if e.io.status[0] == '401'
87
- raise AuthError,
88
- "401 for #{auth[:user]} on #{service}/#{user}"
89
- else
90
- raise
91
- end
92
- end
93
-
94
- data
95
- end # }}}1
96
-
97
- # paginate
98
- def self.paginate (pag, url, &b)
99
- pages = []
100
- while url; pages << data = b[url]; url = pag[data]; end
101
- pages
102
- end
103
-
104
- # get data from API (w/ pagination if necessary); see api_get_
105
- # @return [String]
106
- # @return [<String>] if paginated
107
- def self.api_get (service, user, auth)
108
- get = ->(u) { api_get_ u, auth, service, user }
109
- url = "https://#{APIS[service][user]}"
110
- pag = PAGES[service]
111
- pag ? paginate(pag, url) { |u| get[u] } : get[url]
112
- end
113
-
114
- # get repositories from service; uses api_get if service in APIS,
115
- # api_get_<service> otherwise
116
- #
117
- # @return [<Hash>] [!{name:, remote:, description:},...]
118
- def self.repositories (service, cfg, auth)
119
- rem = REMOTES[service][cfg.fetch(:method, :ssh).to_sym]
120
- args = [service, cfg[:user], auth]
121
- data = APIS[service] ? api_get(*args) :
122
- send("api_get_#{service}", *args)
123
- send service, cfg, data, rem
124
- end
125
-
126
- # --
127
-
128
- # turn bitbucket API data into a list of repositories
129
- def self.bitbucket (cfg, data, rem)
130
- data_ = JSON.load data
131
- repos = data_['repositories'].select { |r| r['scm'] == 'git' }
132
- repos.map do |r|
133
- { name: r['name'], remote: rem[cfg[:user], r['name']],
134
- description: r['description'] }
135
- end
136
- end
137
-
138
- # turn github API data into a list of repositories
139
- def self.github (cfg, data, rem)
140
- JCAT[data].map do |r|
141
- { name: r['name'], remote: rem[cfg[:user], r['name']],
142
- description: r['description'] }
143
- end
144
- end
145
-
146
- # turn gist API data into a list of repositories
147
- def self.gist (cfg, data, rem)
148
- JCAT[data].map do |r|
149
- { name: r['id'], remote: rem[r['id']],
150
- description: r['description'] }
151
- end
152
- end
153
-
154
- end
155
- end
12
+ require 'gitbak/services/bitbucket'
13
+ require 'gitbak/services/gist'
14
+ require 'gitbak/services/github'
156
15
 
157
16
  # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,85 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : gitbak/services/bitbucket.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-02-19
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : GPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'gitbak'
13
+
14
+ module GitBak::Services
15
+
16
+ module Bitbucket
17
+
18
+ API = -> user { "https://bitbucket.org/api/2.0/repositories/#{user}" }
19
+ SSH = -> user, repo { "git@bitbucket.org:#{user}/#{repo}.git" }
20
+ HTTPS = -> user, repo { "https://bitbucket.org/#{user}/#{repo}.git" }
21
+
22
+ def self.to_json(data)
23
+ JSON.load data
24
+ end
25
+
26
+ def self.next_page(data)
27
+ data['next']
28
+ end
29
+
30
+ def self.cat_json_values(pages)
31
+ pages.map { |x| x['values'] } .flatten(1) .sort_by { |x| x['name'] }
32
+ end
33
+
34
+ def self.configure(cfg, dir, opts = {}) # {{{1
35
+ u = opts[:user] or raise 'no user' # TODO
36
+ c = cfg[:bitbucket] ||= []
37
+ a = if opts[:auth] == false
38
+ nil
39
+ else
40
+ p = GitBak::Misc.prompt "password for bitbucket/#{u}: ", true
41
+ { user: u, pass: p }
42
+ end
43
+ c << { dir: dir, user: u, auth: a, method: opts[:method] }
44
+ end # }}}1
45
+
46
+ def self.fetch(cfg, verbose) # {{{1
47
+ f = method :to_json; pag = method :next_page
48
+ join = method :cat_json_values
49
+ (cfg[:bitbucket] || []).each do |x|
50
+ meth = x[:method] == :https ? HTTPS : SSH
51
+ info = "bitbucket/#{x[:user]}"
52
+ print "fetching #{info} ..." if verbose
53
+ repos = GitBak.paginate API[x[:user]], nil, info, x[:auth], f,
54
+ pag, join
55
+ x[:repos] = repos.select { |r| r['scm'] == 'git' } .map do |r|
56
+ { name: r['name'], remote: meth[x[:user], r['name']],
57
+ description: r['description'] }
58
+ end
59
+ puts " #{repos.length} repositories" if verbose
60
+ end
61
+ end # }}}1
62
+
63
+ def self.mirror(cfg, verbose, noact) # {{{1
64
+ (cfg[:bitbucket] || []).each do |x|
65
+ x[:repos].each do |r|
66
+ GitBak.show_mirror 'bitbucket', x[:user], r[:name], r[:description]
67
+ GitBak.mirror_repo verbose, noact, r[:remote], x[:dir]
68
+ puts if verbose
69
+ end
70
+ end
71
+ end # }}}1
72
+
73
+ def self.summarise(cfg)
74
+ (cfg[:bitbucket] || []).each do |x|
75
+ GitBak.show_summary "bitbucket/#{x[:user]}", x[:repos].length
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+ GitBak::SERVICES[:bitbucket] = Bitbucket
82
+
83
+ end
84
+
85
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,80 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : gitbak/services/gist.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-02-19
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : GPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'gitbak'
13
+ require 'gitbak/services/github'
14
+
15
+ module GitBak::Services
16
+
17
+ module Gist
18
+
19
+ API = 'https://api.github.com/gists'
20
+ SSH = -> id { "git@gist.github.com:#{id}.git" }
21
+ HTTPS = -> id { "https://gist.github.com/#{id}.git" }
22
+
23
+ HEADERS = GitHub::HEADERS
24
+
25
+ def self.configure(cfg, dir, opts = {}) # {{{1
26
+ u = opts[:user] or raise 'no user' # TODO
27
+ c = cfg[:gist] ||= []
28
+ a = if opts[:auth] == false
29
+ nil
30
+ elsif opts[:auth] == :github
31
+ (cfg[:github].find { |x| x[:user] == u })[:auth]
32
+ elsif opts[:token]
33
+ t = GitBak::Misc.prompt "token for gist/#{u}: ", true
34
+ { user: t, pass: '' }
35
+ else
36
+ p = GitBak::Misc.prompt "password for gist/#{u}: ", true
37
+ { user: u, pass: p }
38
+ end
39
+ c << { dir: dir, user: u, auth: a, method: opts[:method] }
40
+ end # }}}1
41
+
42
+ def self.fetch(cfg, verbose) # {{{1
43
+ pag = GitHub.method :next_page; join = GitBak.method :cat_json
44
+ (cfg[:gist] || []).each do |x|
45
+ meth = x[:method] == :https ? HTTPS : SSH
46
+ info = "gist/#{x[:user]}"
47
+ print "fetching #{info} ..." if verbose
48
+ repos = GitBak.paginate API, HEADERS, info, x[:auth], nil,
49
+ pag, join
50
+ x[:repos] = repos.map do |r|
51
+ { name: r['id'], remote: meth[r['id']],
52
+ description: r['description'] }
53
+ end
54
+ puts " #{repos.length} repositories" if verbose
55
+ end
56
+ end # }}}1
57
+
58
+ def self.mirror(cfg, verbose, noact) # {{{1
59
+ (cfg[:gist] || []).each do |x|
60
+ x[:repos].each do |r|
61
+ GitBak.show_mirror 'gist', x[:user], r[:name], r[:description]
62
+ GitBak.mirror_repo verbose, noact, r[:remote], x[:dir]
63
+ puts if verbose
64
+ end
65
+ end
66
+ end # }}}1
67
+
68
+ def self.summarise(cfg)
69
+ (cfg[:gist] || []).each do |x|
70
+ GitBak.show_summary "gist/#{x[:user]}", x[:repos].length
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ GitBak::SERVICES[:gist] = Gist
77
+
78
+ end
79
+
80
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,83 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : gitbak/services/github.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-02-19
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : GPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'gitbak'
13
+
14
+ module GitBak::Services
15
+
16
+ module GitHub
17
+
18
+ API = 'https://api.github.com/user/repos?type=owner'
19
+ SSH = -> user, repo { "git@github.com:#{user}/#{repo}.git" }
20
+ HTTPS = -> user, repo { "https://github.com/#{user}/#{repo}.git" }
21
+
22
+ HEADERS = { 'Accept' => 'application/vnd.github.v3+json' }
23
+
24
+ def self.next_page(data)
25
+ (l = data.meta['link']) &&
26
+ (n = l.split(',').grep(/rel="next"/).first) &&
27
+ (r = l.match(%r{<(https://[^>]*)>})) && r[1]
28
+ end
29
+
30
+ def self.configure(cfg, dir, opts = {}) # {{{1
31
+ u = opts[:user] or raise 'no user' # TODO
32
+ c = cfg[:github] ||= []
33
+ a = if opts[:auth] == false
34
+ nil
35
+ elsif opts[:token]
36
+ t = GitBak::Misc.prompt "token for github/#{u}: ", true
37
+ { user: t, pass: '' }
38
+ else
39
+ p = GitBak::Misc.prompt "password for github/#{u}: ", true
40
+ { user: u, pass: p }
41
+ end
42
+ c << { dir: dir, user: u, auth: a, method: opts[:method] }
43
+ end # }}}1
44
+
45
+ def self.fetch(cfg, verbose) # {{{1
46
+ pag = method :next_page; join = GitBak.method :cat_json
47
+ (cfg[:github] || []).each do |x|
48
+ meth = x[:method] == :https ? HTTPS : SSH
49
+ info = "github/#{x[:user]}"
50
+ print "fetching #{info} ..." if verbose
51
+ repos = GitBak.paginate API, HEADERS, info, x[:auth], nil,
52
+ pag, join
53
+ x[:repos] = repos.map do |r|
54
+ { name: r['name'], remote: meth[x[:user], r['name']],
55
+ description: r['description'] }
56
+ end
57
+ puts " #{repos.length} repositories" if verbose
58
+ end
59
+ end # }}}1
60
+
61
+ def self.mirror(cfg, verbose, noact) # {{{1
62
+ (cfg[:github] || []).each do |x|
63
+ x[:repos].each do |r|
64
+ GitBak.show_mirror 'github', x[:user], r[:name], r[:description]
65
+ GitBak.mirror_repo verbose, noact, r[:remote], x[:dir]
66
+ puts if verbose
67
+ end
68
+ end
69
+ end # }}}1
70
+
71
+ def self.summarise(cfg)
72
+ (cfg[:github] || []).each do |x|
73
+ GitBak.show_summary "github/#{x[:user]}", x[:repos].length
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ GitBak::SERVICES[:github] = GitHub
80
+
81
+ end
82
+
83
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -1,7 +1,7 @@
1
1
  module GitBak
2
2
  # version and ...
3
- VERSION = '0.4.3'
3
+ VERSION = '0.5.0'
4
4
 
5
5
  # ... date (for gemspec) ;-)
6
- DATE = '2013-04-21'
6
+ DATE = '2014-02-19'
7
7
  end
metadata CHANGED
@@ -1,44 +1,25 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitbak
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
5
- prerelease:
4
+ version: 0.5.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Felix C. Stegerman
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-04-21 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: json
16
- requirement: &17607720 !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: *17607720
25
- description: ! 'GitBak mirrors Bitbucket/GitHub/Gist repositories; paths, users,
26
-
11
+ date: 2014-02-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ GitBak mirrors Bitbucket/GitHub/Gist repositories; paths, users,
27
15
  and authentication are specified in ~/.gitbak.
28
16
 
29
-
30
17
  When run, gitbak:
31
18
 
32
-
33
- * asks for unspecified passwords;
34
-
19
+ * asks for passwords;
35
20
  * lists repositories using APIs - authenticating if necessary;
36
-
37
21
  * clones/updates repositories;
38
-
39
22
  * shows a summary (if verbose)
40
-
41
- '
42
23
  email:
43
24
  - flx@obfusk.net
44
25
  executables:
@@ -46,41 +27,40 @@ executables:
46
27
  extensions: []
47
28
  extra_rdoc_files: []
48
29
  files:
49
- - .yardopts
30
+ - ".yardopts"
50
31
  - README.md
51
- - bin/gitbak
52
32
  - gitbak.gemspec
53
- - lib/gitbak.rb
54
- - lib/gitbak/version.rb
55
33
  - lib/gitbak/misc.rb
56
- - lib/gitbak/eval.rb
57
- - lib/gitbak/config.rb
34
+ - lib/gitbak/services/bitbucket.rb
35
+ - lib/gitbak/services/gist.rb
36
+ - lib/gitbak/services/github.rb
58
37
  - lib/gitbak/services.rb
38
+ - lib/gitbak/version.rb
59
39
  - lib/gitbak/exec.rb
40
+ - lib/gitbak.rb
41
+ - bin/gitbak
60
42
  homepage: https://github.com/obfusk/gitbak
61
43
  licenses:
62
- - GPLv2
44
+ - GPLv3+
45
+ metadata: {}
63
46
  post_install_message:
64
47
  rdoc_options: []
65
48
  require_paths:
66
49
  - lib
67
50
  required_ruby_version: !ruby/object:Gem::Requirement
68
- none: false
69
51
  requirements:
70
- - - ! '>='
52
+ - - ">="
71
53
  - !ruby/object:Gem::Version
72
54
  version: 1.9.1
73
55
  required_rubygems_version: !ruby/object:Gem::Requirement
74
- none: false
75
56
  requirements:
76
- - - ! '>='
57
+ - - ">="
77
58
  - !ruby/object:Gem::Version
78
59
  version: '0'
79
60
  requirements: []
80
61
  rubyforge_project:
81
- rubygems_version: 1.8.11
62
+ rubygems_version: 2.0.14
82
63
  signing_key:
83
- specification_version: 3
64
+ specification_version: 4
84
65
  summary: bitbucket/github/gist backup
85
66
  test_files: []
86
- has_rdoc:
@@ -1,112 +0,0 @@
1
- # -- ; {{{1
2
- #
3
- # File : gitbak/config.rb
4
- # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
- # Date : 2013-01-03
6
- #
7
- # Copyright : Copyright (C) 2013 Felix C. Stegerman
8
- # Licence : GPLv2
9
- #
10
- # -- ; }}}1
11
-
12
- require 'gitbak/eval'
13
- require 'gitbak/misc'
14
- require 'gitbak/services'
15
-
16
- # --
17
-
18
- # gitbak namespace
19
- module GitBak
20
-
21
- # configuration
22
- module Config # {{{1
23
-
24
- # configuration error
25
- class ConfigError < GitBak::Error; end
26
-
27
- # --
28
-
29
- # configuration base class
30
- class ServiceCfg # {{{2
31
- # data
32
- attr_reader :_data
33
-
34
- # init
35
- def initialize
36
- @_data = {}
37
- end
38
-
39
- # pass on to _service or super
40
- def method_missing (meth, *args, &block)
41
- if GitBak::Services::SERVICES.include? meth
42
- _service meth, *args
43
- else
44
- super
45
- end
46
- end
47
- end # }}}2
48
-
49
- # authentication configuration
50
- class AuthCfg < ServiceCfg # {{{2
51
- # set service auth
52
- def _service (name, user, pass = nil)
53
- (@_data[name] ||= {})[user] = { user: user, pass: pass }
54
- end
55
- end # }}}2
56
-
57
- # repository configuration
58
- class ReposCfg < ServiceCfg # {{{2
59
- # set service repo
60
- def _service (name, dir, user, opts = {})
61
- c = opts.merge dir: dir, user: user
62
- c[:auth] = c[:user] if c[:auth] == true
63
- (@_data[name] ||= []) << c
64
- end
65
- end # }}}2
66
-
67
- # authentication and repository configuration
68
- class Cfg # {{{2
69
- # data
70
- attr_reader :auth, :repos
71
-
72
- # init
73
- def initialize
74
- @auth = AuthCfg.new
75
- @repos = ReposCfg.new
76
- end
77
-
78
- # get data
79
- def data # {{{3
80
- auth = @auth._data
81
- repos = @repos._data
82
-
83
- { auth: auth, repos: repos }
84
- end # }}}3
85
- end # }}}2
86
-
87
- # --
88
-
89
- # load configuration file
90
- # @raise ConfigError unless file returns Cfg
91
- def self.load (file) # {{{2
92
- cfg = eval File.read(file), GitBak::Eval.new.binding, file # ???
93
-
94
- raise ConfigError, "[#{file}] isn't a GitBak::Config::Cfg " \
95
- "(#{cfg.class} instead)." \
96
- unless Cfg === cfg
97
-
98
- cfg
99
- end # }}}2
100
-
101
- end # }}}1
102
-
103
- # configure
104
- def self.configure (&block)
105
- cfg = Config::Cfg.new
106
- block[cfg.auth, cfg.repos]
107
- cfg
108
- end
109
-
110
- end
111
-
112
- # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -1,6 +0,0 @@
1
- module GitBak
2
- # evaluation wrapper
3
- class Eval
4
- public :binding
5
- end
6
- end