bitbucket_migration 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/Gemfile +8 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +66 -0
- data/Rakefile +16 -0
- data/bin/bitbucket_migration +6 -0
- data/bitbucket_migration.gemspec +32 -0
- data/lib/bitbucket_migration.rb +81 -0
- data/lib/bitbucket_migration/api.rb +195 -0
- data/lib/bitbucket_migration/configuration.rb +46 -0
- data/lib/bitbucket_migration/csv_import.rb +22 -0
- data/lib/bitbucket_migration/git_interface.rb +56 -0
- data/lib/bitbucket_migration/git_repository.rb +34 -0
- data/lib/bitbucket_migration/git_workdir.rb +27 -0
- data/lib/bitbucket_migration/version.rb +3 -0
- data/spec/data/bitbucket_response.json +125 -0
- data/spec/data/source_repositories.csv +1 -0
- data/spec/lib/bitbucket_migration_spec.rb +176 -0
- data/spec/spec_helper.rb +9 -0
- metadata +197 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 94c0e33c0d8ed11a0a306ef5058dcfb7e92f9746
|
4
|
+
data.tar.gz: fa256909741093fb7dc167a3fa8b8c934a22d0cc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c53a3c118fa3eb2b55d303b63aacc6c583ac8abc3218090921f3d71610ef31af888404cb4615c67ff7e2f78d34be9988f627f4aa33d2271a111a97469df64913
|
7
|
+
data.tar.gz: 5d8148fb75b0e6b0efd58ef147ab7095ca8ef8c6b46b4dc615fa7cf55c2429b675127cd988a6324007a050b5b0b4febb58a20983fb7098b677c98814cabb374a
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bin
|
4
|
+
.bundle
|
5
|
+
.config
|
6
|
+
.yardoc
|
7
|
+
Gemfile.lock
|
8
|
+
InstalledFiles
|
9
|
+
_yardoc
|
10
|
+
coverage
|
11
|
+
doc/
|
12
|
+
lib/bundler/man
|
13
|
+
pkg
|
14
|
+
rdoc
|
15
|
+
spec/reports
|
16
|
+
test/tmp
|
17
|
+
test/version_tmp
|
18
|
+
tmp
|
19
|
+
config.yml
|
20
|
+
config_*.yml
|
21
|
+
.rake_tasks*
|
22
|
+
.DS_S*
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Denis Vazhenin
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# BitbucketMigration
|
2
|
+
|
3
|
+
Sequentially import repositories listed in csv file to bitbucket.
|
4
|
+
|
5
|
+
Note: this was created with goal to bulk import all existing repositories to bitbucket, i.e. it supposed to be one time task.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'bitbucket_migration'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install bitbucket_migration
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
In order to use this gem it is necessary to prepare the following:
|
24
|
+
|
25
|
+
YAML configuration file in the following format
|
26
|
+
|
27
|
+
username: %bitbucket username%
|
28
|
+
password: %password or API key (in case of team)%
|
29
|
+
team: %optionally, if importing to team%
|
30
|
+
|
31
|
+
Repository list in csv format, where values have to be in the following order
|
32
|
+
|
33
|
+
ssh link to repository, name repository will be created in bitbucket, programming language
|
34
|
+
|
35
|
+
For example, if importing from some remote repository first line of csv file would look like below:
|
36
|
+
|
37
|
+
git@gitserver:proj/myrepo.git,myrepo,ruby
|
38
|
+
|
39
|
+
It is recommended to setup ssh access with private key authentication, thus it won't be necessary to
|
40
|
+
input password for every repository migration.
|
41
|
+
|
42
|
+
Lastly, starting migration to bitbucket:
|
43
|
+
|
44
|
+
$ bitbucket_migration -c config.yml -l list.csv
|
45
|
+
|
46
|
+
## Contributing (following git-flow model)
|
47
|
+
|
48
|
+
0. Acknowledge, that in its current state code and tests require severe refactoring
|
49
|
+
1. Fork it
|
50
|
+
2. Create your feature branch (`git checkout -b feature/my-new-feature`)
|
51
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
52
|
+
4. Push to the branch (`git push origin feature/my-new-feature`)
|
53
|
+
5. Create new Pull Request
|
54
|
+
|
55
|
+
## Running tests
|
56
|
+
|
57
|
+
1. Create configuration file and put it in `spec/data/config.yml`
|
58
|
+
2. Create csv file with valid repositories and put it in `spec/data/source_repositories.csv`
|
59
|
+
3. Run tests (`rake spec`)
|
60
|
+
4. Optionally to use guard to constantly execute tests, run (`guard`)
|
61
|
+
|
62
|
+
## Documentation
|
63
|
+
|
64
|
+
1. Clone it
|
65
|
+
2. Install required development dependencies (`bundle install`)
|
66
|
+
3. Generate documentation (`rake doc`)
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
require "yard"
|
4
|
+
|
5
|
+
# Default tasks for testing
|
6
|
+
RSpec::Core::RakeTask.new
|
7
|
+
task :default => :spec
|
8
|
+
task :test => :spec
|
9
|
+
|
10
|
+
# Documentation
|
11
|
+
YARD::Rake::YardocTask.new do |t|
|
12
|
+
t.files = ['lib/**/*.rb'] # optional
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Generate gem documentation (same as running 'rake yard')"
|
16
|
+
task :doc => :yard
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'bitbucket_migration/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "bitbucket_migration"
|
8
|
+
spec.version = BitbucketMigration::VERSION
|
9
|
+
spec.authors = ["Denis Vazhenin"]
|
10
|
+
spec.email = ["denis.vazhenin@me.com"]
|
11
|
+
spec.description = %q{Migrates existing git repositories to bitbucket.}
|
12
|
+
spec.summary = %q{Imports existing git repositories and exports them to bitbucket.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.required_ruby_version = '~> 2.0'
|
22
|
+
|
23
|
+
spec.add_runtime_dependency 'git'
|
24
|
+
spec.add_runtime_dependency 'rest-client'
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec"
|
28
|
+
spec.add_development_dependency "rspec-expectations"
|
29
|
+
spec.add_development_dependency "guard-rspec"
|
30
|
+
spec.add_development_dependency "ruby_gntp"
|
31
|
+
spec.add_development_dependency "yard"
|
32
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "bitbucket_migration/version"
|
2
|
+
require "bitbucket_migration/csv_import"
|
3
|
+
require "bitbucket_migration/configuration"
|
4
|
+
require "bitbucket_migration/git_repository"
|
5
|
+
require "bitbucket_migration/git_interface"
|
6
|
+
require "bitbucket_migration/git_workdir"
|
7
|
+
require "bitbucket_migration/api"
|
8
|
+
require 'optparse'
|
9
|
+
|
10
|
+
module BitbucketMigration
|
11
|
+
|
12
|
+
# Main method implementing command line interface
|
13
|
+
def self.run!(argv)
|
14
|
+
options = {}
|
15
|
+
opt_parser =OptionParser.new do |opts|
|
16
|
+
opts.banner = "Usage: bitbucket_migration [options]"
|
17
|
+
|
18
|
+
options[:config] = nil
|
19
|
+
opts.on("-c", "--config FILE", String, "Configuration file with bitbucket credentials") do |conf|
|
20
|
+
options[:config] = conf
|
21
|
+
end
|
22
|
+
|
23
|
+
options[:list] = nil
|
24
|
+
opts.on("-l", "--list FILE", String, "List file in CSV format with repository migration information") do |list|
|
25
|
+
options[:list] = list
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on("-h", "--help", "Show help") do |h|
|
29
|
+
options[:help] = h
|
30
|
+
puts opt_parser
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on("-v", "--version", "Show version") do |vers|
|
35
|
+
options[:version] = vers
|
36
|
+
puts BitbucketMigration::VERSION
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
begin
|
42
|
+
opt_parser.parse!(argv)
|
43
|
+
|
44
|
+
required = [:config, :list]
|
45
|
+
missing = required.select { |param| options[param].nil? }
|
46
|
+
if not missing.empty?
|
47
|
+
puts "Missing options: #{required.join(', ')}"
|
48
|
+
puts opt_parser
|
49
|
+
exit
|
50
|
+
end
|
51
|
+
|
52
|
+
begin
|
53
|
+
config =BitbucketMigration::Configuration.new(options[:config])
|
54
|
+
list =BitbucketMigration::CSVImport.new(options[:list])
|
55
|
+
bitbucket =BitbucketMigration::Api.new(config.username, config.password, config.team)
|
56
|
+
|
57
|
+
list.repositories.each do |src_repo|
|
58
|
+
# Print current migration
|
59
|
+
puts "Migrating repository #{src_repo.name}"
|
60
|
+
workdir =BitbucketMigration::WorkDir.new
|
61
|
+
target_repo =bitbucket.create_repository(src_repo.name, src_repo.language)
|
62
|
+
if (bitbucket.repository_exists?(src_repo.name))
|
63
|
+
gitinterface =BitbucketMigration::GitInterface.new(src_repo, target_repo)
|
64
|
+
gitinterface.init_src_repo(workdir.path)
|
65
|
+
gitinterface.init_target_repo
|
66
|
+
gitinterface.fetch_src_repo_all
|
67
|
+
gitinterface.push_target_repo_all
|
68
|
+
end
|
69
|
+
workdir.clean!
|
70
|
+
end
|
71
|
+
rescue
|
72
|
+
puts $!.to_s
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
|
76
|
+
puts $!.to_s
|
77
|
+
puts opt_parser
|
78
|
+
exit
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'rest-client'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module BitbucketMigration
|
6
|
+
class Api
|
7
|
+
attr :username, :password, :team, :endpoint
|
8
|
+
|
9
|
+
def initialize(username, password, team)
|
10
|
+
@username ||= username
|
11
|
+
@password ||= password
|
12
|
+
@team ||= team
|
13
|
+
@endpoint ||= Endpoint.new(@username, @password, @team)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get repository list #### TO-do rewrite to return array of Repository objects
|
17
|
+
def get_repositories
|
18
|
+
return JSON.parse(RestClient.get(@endpoint.repositories))
|
19
|
+
end
|
20
|
+
|
21
|
+
# Get information for specific repository
|
22
|
+
def get_repository(name)
|
23
|
+
raise ArgumentError.new("Unable to query with nil value for name") if name == nil
|
24
|
+
response = RestClient.get(@endpoint.repository(name))
|
25
|
+
if response.code == 200
|
26
|
+
return Repository.new(JSON.parse(response))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Check if repository with given name exists on the bitbucket side
|
31
|
+
def repository_exists?(name)
|
32
|
+
raise ArgumentError.new("Unable to query with nil value for name") if name == nil
|
33
|
+
begin
|
34
|
+
repository = JSON.parse(RestClient.get(@endpoint.repository(name)))
|
35
|
+
return repository['name'] == name ? true : false
|
36
|
+
rescue RestClient::ResourceNotFound
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create new repository
|
42
|
+
def create_repository(name, language=nil)
|
43
|
+
raise ArgumentError.new("Unable to create repository if name is nil") if name == nil
|
44
|
+
raise RuntimeError.new("Unable to create new repository over existing one") if self.repository_exists?(name)
|
45
|
+
payload = { 'scm' => 'git', 'name' => name, 'is_private' => true }
|
46
|
+
payload['language'] = language if language != nil
|
47
|
+
|
48
|
+
# Sending POST request with payload
|
49
|
+
response = RestClient.post(@endpoint.repository(name), payload.to_json, :content_type => :json, :accept => :json)
|
50
|
+
if response.code == 200 || response.code == 201
|
51
|
+
return self.get_repository(name)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Delete existing repository
|
56
|
+
def delete_repository(name)
|
57
|
+
raise ArgumentError.new("Unable to delete repository if name is nil") if name == nil
|
58
|
+
begin
|
59
|
+
response = RestClient.delete(@endpoint.repository(name))
|
60
|
+
case response.code
|
61
|
+
when 200
|
62
|
+
when 204
|
63
|
+
return true
|
64
|
+
else
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
rescue => e
|
68
|
+
e.response
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Handles URI generation used in requests to bitbucket
|
73
|
+
class Endpoint
|
74
|
+
attr_reader :userinfo, :host, :api, :version, :team, :keywords
|
75
|
+
|
76
|
+
def initialize(username, password, team)
|
77
|
+
self.set_userinfo(username, password)
|
78
|
+
self.set_keywords
|
79
|
+
@host ||="bitbucket.org"
|
80
|
+
@api ||="api"
|
81
|
+
@version ||="2.0"
|
82
|
+
@team ||=team !=nil ? team : username
|
83
|
+
end
|
84
|
+
|
85
|
+
# Set userinfo with string of username:password
|
86
|
+
def set_userinfo(username, password)
|
87
|
+
if (username == nil || password == nil)
|
88
|
+
raise ArgumentError.new("Unable to set userinfo with empty values")
|
89
|
+
end
|
90
|
+
@userinfo ||=[username, password].join(":")
|
91
|
+
end
|
92
|
+
|
93
|
+
# Set endpoint keywords supported in bitbucket
|
94
|
+
def set_keywords
|
95
|
+
keywords = {}
|
96
|
+
keywords[:repositories] = 'repositories'
|
97
|
+
keywords[:users] = 'users'
|
98
|
+
keywords[:teams] = 'teams'
|
99
|
+
@keywords ||= keywords
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns path for specified endpoint and tail(optional)
|
103
|
+
def path(endpoint, tail=nil)
|
104
|
+
if (endpoint == nil)
|
105
|
+
raise ArgumentError.new("Unable to set path with nil endpoint")
|
106
|
+
end
|
107
|
+
_path = "/"
|
108
|
+
tokens =[]
|
109
|
+
[@api, @version, endpoint, @team, tail].each do |token|
|
110
|
+
tokens.push(token) unless token.nil?
|
111
|
+
end
|
112
|
+
_path << tokens.join('/')
|
113
|
+
end
|
114
|
+
|
115
|
+
# Generates uri string from default values and given path parameter
|
116
|
+
def uri(path)
|
117
|
+
raise ArgumentError.new("Unable to generate uri with nil path") if path == nil
|
118
|
+
return URI::HTTPS.build(:userinfo => userinfo, :host => host, :path => path).to_s
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns URI for repositories
|
122
|
+
def repositories
|
123
|
+
return self.uri self.path(@keywords[:repositories])
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns URI for users
|
127
|
+
def users
|
128
|
+
return self.uri self.path(@keywords[:users])
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns URI for teams
|
132
|
+
def teams
|
133
|
+
return self.uri self.path(@keywords[:teams])
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns URI for specific repository
|
137
|
+
def repository(name)
|
138
|
+
return self.uri self.path(@keywords[:repositories], name)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class Repository
|
143
|
+
attr_reader :name, :scm, :ssh_href, :owner, :language
|
144
|
+
|
145
|
+
def name=(new_name)
|
146
|
+
if (new_name == nil)
|
147
|
+
raise ArgumentError.new("Repository name cannot be nil")
|
148
|
+
end
|
149
|
+
@name = new_name
|
150
|
+
end
|
151
|
+
|
152
|
+
def scm=(new_scm)
|
153
|
+
if (new_scm != nil && new_scm != 'git')
|
154
|
+
raise ArgumentError.new("Only git is supported")
|
155
|
+
else
|
156
|
+
@scm = 'git'
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def owner=(new_owner)
|
161
|
+
if (new_owner == nil)
|
162
|
+
raise ArgumentError.new("Repository owner cannot be nil")
|
163
|
+
end
|
164
|
+
@owner = new_owner
|
165
|
+
end
|
166
|
+
|
167
|
+
def language=(new_language)
|
168
|
+
if (new_language == nil)
|
169
|
+
raise ArgumentError.new("Language cannot be nil")
|
170
|
+
end
|
171
|
+
@language = new_language
|
172
|
+
end
|
173
|
+
|
174
|
+
def ssh_href=(links)
|
175
|
+
if (links == nil)
|
176
|
+
raise ArgumentError.new("Unable get link for git repository from nil value")
|
177
|
+
elsif (links && links.class == Array)
|
178
|
+
link =links.select { |l| l["name"] == "ssh" }.first
|
179
|
+
if (link["href"] == nil)
|
180
|
+
raise ArgumentError.new("Unable to find link for git repository")
|
181
|
+
end
|
182
|
+
end
|
183
|
+
@ssh_href =link['href']
|
184
|
+
end
|
185
|
+
|
186
|
+
def initialize(data)
|
187
|
+
self.name =data['name']
|
188
|
+
self.scm =data['scm']
|
189
|
+
self.ssh_href =data['links']['clone']
|
190
|
+
self.owner =data['owner']['username']
|
191
|
+
self.language =data['language']
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module BitbucketMigration
|
4
|
+
class Configuration
|
5
|
+
DEFAULT_CONFIG='config.yml'
|
6
|
+
|
7
|
+
attr_reader :file, :username, :password, :team
|
8
|
+
|
9
|
+
def initialize(configfile)
|
10
|
+
config = read(configfile)
|
11
|
+
|
12
|
+
if(valid?(config))
|
13
|
+
@file = configfile
|
14
|
+
@username =config['username']
|
15
|
+
@password =config['password']
|
16
|
+
@team =config['team']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Read configuration file
|
21
|
+
def read(configfile=nil)
|
22
|
+
file = configfile ? configfile : DEFAULT_CONFIG
|
23
|
+
begin
|
24
|
+
conf = YAML::load_file(file)
|
25
|
+
return conf
|
26
|
+
rescue
|
27
|
+
puts "[Error] " + $!.to_s
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Check if configuration file has required keys and values
|
33
|
+
def valid?(yml)
|
34
|
+
expected = ['username','password','team']
|
35
|
+
|
36
|
+
# check if required keys present
|
37
|
+
valid_keys = expected.all? { |e| yml.keys.include?(e) }
|
38
|
+
|
39
|
+
# check if values exists and not nil
|
40
|
+
valid_values = yml.all? { |k,v| v.nil? || v.empty? || v.size == 0 }
|
41
|
+
|
42
|
+
return valid_keys || valid_values ? true : false
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module BitbucketMigration
|
4
|
+
class CSVImport
|
5
|
+
attr :repositories
|
6
|
+
|
7
|
+
# Holds list of source repositories loaded from the csv file
|
8
|
+
def initialize(csvfile)
|
9
|
+
@repositories ||=[]
|
10
|
+
raise ArgumentError, "Argument cannot be empty" unless !csvfile.nil?
|
11
|
+
|
12
|
+
file = CSV.open(csvfile)
|
13
|
+
file.each_with_index do |item|
|
14
|
+
url =item.shift
|
15
|
+
name =item.shift
|
16
|
+
language =item.shift
|
17
|
+
|
18
|
+
repositories << GitRepository.new(url, name, language)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module BitbucketMigration
|
4
|
+
class GitInterface
|
5
|
+
attr_reader :git, :src_repo, :target_repo, :remotes
|
6
|
+
|
7
|
+
def src_repo=(src_repo)
|
8
|
+
if (src_repo == nil)
|
9
|
+
raise ArgumentError.new("Source repository value cannot be nil")
|
10
|
+
end
|
11
|
+
@src_repo = src_repo
|
12
|
+
end
|
13
|
+
|
14
|
+
def target_repo=(target_repo)
|
15
|
+
if (target_repo == nil)
|
16
|
+
raise ArgumentError.new("Cannot set target repository to nil")
|
17
|
+
end
|
18
|
+
@target_repo = target_repo
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(src_repo, target_repo)
|
22
|
+
self.src_repo =src_repo
|
23
|
+
self.target_repo =target_repo
|
24
|
+
@remotes ||= {:default => 'origin', :bitbucket => 'bitbucket'}.freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
# Clones source repository to the temporary working directory
|
28
|
+
def init_src_repo(workdir)
|
29
|
+
if (workdir == nil or workdir.size == 0)
|
30
|
+
raise ArgumentError.new("Unable to continue without working directory")
|
31
|
+
else
|
32
|
+
@git = Git.clone(@src_repo.url, @src_repo.name, :path => workdir)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Add remote reference to target repository ( in bitbucket )
|
37
|
+
def init_target_repo
|
38
|
+
@git.add_remote(@remotes[:bitbucket], @target_repo.ssh_href)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Fetches all related remote branches and tags for source repository
|
42
|
+
def fetch_src_repo_all
|
43
|
+
@git.fetch
|
44
|
+
@git.branches.remote.each do |rb|
|
45
|
+
@git.checkout(rb.name) if !rb.name.match('^HEAD')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Push all related branches and tags to target repository
|
50
|
+
def push_target_repo_all
|
51
|
+
@git.branches.local.each do |lb|
|
52
|
+
@git.push(@remotes[:bitbucket], lb.name, {:tags => true})
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module BitbucketMigration
|
2
|
+
class GitRepository
|
3
|
+
attr_reader :url, :name, :language
|
4
|
+
|
5
|
+
def url=(url)
|
6
|
+
if (url == nil or url.size == 0)
|
7
|
+
raise ArgumentError.new("Repository URL cannot be empty")
|
8
|
+
end
|
9
|
+
@url = url
|
10
|
+
end
|
11
|
+
|
12
|
+
def name=(name)
|
13
|
+
if (name == nil or name.size == 0)
|
14
|
+
raise ArgumentError.new("Repository Name cannot be empty")
|
15
|
+
end
|
16
|
+
@name = name
|
17
|
+
end
|
18
|
+
|
19
|
+
def language=(language)
|
20
|
+
if (language == nil or language.size == 0)
|
21
|
+
@language = 'other'
|
22
|
+
else
|
23
|
+
@language = language
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Holds values for specific git source repository
|
28
|
+
def initialize(url, name, language)
|
29
|
+
self.url =url
|
30
|
+
self.name =name
|
31
|
+
self.language =language
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
|
3
|
+
module BitbucketMigration
|
4
|
+
class WorkDir
|
5
|
+
attr_reader :path
|
6
|
+
|
7
|
+
# Used to create temporary directory to store source git repository
|
8
|
+
def initialize
|
9
|
+
dir = Dir.mktmpdir
|
10
|
+
|
11
|
+
if Dir.exists?(dir)
|
12
|
+
@path = dir
|
13
|
+
else
|
14
|
+
raise RuntimeError.new("Unable to create temporary directory")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Cleanup method to force removal of temporary directory
|
19
|
+
# with all its contents
|
20
|
+
def clean!
|
21
|
+
FileUtils.remove_entry_secure @path
|
22
|
+
@path = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :clean, :clean!
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
{
|
2
|
+
"pagelen": 10,
|
3
|
+
"values": [
|
4
|
+
{
|
5
|
+
"scm": "git",
|
6
|
+
"has_wiki": false,
|
7
|
+
"description": "Creative Factory Seminar 2013 Class Materials",
|
8
|
+
"links": {
|
9
|
+
"watchers": {
|
10
|
+
"href": "https://bitbucket.org/!api/2.0/repositories/denis_vazhenin/cfs2013/watchers"
|
11
|
+
},
|
12
|
+
"commits": {
|
13
|
+
"href": "https://bitbucket.org/!api/2.0/repositories/denis_vazhenin/cfs2013/commits"
|
14
|
+
},
|
15
|
+
"self": {
|
16
|
+
"href": "https://bitbucket.org/!api/2.0/repositories/denis_vazhenin/cfs2013"
|
17
|
+
},
|
18
|
+
"html": {
|
19
|
+
"href": "https://bitbucket.org/denis_vazhenin/cfs2013"
|
20
|
+
},
|
21
|
+
"avatar": {
|
22
|
+
"href": "https://d3oaxc4q5k2d6q.cloudfront.net/m/e0f0cee6c478/img/language-avatars/java_16.png"
|
23
|
+
},
|
24
|
+
"forks": {
|
25
|
+
"href": "https://bitbucket.org/!api/2.0/repositories/denis_vazhenin/cfs2013/forks"
|
26
|
+
},
|
27
|
+
"clone": [
|
28
|
+
{
|
29
|
+
"href": "https://bitbucket.org/denis_vazhenin/cfs2013.git",
|
30
|
+
"name": "https"
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"href": "ssh://git@bitbucket.org/denis_vazhenin/cfs2013.git",
|
34
|
+
"name": "ssh"
|
35
|
+
}
|
36
|
+
],
|
37
|
+
"pullrequests": {
|
38
|
+
"href": "https://bitbucket.org/!api/2.0/repositories/denis_vazhenin/cfs2013/pullrequests"
|
39
|
+
}
|
40
|
+
},
|
41
|
+
"fork_policy": "allow_forks",
|
42
|
+
"language": "java",
|
43
|
+
"created_on": "2013-12-03T02:31:38.549442+00:00",
|
44
|
+
"full_name": "denis_vazhenin/cfs2013",
|
45
|
+
"has_issues": false,
|
46
|
+
"owner": {
|
47
|
+
"username": "denis_vazhenin",
|
48
|
+
"display_name": "Denis Vazhenin",
|
49
|
+
"links": {
|
50
|
+
"self": {
|
51
|
+
"href": "https://bitbucket.org/!api/2.0/users/denis_vazhenin"
|
52
|
+
},
|
53
|
+
"avatar": {
|
54
|
+
"href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Oct/15/denis_vazhenin-avatar-3797905098-10_avatar.png"
|
55
|
+
}
|
56
|
+
}
|
57
|
+
},
|
58
|
+
"updated_on": "2014-03-02T14:45:01.413396+00:00",
|
59
|
+
"size": 279877,
|
60
|
+
"is_private": false,
|
61
|
+
"name": "cfs2013"
|
62
|
+
},
|
63
|
+
{
|
64
|
+
"scm": "git",
|
65
|
+
"has_wiki": false,
|
66
|
+
"description": "",
|
67
|
+
"links": {
|
68
|
+
"watchers": {
|
69
|
+
"href": "https://bitbucket.org/!api/2.0/repositories/denis_vazhenin/domain_sync/watchers"
|
70
|
+
},
|
71
|
+
"commits": {
|
72
|
+
"href": "https://bitbucket.org/!api/2.0/repositories/denis_vazhenin/domain_sync/commits"
|
73
|
+
},
|
74
|
+
"self": {
|
75
|
+
"href": "https://bitbucket.org/!api/2.0/repositories/denis_vazhenin/domain_sync"
|
76
|
+
},
|
77
|
+
"html": {
|
78
|
+
"href": "https://bitbucket.org/denis_vazhenin/domain_sync"
|
79
|
+
},
|
80
|
+
"avatar": {
|
81
|
+
"href": "https://d3oaxc4q5k2d6q.cloudfront.net/m/e0f0cee6c478/img/language-avatars/ruby_16.png"
|
82
|
+
},
|
83
|
+
"forks": {
|
84
|
+
"href": "https://bitbucket.org/!api/2.0/repositories/denis_vazhenin/domain_sync/forks"
|
85
|
+
},
|
86
|
+
"clone": [
|
87
|
+
{
|
88
|
+
"href": "https://bitbucket.org/denis_vazhenin/domain_sync.git",
|
89
|
+
"name": "https"
|
90
|
+
},
|
91
|
+
{
|
92
|
+
"href": "ssh://git@bitbucket.org/denis_vazhenin/domain_sync.git",
|
93
|
+
"name": "ssh"
|
94
|
+
}
|
95
|
+
],
|
96
|
+
"pullrequests": {
|
97
|
+
"href": "https://bitbucket.org/!api/2.0/repositories/denis_vazhenin/domain_sync/pullrequests"
|
98
|
+
}
|
99
|
+
},
|
100
|
+
"fork_policy": "allow_forks",
|
101
|
+
"language": "ruby",
|
102
|
+
"created_on": "2013-12-16T12:35:39.710097+00:00",
|
103
|
+
"full_name": "denis_vazhenin/domain_sync",
|
104
|
+
"has_issues": false,
|
105
|
+
"owner": {
|
106
|
+
"username": "denis_vazhenin",
|
107
|
+
"display_name": "Denis Vazhenin",
|
108
|
+
"links": {
|
109
|
+
"self": {
|
110
|
+
"href": "https://bitbucket.org/!api/2.0/users/denis_vazhenin"
|
111
|
+
},
|
112
|
+
"avatar": {
|
113
|
+
"href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Oct/15/denis_vazhenin-avatar-3797905098-10_avatar.png"
|
114
|
+
}
|
115
|
+
}
|
116
|
+
},
|
117
|
+
"updated_on": "2014-03-02T14:44:09.779836+00:00",
|
118
|
+
"size": 164708,
|
119
|
+
"is_private": false,
|
120
|
+
"name": "domain_sync"
|
121
|
+
}
|
122
|
+
],
|
123
|
+
"page": 1,
|
124
|
+
"size": 2
|
125
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
ssh://git@bitbucket.org/denis_vazhenin/domain_sync.git,sync,ruby
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BitbucketMigration do
|
4
|
+
|
5
|
+
# Required static files
|
6
|
+
# Note: paths set relative to path of spec_helper
|
7
|
+
data =get_full_path('/data/source_repositories.csv')
|
8
|
+
configfile =get_full_path('data/config.yml')
|
9
|
+
bitbucket_response_sample = get_full_path('data/bitbucket_response.json')
|
10
|
+
|
11
|
+
it "should return current version number" do
|
12
|
+
version = BitbucketMigration::VERSION
|
13
|
+
version.should_not be_nil
|
14
|
+
version.split('.').size.should == 3
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should have valid model for local git repository" do
|
18
|
+
url = 'ssh://git@git.test:test.git'
|
19
|
+
name = 'test_repo'
|
20
|
+
language = 'ruby'
|
21
|
+
|
22
|
+
gitrepo = BitbucketMigration::GitRepository.new(url, name, language)
|
23
|
+
gitrepo.should_not be_nil
|
24
|
+
gitrepo.url.should == url
|
25
|
+
gitrepo.name.should == name
|
26
|
+
gitrepo.language.should == language
|
27
|
+
expect { gitrepo.url=nil }.to raise_error(ArgumentError)
|
28
|
+
expect { gitrepo.name=nil }.to raise_error(ArgumentError)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should read csv file and return array of GitRepository objects" do
|
32
|
+
csv =BitbucketMigration::CSVImport.new(data)
|
33
|
+
expect {BitbucketMigration::CSVImport.new(nil)}.to raise_error(ArgumentError)
|
34
|
+
csv.repositories.should_not be_nil
|
35
|
+
csv.repositories.size.should_not == 0
|
36
|
+
csv.repositories.class.should == Array
|
37
|
+
csv.repositories[rand(csv.repositories.size - 1)].class.should == BitbucketMigration::GitRepository
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should be possible to load configuration from file" do
|
41
|
+
config = BitbucketMigration::Configuration.new(configfile)
|
42
|
+
config.username.should_not be_nil
|
43
|
+
config.password.should_not be_nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should be possible to create and delete temporary working directory" do
|
47
|
+
workdir = BitbucketMigration::WorkDir.new
|
48
|
+
Dir.exists?(workdir.path).should == true
|
49
|
+
workdir.clean
|
50
|
+
workdir.path.should be_nil
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Bitbucket API related tests
|
55
|
+
#
|
56
|
+
it "should return valid endpoint for given parameters and requests" do
|
57
|
+
user = 'testuser'
|
58
|
+
pass = 'testpass'
|
59
|
+
team = 'testteam'
|
60
|
+
repo = 'testrepo'
|
61
|
+
endpoint = BitbucketMigration::Api::Endpoint.new(user, pass, team)
|
62
|
+
endpoint.repositories.should == "https://#{user}:#{pass}@bitbucket.org/api/2.0/repositories/#{team}"
|
63
|
+
endpoint.repository(repo).should == "https://#{user}:#{pass}@bitbucket.org/api/2.0/repositories/#{team}/#{repo}"
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should use username if team is nil" do
|
67
|
+
user = 'testuser'
|
68
|
+
pass = 'testpass'
|
69
|
+
team = nil
|
70
|
+
endpoint = BitbucketMigration::Api::Endpoint.new(user, pass, team)
|
71
|
+
endpoint.repositories.should == "https://#{user}:#{pass}@bitbucket.org/api/2.0/repositories/#{user}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Using local json file with content exactly matching response from bitbucket
|
75
|
+
it "should have valid model for bitbucket api repository" do
|
76
|
+
data_sample = JSON.load(load_file(bitbucket_response_sample))
|
77
|
+
sample_repo = BitbucketMigration::Api::Repository.new(data_sample['values'].last)
|
78
|
+
sample_repo.name.should == 'domain_sync'
|
79
|
+
sample_repo.scm.should == 'git'
|
80
|
+
sample_repo.ssh_href.should == 'ssh://git@bitbucket.org/denis_vazhenin/domain_sync.git'
|
81
|
+
sample_repo.owner.should == 'denis_vazhenin'
|
82
|
+
sample_repo.language.should == 'ruby'
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should be possible to get list of respositories in bitbucket" do
|
86
|
+
config = BitbucketMigration::Configuration.new(configfile)
|
87
|
+
bitbucket = BitbucketMigration::Api.new(config.username, config.password, config.team)
|
88
|
+
repos = bitbucket.get_repositories
|
89
|
+
repos.should_not be_nil
|
90
|
+
repos['values'].class.should == Array
|
91
|
+
repos['values'].size.should > 0
|
92
|
+
end
|
93
|
+
|
94
|
+
# Assuming repository doesn't exists
|
95
|
+
it "should be possible to check if repository doesn't exists in the bitbucket" do
|
96
|
+
csv =BitbucketMigration::CSVImport.new(data)
|
97
|
+
repo =csv.repositories.first
|
98
|
+
config =BitbucketMigration::Configuration.new(configfile)
|
99
|
+
bitbucket =BitbucketMigration::Api.new(config.username, config.password, config.team)
|
100
|
+
bitbucket.repository_exists?(repo.name).should == false
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Below tests should be executed in order they appear, otherwise it might lead to strange outcome
|
105
|
+
#
|
106
|
+
|
107
|
+
it "should be possible to create repository in bitbucket" do
|
108
|
+
csv =BitbucketMigration::CSVImport.new(data)
|
109
|
+
repo =csv.repositories.first
|
110
|
+
config =BitbucketMigration::Configuration.new(configfile)
|
111
|
+
bitbucket =BitbucketMigration::Api.new(config.username, config.password, config.team)
|
112
|
+
new_repo =bitbucket.create_repository(repo.name, repo.language)
|
113
|
+
new_repo.class.should ==BitbucketMigration::Api::Repository
|
114
|
+
repo.name.should ==new_repo.name
|
115
|
+
bitbucket.repository_exists?(repo.name).should == true
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should be possible to clone git repository" do
|
119
|
+
csv =BitbucketMigration::CSVImport.new(data)
|
120
|
+
config =BitbucketMigration::Configuration.new(configfile)
|
121
|
+
src_repo =csv.repositories.first
|
122
|
+
workdir =BitbucketMigration::WorkDir.new
|
123
|
+
bitbucket =BitbucketMigration::Api.new(config.username, config.password, config.team)
|
124
|
+
target_repo =bitbucket.get_repository(src_repo.name)
|
125
|
+
@gitinterface =BitbucketMigration::GitInterface.new(src_repo, target_repo)
|
126
|
+
@gitinterface.init_src_repo(workdir.path)
|
127
|
+
@gitinterface.git.should_not be_nil
|
128
|
+
|
129
|
+
workdir.clean!
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should be possible to fetch all existing branches of repository" do
|
133
|
+
csv =BitbucketMigration::CSVImport.new(data)
|
134
|
+
config =BitbucketMigration::Configuration.new(configfile)
|
135
|
+
src_repo =csv.repositories.first
|
136
|
+
workdir =BitbucketMigration::WorkDir.new
|
137
|
+
bitbucket =BitbucketMigration::Api.new(config.username, config.password, config.team)
|
138
|
+
target_repo =bitbucket.get_repository(src_repo.name)
|
139
|
+
@gitinterface =BitbucketMigration::GitInterface.new(src_repo, target_repo)
|
140
|
+
@gitinterface.init_src_repo(workdir.path)
|
141
|
+
@gitinterface.fetch_src_repo_all
|
142
|
+
|
143
|
+
workdir.clean!
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should be possible to add remote repository reference and push code there" do
|
147
|
+
csv =BitbucketMigration::CSVImport.new(data)
|
148
|
+
config =BitbucketMigration::Configuration.new(configfile)
|
149
|
+
src_repo =csv.repositories.first
|
150
|
+
workdir =BitbucketMigration::WorkDir.new
|
151
|
+
bitbucket =BitbucketMigration::Api.new(config.username, config.password, config.team)
|
152
|
+
target_repo =bitbucket.get_repository(src_repo.name)
|
153
|
+
@gitinterface =BitbucketMigration::GitInterface.new(src_repo, target_repo)
|
154
|
+
@gitinterface.init_src_repo(workdir.path)
|
155
|
+
@gitinterface.init_target_repo
|
156
|
+
@gitinterface.fetch_src_repo_all
|
157
|
+
@gitinterface.push_target_repo_all
|
158
|
+
|
159
|
+
workdir.clean!
|
160
|
+
end
|
161
|
+
|
162
|
+
# Delete bitbucket repository
|
163
|
+
it "should be possible to delete repository in bitbucket" do
|
164
|
+
csv =BitbucketMigration::CSVImport.new(data)
|
165
|
+
repo =csv.repositories.first
|
166
|
+
config =BitbucketMigration::Configuration.new(configfile)
|
167
|
+
bitbucket =BitbucketMigration::Api.new(config.username, config.password, config.team)
|
168
|
+
if (bitbucket.repository_exists?(repo.name))
|
169
|
+
bitbucket.delete_repository(repo.name).should == true
|
170
|
+
bitbucket.repository_exists?(repo.name).should == false
|
171
|
+
else
|
172
|
+
bitbucket.delete_repository(repo.name).should == false
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bitbucket_migration
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Denis Vazhenin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: git
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rest-client
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec-expectations
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: guard-rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: ruby_gntp
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: yard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: Migrates existing git repositories to bitbucket.
|
140
|
+
email:
|
141
|
+
- denis.vazhenin@me.com
|
142
|
+
executables:
|
143
|
+
- bitbucket_migration
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- .gitignore
|
148
|
+
- .rspec
|
149
|
+
- Gemfile
|
150
|
+
- Guardfile
|
151
|
+
- LICENSE.txt
|
152
|
+
- README.md
|
153
|
+
- Rakefile
|
154
|
+
- bin/bitbucket_migration
|
155
|
+
- bitbucket_migration.gemspec
|
156
|
+
- lib/bitbucket_migration.rb
|
157
|
+
- lib/bitbucket_migration/api.rb
|
158
|
+
- lib/bitbucket_migration/configuration.rb
|
159
|
+
- lib/bitbucket_migration/csv_import.rb
|
160
|
+
- lib/bitbucket_migration/git_interface.rb
|
161
|
+
- lib/bitbucket_migration/git_repository.rb
|
162
|
+
- lib/bitbucket_migration/git_workdir.rb
|
163
|
+
- lib/bitbucket_migration/version.rb
|
164
|
+
- spec/data/bitbucket_response.json
|
165
|
+
- spec/data/source_repositories.csv
|
166
|
+
- spec/lib/bitbucket_migration_spec.rb
|
167
|
+
- spec/spec_helper.rb
|
168
|
+
homepage: ''
|
169
|
+
licenses:
|
170
|
+
- MIT
|
171
|
+
metadata: {}
|
172
|
+
post_install_message:
|
173
|
+
rdoc_options: []
|
174
|
+
require_paths:
|
175
|
+
- lib
|
176
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ~>
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '2.0'
|
181
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
182
|
+
requirements:
|
183
|
+
- - '>='
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: '0'
|
186
|
+
requirements: []
|
187
|
+
rubyforge_project:
|
188
|
+
rubygems_version: 2.0.14
|
189
|
+
signing_key:
|
190
|
+
specification_version: 4
|
191
|
+
summary: Imports existing git repositories and exports them to bitbucket.
|
192
|
+
test_files:
|
193
|
+
- spec/data/bitbucket_response.json
|
194
|
+
- spec/data/source_repositories.csv
|
195
|
+
- spec/lib/bitbucket_migration_spec.rb
|
196
|
+
- spec/spec_helper.rb
|
197
|
+
has_rdoc:
|