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 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
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bitbucket_migration.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'ruby_gntp', :require => false unless RUBY_PLATFORM =~ /darwin/i
8
+ end
data/Guardfile ADDED
@@ -0,0 +1,6 @@
1
+ guard :rspec do
2
+ notification :gntp
3
+ watch(%r{^spec/.+_spec\.rb$})
4
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
5
+ watch('spec/spec_helper.rb') { "spec" }
6
+ end
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,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This is command line interface to bitbucket_migration
3
+
4
+ require 'bitbucket_migration'
5
+
6
+ BitbucketMigration.run!(ARGV)
@@ -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,3 @@
1
+ module BitbucketMigration
2
+ VERSION = "0.1.0"
3
+ 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
@@ -0,0 +1,9 @@
1
+ require 'bitbucket_migration'
2
+
3
+ def get_full_path(path)
4
+ return File.join(File.dirname(__FILE__), path)
5
+ end
6
+
7
+ def load_file(location)
8
+ return File.new(location, 'r')
9
+ end
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: