gitbak 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +62 -0
- data/bin/gitbak +118 -0
- data/lib/gitbak.rb +175 -0
- data/lib/gitbak/version.rb +4 -0
- 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 :
|
data/bin/gitbak
ADDED
@@ -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 :
|
data/lib/gitbak.rb
ADDED
@@ -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 :
|
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: []
|