git2bit 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +76 -0
- data/LICENSE.txt +22 -0
- data/README.md +77 -0
- data/README.rdoc +19 -0
- data/Rakefile +61 -0
- data/bin/git2bit +127 -0
- data/features/git2bit.feature +13 -0
- data/features/step_definitions/git2bit_steps.rb +1 -0
- data/features/support/env.rb +16 -0
- data/git2bit.gemspec +25 -0
- data/git2bit.rb +133 -0
- data/lib/git2bit/bitbucket.rb +140 -0
- data/lib/git2bit/github.rb +50 -0
- data/lib/git2bit/version.rb +3 -0
- data/lib/git2bit.rb +3 -0
- data/test/tc_something.rb +7 -0
- metadata +172 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
git2bit (1.0.1)
|
5
|
+
bitbucket_rest_api
|
6
|
+
github_api
|
7
|
+
methadone (~> 1.2.4)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
aruba (0.5.1)
|
13
|
+
childprocess (~> 0.3.6)
|
14
|
+
cucumber (>= 1.1.1)
|
15
|
+
rspec-expectations (>= 2.7.0)
|
16
|
+
bitbucket_rest_api (0.1.1)
|
17
|
+
faraday (~> 0.8.1)
|
18
|
+
faraday_middleware (~> 0.8.1)
|
19
|
+
hashie (~> 1.2.0)
|
20
|
+
multi_json (~> 1.3)
|
21
|
+
nokogiri (~> 1.5.2)
|
22
|
+
simple_oauth
|
23
|
+
builder (3.1.4)
|
24
|
+
childprocess (0.3.6)
|
25
|
+
ffi (~> 1.0, >= 1.0.6)
|
26
|
+
cucumber (1.2.1)
|
27
|
+
builder (>= 2.1.2)
|
28
|
+
diff-lcs (>= 1.1.3)
|
29
|
+
gherkin (~> 2.11.0)
|
30
|
+
json (>= 1.4.6)
|
31
|
+
diff-lcs (1.1.3)
|
32
|
+
faraday (0.8.4)
|
33
|
+
multipart-post (~> 1.1)
|
34
|
+
faraday_middleware (0.8.8)
|
35
|
+
faraday (>= 0.7.4, < 0.9)
|
36
|
+
ffi (1.2.0)
|
37
|
+
gherkin (2.11.5)
|
38
|
+
json (>= 1.4.6)
|
39
|
+
github_api (0.8.6)
|
40
|
+
faraday (~> 0.8.1)
|
41
|
+
hashie (~> 1.2.0)
|
42
|
+
multi_json (~> 1.4)
|
43
|
+
nokogiri (~> 1.5.2)
|
44
|
+
oauth2
|
45
|
+
hashie (1.2.0)
|
46
|
+
httpauth (0.2.0)
|
47
|
+
json (1.7.5)
|
48
|
+
jwt (0.1.5)
|
49
|
+
multi_json (>= 1.0)
|
50
|
+
methadone (1.2.4)
|
51
|
+
bundler
|
52
|
+
multi_json (1.5.0)
|
53
|
+
multipart-post (1.1.5)
|
54
|
+
nokogiri (1.5.6)
|
55
|
+
oauth2 (0.8.0)
|
56
|
+
faraday (~> 0.8)
|
57
|
+
httpauth (~> 0.1)
|
58
|
+
jwt (~> 0.1.4)
|
59
|
+
multi_json (~> 1.0)
|
60
|
+
rack (~> 1.2)
|
61
|
+
rack (1.4.1)
|
62
|
+
rake (0.9.2.2)
|
63
|
+
rdoc (3.12)
|
64
|
+
json (~> 1.4)
|
65
|
+
rspec-expectations (2.12.1)
|
66
|
+
diff-lcs (~> 1.1.3)
|
67
|
+
simple_oauth (0.2.0)
|
68
|
+
|
69
|
+
PLATFORMS
|
70
|
+
ruby
|
71
|
+
|
72
|
+
DEPENDENCIES
|
73
|
+
aruba
|
74
|
+
git2bit!
|
75
|
+
rake (~> 0.9.2)
|
76
|
+
rdoc
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Joe Workman
|
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,77 @@
|
|
1
|
+
# git2bit
|
2
|
+
|
3
|
+
There are many people out there that have migrated their private repositories from Github to BitBucket. BitBucket has a goo utility that will clone your entire repository over, however, there is no way to migrate any existing issues that you have on Github.
|
4
|
+
|
5
|
+
git2bit is a CLI that solves this by migrating all your issues along with their comments and milestones from Github into your new BitBucket repo.
|
6
|
+
|
7
|
+
**Important Tip**: I recommend that you create a new blank repo in Bitbucket to run intial migration tests with. Once you are happy with the migration results, you can delete this repository and proceed with migrating issues to the production repo.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Install it via the gem command in terminal:
|
12
|
+
|
13
|
+
$ gem install git2bit
|
14
|
+
|
15
|
+
You can now execute it:
|
16
|
+
|
17
|
+
$ git2bit --help
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Here is an overview of the options available to git2bit along with some helpful examples. See below for how to set these values using a `.git2bit.rc` config file.
|
22
|
+
|
23
|
+
<pre>
|
24
|
+
Usage: git2bit [options]
|
25
|
+
|
26
|
+
Options:
|
27
|
+
-h, --help Show command line help
|
28
|
+
--version Show help/version info
|
29
|
+
--[no-]closed [Do not] migrate closed issues
|
30
|
+
--issue ISSUE_NUMBER Process only a single Github issue number
|
31
|
+
--guser USER Github username
|
32
|
+
--gpass PASSWORD Github password
|
33
|
+
--grepo REPO Github repo name
|
34
|
+
--buser USER BitBucket username
|
35
|
+
--bpass PASSWORD BitBucket password
|
36
|
+
--brepo REPO BitBucket repo name
|
37
|
+
--log-level LEVEL Set the logging level
|
38
|
+
(debug|info|warn|error|fatal)
|
39
|
+
(Default: info)
|
40
|
+
</pre>
|
41
|
+
|
42
|
+
The following examples assume that you have the following options configured in your `.git2bit.rc` file: guser, gpass, grepo, buser, bpass, brepo
|
43
|
+
|
44
|
+
Move *all* issues (open and closed):
|
45
|
+
|
46
|
+
$ git2bit
|
47
|
+
|
48
|
+
Move only *open* issues:
|
49
|
+
|
50
|
+
$ git2bit --no-close
|
51
|
+
|
52
|
+
Move a single issue:
|
53
|
+
|
54
|
+
$ git2bit -i 532
|
55
|
+
|
56
|
+
### .git2bit.rc
|
57
|
+
|
58
|
+
Instead of passing a ton of options via command line, you can setup the configuration values in a file named **.git2bit.rc**. This file should live in your home directory.
|
59
|
+
|
60
|
+
Here is a sample file to get you going.
|
61
|
+
|
62
|
+
guser: joeworkman
|
63
|
+
gpass: p@ssw0rd
|
64
|
+
grepo: myrepo
|
65
|
+
|
66
|
+
buser: joeworkman
|
67
|
+
bpass: p@ssw0rd
|
68
|
+
brepo: newrepo
|
69
|
+
|
70
|
+
|
71
|
+
## Contributing
|
72
|
+
|
73
|
+
1. Fork it
|
74
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
75
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
76
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
77
|
+
5. Create new Pull Request
|
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= git2bit - DESCRIBE YOUR GEM
|
2
|
+
|
3
|
+
Author:: YOUR NAME (YOUR EMAIL)
|
4
|
+
Copyright:: Copyright (c) 2013 YOUR NAME
|
5
|
+
|
6
|
+
|
7
|
+
DESCRIBE YOUR GEM HERE
|
8
|
+
|
9
|
+
== Links
|
10
|
+
|
11
|
+
* {Source on Github}[LINK TO GITHUB]
|
12
|
+
* RDoc[LINK TO RDOC.INFO]
|
13
|
+
|
14
|
+
== Install
|
15
|
+
|
16
|
+
== Examples
|
17
|
+
|
18
|
+
== Contributing
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
def dump_load_path
|
2
|
+
puts $LOAD_PATH.join("\n")
|
3
|
+
found = nil
|
4
|
+
$LOAD_PATH.each do |path|
|
5
|
+
if File.exists?(File.join(path,"rspec"))
|
6
|
+
puts "Found rspec in #{path}"
|
7
|
+
if File.exists?(File.join(path,"rspec","core"))
|
8
|
+
puts "Found core"
|
9
|
+
if File.exists?(File.join(path,"rspec","core","rake_task"))
|
10
|
+
puts "Found rake_task"
|
11
|
+
found = path
|
12
|
+
else
|
13
|
+
puts "!! no rake_task"
|
14
|
+
end
|
15
|
+
else
|
16
|
+
puts "!!! no core"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
if found.nil?
|
21
|
+
puts "Didn't find rspec/core/rake_task anywhere"
|
22
|
+
else
|
23
|
+
puts "Found in #{path}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
require 'bundler'
|
27
|
+
require 'rake/clean'
|
28
|
+
|
29
|
+
require 'rake/testtask'
|
30
|
+
|
31
|
+
require 'cucumber'
|
32
|
+
require 'cucumber/rake/task'
|
33
|
+
gem 'rdoc' # we need the installed RDoc gem, not the system one
|
34
|
+
require 'rdoc/task'
|
35
|
+
|
36
|
+
include Rake::DSL
|
37
|
+
|
38
|
+
Bundler::GemHelper.install_tasks
|
39
|
+
|
40
|
+
|
41
|
+
Rake::TestTask.new do |t|
|
42
|
+
t.pattern = 'test/tc_*.rb'
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
CUKE_RESULTS = 'results.html'
|
47
|
+
CLEAN << CUKE_RESULTS
|
48
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
49
|
+
t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format pretty --no-source -x"
|
50
|
+
t.fork = false
|
51
|
+
end
|
52
|
+
|
53
|
+
Rake::RDocTask.new do |rd|
|
54
|
+
|
55
|
+
rd.main = "README.rdoc"
|
56
|
+
|
57
|
+
rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
|
58
|
+
end
|
59
|
+
|
60
|
+
task :default => [:test,:features]
|
61
|
+
|
data/bin/git2bit
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
require 'optparse'
|
5
|
+
require 'methadone'
|
6
|
+
require 'git2bit'
|
7
|
+
|
8
|
+
class App
|
9
|
+
include Methadone::Main
|
10
|
+
include Methadone::CLILogging
|
11
|
+
|
12
|
+
main do
|
13
|
+
help_now!("'guser' is required") unless options[:guser]
|
14
|
+
help_now!("'gpass' is required") unless options[:gpass]
|
15
|
+
help_now!("'grepo' is required") unless options[:grepo]
|
16
|
+
|
17
|
+
help_now!("'buser' is required") unless options[:buser]
|
18
|
+
help_now!("'bpass' is required") unless options[:bpass]
|
19
|
+
help_now!("'brepo' is required") unless options[:brepo]
|
20
|
+
|
21
|
+
debug "Configuration Used: " + options.inspect
|
22
|
+
|
23
|
+
# Connect to Bitbucket and Github
|
24
|
+
bitbucket = Git2bit::BitbucketProxy.new({:user => options[:buser], :pass => options[:bpass],:repo => options[:brepo]})
|
25
|
+
github = Git2bit::GithubProxy.new({:user => options[:guser], :pass => options[:gpass], :repo => options[:grepo]})
|
26
|
+
|
27
|
+
if options[:issue]
|
28
|
+
info "Will only process Github issue ##{options[:issue]}"
|
29
|
+
else
|
30
|
+
if options[:closed]
|
31
|
+
info "Starting to process through ALL Github issues"
|
32
|
+
else
|
33
|
+
info "Starting to process through only the OPENED Github issues"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Start processing each issue in Github
|
38
|
+
github.each_issue(options[:closed]) { |issue|
|
39
|
+
|
40
|
+
# Go to the next iteration if the issue number does not match option provided
|
41
|
+
next if options[:issue] and options[:issue].to_i != issue.number
|
42
|
+
|
43
|
+
info "Processing Github issue ##{issue.number}"
|
44
|
+
|
45
|
+
if issue.milestone and !bitbucket.milestones.find_index(issue.milestone.title)
|
46
|
+
# If there is a milestone and it does not already exist, then create it
|
47
|
+
bitbucket.create_milestone(issue.milestone.title)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Analyze the Github labels in an attempt to find the BitBucket data we need
|
51
|
+
tags = bitbucket.analyze_labels issue.labels.map{|x| x.name}
|
52
|
+
|
53
|
+
if tags[:status].nil? or issue.state == 'closed'
|
54
|
+
# If a status was not defined in the github labels, then set to open/resolved
|
55
|
+
# If the Github state is closed, we want to ignore the labels and set the ticket to resolved
|
56
|
+
tags[:status] = issue.state == 'open' ? 'open' : 'resolved'
|
57
|
+
end
|
58
|
+
|
59
|
+
issue_date = DateTime.parse(issue.created_at)
|
60
|
+
issue_body = "_Github issue **##{issue.number}** created by **#{issue.user.login}** on #{issue_date.strftime('%F %T')}_\n\n#{issue[:body]}"
|
61
|
+
|
62
|
+
# Create the Bitbucket issue
|
63
|
+
new_issue = bitbucket.create_issue({
|
64
|
+
:title => issue.title,
|
65
|
+
:content => issue_body,
|
66
|
+
:responsible => issue.assignee,
|
67
|
+
:milestone => issue.milestone ? issue.milestone.title : nil,
|
68
|
+
:component => tags[:component],
|
69
|
+
:priority => tags[:priority],
|
70
|
+
:status => tags[:status],
|
71
|
+
:kind => tags[:kind]
|
72
|
+
})
|
73
|
+
|
74
|
+
# Add the Github labels as a comment so the data is not lost
|
75
|
+
bitbucket.create_comment new_issue, "**Github labels**: " + tags[:labels].join(', ')
|
76
|
+
|
77
|
+
# Process Comments if the Github issue has any
|
78
|
+
if issue.comments > 1
|
79
|
+
# Get the Github comments
|
80
|
+
comments = github.get_comments issue.number
|
81
|
+
|
82
|
+
comments.each do |comment|
|
83
|
+
comment_date = DateTime.parse(comment.created_at)
|
84
|
+
# Add the comment into the newly created bitbucket issue
|
85
|
+
bitbucket.create_comment new_issue, "_Github comment by **#{comment.user.login}** on #{comment_date.strftime('%F %T')}_\n\n#{comment[:body]}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
}
|
89
|
+
info "Processing Complete."
|
90
|
+
end
|
91
|
+
|
92
|
+
# Declare command-line interface here
|
93
|
+
version Git2bit::VERSION
|
94
|
+
|
95
|
+
description 'git2bit migrates issues with their comments and milestones from a github repo to a bitbucket repo'
|
96
|
+
|
97
|
+
on("--[no-]closed","[Do not] migrate closed issues")
|
98
|
+
|
99
|
+
on("--issue ISSUE_NUMBER","Process only a single Github issue number")
|
100
|
+
|
101
|
+
on("--guser USER","Github username")
|
102
|
+
on("--gpass PASSWORD","Github password")
|
103
|
+
on("--grepo REPO","Github repo name")
|
104
|
+
|
105
|
+
on("--buser USER","BitBucket username")
|
106
|
+
on("--bpass PASSWORD","BitBucket password")
|
107
|
+
on("--brepo REPO","BitBucket repo name")
|
108
|
+
|
109
|
+
use_log_level_option
|
110
|
+
defaults_from_config_file '.git2bit.rc'
|
111
|
+
|
112
|
+
go!
|
113
|
+
end
|
114
|
+
|
115
|
+
__END__
|
116
|
+
|
117
|
+
# Sample .git2bit.rc file
|
118
|
+
|
119
|
+
closed: true
|
120
|
+
|
121
|
+
guser: joeworkman
|
122
|
+
gpass: p@ssw0rd
|
123
|
+
grepo: myrepo
|
124
|
+
|
125
|
+
buser: joeworkman
|
126
|
+
bpass: p@ssw0rd
|
127
|
+
brepo: newrepo
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Feature: My bootstrapped app kinda works
|
2
|
+
In order to get going on coding my awesome app
|
3
|
+
I want to have aruba and cucumber setup
|
4
|
+
So I don't have to do it myself
|
5
|
+
|
6
|
+
Scenario: App just runs
|
7
|
+
When I get help for "git2bit"
|
8
|
+
Then the exit status should be 0
|
9
|
+
And the banner should be present
|
10
|
+
And the banner should document that this app takes options
|
11
|
+
And the following options should be documented:
|
12
|
+
|--version|
|
13
|
+
And the banner should document that this app takes no arguments
|
@@ -0,0 +1 @@
|
|
1
|
+
# Put your step definitions here
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'aruba/cucumber'
|
2
|
+
require 'methadone/cucumber'
|
3
|
+
|
4
|
+
ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|
5
|
+
LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
|
6
|
+
|
7
|
+
Before do
|
8
|
+
# Using "announce" causes massive warnings on 1.9.2
|
9
|
+
@puts = true
|
10
|
+
@original_rubylib = ENV['RUBYLIB']
|
11
|
+
ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
After do
|
15
|
+
ENV['RUBYLIB'] = @original_rubylib
|
16
|
+
end
|
data/git2bit.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'git2bit/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "git2bit"
|
8
|
+
gem.version = Git2bit::VERSION
|
9
|
+
gem.authors = ["Joe Workman"]
|
10
|
+
gem.email = ["joe at workmanmail.com"]
|
11
|
+
gem.description = %q{git2bit migrates issues with their comments and milestones from a github repo to a bitbucket repo}
|
12
|
+
gem.summary = %q{git2bit migrates issues with their comments and milestones from a github repo to a bitbucket repo}
|
13
|
+
gem.homepage = "https://bitbucket.org/joeworkman/git2bit"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
gem.add_development_dependency('rdoc')
|
20
|
+
gem.add_development_dependency('aruba')
|
21
|
+
gem.add_development_dependency('rake', '~> 0.9.2')
|
22
|
+
gem.add_dependency('methadone', '~> 1.2.4')
|
23
|
+
gem.add_dependency('github_api')
|
24
|
+
gem.add_dependency('bitbucket_rest_api')
|
25
|
+
end
|
data/git2bit.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'github_api'
|
3
|
+
require 'bitbucket_rest_api'
|
4
|
+
|
5
|
+
bb_owner = 'joeworkman'
|
6
|
+
bb_repo = 'testinggit2bit'
|
7
|
+
bb_user = 'joeworkman'
|
8
|
+
bb_pass = 'dopey09'
|
9
|
+
|
10
|
+
# Possible values from BitBucket
|
11
|
+
BB_STATUS = ['new','open','resolved','on hold','invalid','duplicate','wontfix']
|
12
|
+
BB_KINDS = ['bug','enhancement','proposal','task']
|
13
|
+
BB_PRIORITIES = ['trivial','minor','major','critical','blocker']
|
14
|
+
|
15
|
+
gh_repo = 'stacks'
|
16
|
+
gh_user = 'joeworkman'
|
17
|
+
gh_pass = 'dopey09'
|
18
|
+
|
19
|
+
|
20
|
+
def analyze_labels(labels,components)
|
21
|
+
# The tag values that we are going to try to discover
|
22
|
+
tags = { :component => nil, :priority => nil, :kind => nil, :status => nil, :labels => Array.new }
|
23
|
+
|
24
|
+
labels.each do |label|
|
25
|
+
# Store the label name
|
26
|
+
tags[:labels].push label.name
|
27
|
+
|
28
|
+
name = label.name.downcase
|
29
|
+
|
30
|
+
# Look for a priority in available fields in BB_PRIORITIES
|
31
|
+
if tags[:priority].nil? and BB_PRIORITIES.find_index(name)
|
32
|
+
tags[:priority] = name
|
33
|
+
next
|
34
|
+
end
|
35
|
+
|
36
|
+
# Look for a kind in available fields in BB_KINDS
|
37
|
+
if tags[:kind].nil? and BB_KINDS.find_index(name)
|
38
|
+
tags[:kind] = name
|
39
|
+
next
|
40
|
+
end
|
41
|
+
|
42
|
+
# Look for a status in available fields in BB_STATUS
|
43
|
+
if tags[:status].nil? and BB_STATUS.find_index(name)
|
44
|
+
tags[:status] = name
|
45
|
+
next
|
46
|
+
end
|
47
|
+
|
48
|
+
# Look for a component. Need to do case insensitive match on each known component.
|
49
|
+
if tags[:component].nil?
|
50
|
+
components.each do |c|
|
51
|
+
if c.match(/#{name}/i)
|
52
|
+
tags[:component] = c
|
53
|
+
break
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
tags
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# Connect to BitBucket
|
63
|
+
bitbucket = BitBucket.new :basic_auth => "#{bb_user}:#{bb_pass}", :user => bb_user, :repo => bb_repo
|
64
|
+
# Store Milestones to search upon later on
|
65
|
+
bb_milestones = Array.new
|
66
|
+
bitbucket.issues.milestones.list(bb_owner, bb_repo).each {|m| bb_milestones.push m.name}
|
67
|
+
# Store Components to search upon later on
|
68
|
+
bb_components = Array.new
|
69
|
+
bitbucket.issues.components.list(bb_owner, bb_repo).each {|c| bb_components.push c.name}
|
70
|
+
|
71
|
+
|
72
|
+
# Setup Github connection
|
73
|
+
github = Github.new :basic_auth => "#{gh_user}:#{gh_pass}"
|
74
|
+
# Get all open and closed issues
|
75
|
+
github_data = Array.new
|
76
|
+
github_data.push github.issues.list :filter => 'all', :state => 'open', :user => gh_user, :repo => gh_repo
|
77
|
+
github_data.push github.issues.list :filter => 'all', :state => 'closed', :user => gh_user, :repo => gh_repo
|
78
|
+
|
79
|
+
# Process through each page of github issues and then through each issue per page
|
80
|
+
github_data.each do |result_set|
|
81
|
+
result_set.each_page do |page|
|
82
|
+
page.each do |issue|
|
83
|
+
|
84
|
+
if issue.milestone and !bb_milestones.find_index(issue.milestone.title)
|
85
|
+
# If there is a milestone and it does not already exist, then create it
|
86
|
+
milestone = bitbucket.issues.milestones.create bb_owner, bb_repo, { :name => issue.milestone.title }
|
87
|
+
# Add new milestone to array so its not created again
|
88
|
+
bb_milestones.push milestone.name
|
89
|
+
end
|
90
|
+
|
91
|
+
# Analyze the Github labels in an attempt to find the BitBucket data we need
|
92
|
+
tags = analyze_labels issue.labels, bb_components
|
93
|
+
|
94
|
+
if tags[:status].nil? or issue.state == 'closed'
|
95
|
+
# If a status was not defined in the github labels, then set to open/resolved
|
96
|
+
# If the Github state is closed, we want to ignore the labels and set the ticket to resolved
|
97
|
+
tags[:status] = issue.state == 'open' ? 'open' : 'resolved'
|
98
|
+
end
|
99
|
+
|
100
|
+
issue_date = DateTime.parse(issue.created_at)
|
101
|
+
issue_body = "_Github issue **##{issue.number}** created by **#{issue.user.login}** on #{issue_date.strftime('%F %T')}_\n\n#{issue[:body]}"
|
102
|
+
|
103
|
+
# Create the Bitbucket issue
|
104
|
+
bb_issue = bitbucket.issues.create bb_owner, bb_repo, {
|
105
|
+
:title => issue.title,
|
106
|
+
:content => issue_body,
|
107
|
+
:responsible => issue.assignee,
|
108
|
+
:milestone => issue.milestone ? issue.milestone.title : nil,
|
109
|
+
:component => tags[:component],
|
110
|
+
:priority => tags[:priority],
|
111
|
+
:status => tags[:status],
|
112
|
+
:kind => tags[:kind]
|
113
|
+
}
|
114
|
+
|
115
|
+
labels = "**Github labels**: " + tags[:labels].join(', ')
|
116
|
+
bitbucket.issues.comments.create bb_owner, bb_repo, bb_issue.local_id, {:content => labels}
|
117
|
+
|
118
|
+
# Process Comments if the Github issue has any
|
119
|
+
if issue.comments > 1
|
120
|
+
# Get the Github comments
|
121
|
+
comments = github.issues.comments.all gh_user, gh_repo, issue_id: issue.number
|
122
|
+
|
123
|
+
comments.each do |comment|
|
124
|
+
comment_date = DateTime.parse(comment.created_at)
|
125
|
+
content = "_Github comment by **#{comment.user.login}** on #{comment_date.strftime('%F %T')}_\n\n#{comment[:body]}"
|
126
|
+
# Add the comment into the newly created bitbucket issue
|
127
|
+
bitbucket.issues.comments.create bb_owner, bb_repo, bb_issue.local_id, {:content => content}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
exit
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'bitbucket_rest_api'
|
3
|
+
require 'methadone'
|
4
|
+
|
5
|
+
module Git2bit
|
6
|
+
class BitbucketProxy
|
7
|
+
include Methadone::Main
|
8
|
+
include Methadone::CLILogging
|
9
|
+
|
10
|
+
attr_reader :components, :milestones
|
11
|
+
|
12
|
+
# Possible values from BitBucket
|
13
|
+
STATUS = ['new','open','resolved','on hold','invalid','duplicate','wontfix']
|
14
|
+
KINDS = ['bug','enhancement','proposal','task']
|
15
|
+
PRIORITIES = ['trivial','minor','major','critical','blocker']
|
16
|
+
|
17
|
+
def initialize(args)
|
18
|
+
@repo = args[:repo]
|
19
|
+
@user = args[:user]
|
20
|
+
@owner = args[:user]
|
21
|
+
|
22
|
+
# Connect to BitBucket
|
23
|
+
begin
|
24
|
+
@conn = BitBucket.new :basic_auth => "#{@user}:#{args[:pass]}", :user => @user, :repo => @repo
|
25
|
+
|
26
|
+
# Store Milestones to search upon later on
|
27
|
+
@milestones = Array.new
|
28
|
+
@conn.issues.milestones.list(@owner, @repo).each {|m| @milestones.push m.name}
|
29
|
+
debug "BitBucket Milestones:\n - " + @milestones.join("\n - ")
|
30
|
+
|
31
|
+
# Store Components to search upon later on
|
32
|
+
@components = Array.new
|
33
|
+
@conn.issues.components.list(@owner, @repo).each {|c| @components.push c.name}
|
34
|
+
debug "BitBucket Components:\n - " + @components.join("\n - ")
|
35
|
+
|
36
|
+
info "Successfully connected to BitBucket #{@user}/#{@repo}"
|
37
|
+
rescue Exception => e
|
38
|
+
error = ["Error: Unable to connect to BitBucket #{@user}/#{@repo}", e.message].push e.backtrace
|
39
|
+
exit_now!(error.join("\n"))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_milestone(title)
|
44
|
+
begin
|
45
|
+
# Create the milestone in BitBucket
|
46
|
+
milestone = @conn.issues.milestones.create @owner, @repo, { :name => title }
|
47
|
+
|
48
|
+
# Add it to known milestones
|
49
|
+
@milestones.push title
|
50
|
+
|
51
|
+
info "BitBucket: created milestone '#{title}'"
|
52
|
+
rescue Exception => e
|
53
|
+
error "Error: Unable to create to BitBucket milestone '#{title}' - " + e.message
|
54
|
+
debug e.backtrace.join("\n")
|
55
|
+
end
|
56
|
+
milestone
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_issue(args)
|
60
|
+
begin
|
61
|
+
# Create the Bitbucket issue
|
62
|
+
issue = @conn.issues.create @owner, @repo, {
|
63
|
+
:title => args[:title],
|
64
|
+
:content => args[:content],
|
65
|
+
:responsible => args[:responsible],
|
66
|
+
:milestone => args[:milestone],
|
67
|
+
:component => args[:component],
|
68
|
+
:priority => args[:priority],
|
69
|
+
:status => args[:status],
|
70
|
+
:kind => args[:kind]
|
71
|
+
}
|
72
|
+
info "BitBucket: created issue ##{issue.local_id} '#{args[:title]}'"
|
73
|
+
rescue Exception => e
|
74
|
+
error "Error: Unable to create to BitBucket issue '#{args[:title]}' - " + e.message
|
75
|
+
debug e.backtrace.join("\n")
|
76
|
+
end
|
77
|
+
issue.local_id
|
78
|
+
end
|
79
|
+
|
80
|
+
def create_comment(id,content)
|
81
|
+
begin
|
82
|
+
# Create the Bitbucket comment
|
83
|
+
comment = @conn.issues.comments.create @owner, @repo, id, {:content => content}
|
84
|
+
info "BitBucket: created comment for issue ##{id}"
|
85
|
+
rescue Exception => e
|
86
|
+
error "Error: Unable to create to BitBucket comment for issue ##{id} - " + e.message
|
87
|
+
debug e.backtrace.join("\n")
|
88
|
+
end
|
89
|
+
comment
|
90
|
+
end
|
91
|
+
|
92
|
+
def analyze_labels(labels)
|
93
|
+
debug "Analyzing labels: " + labels.inspect
|
94
|
+
|
95
|
+
# The tag values that we are going to try to discover
|
96
|
+
tags = { :component => nil, :priority => nil, :kind => nil, :status => nil, :labels => Array.new }
|
97
|
+
|
98
|
+
labels.each do |label|
|
99
|
+
# Store the label name
|
100
|
+
tags[:labels].push label
|
101
|
+
|
102
|
+
name = label.downcase
|
103
|
+
|
104
|
+
# Look for a priority in available fields in BB_PRIORITIES
|
105
|
+
if tags[:priority].nil? and PRIORITIES.find_index(name)
|
106
|
+
tags[:priority] = name
|
107
|
+
next
|
108
|
+
end
|
109
|
+
|
110
|
+
# Look for a kind in available fields in BB_KINDS
|
111
|
+
if tags[:kind].nil? and KINDS.find_index(name)
|
112
|
+
tags[:kind] = name
|
113
|
+
next
|
114
|
+
end
|
115
|
+
|
116
|
+
# Look for a status in available fields in BB_STATUS
|
117
|
+
if tags[:status].nil? and STATUS.find_index(name)
|
118
|
+
tags[:status] = name
|
119
|
+
next
|
120
|
+
end
|
121
|
+
|
122
|
+
# Look for a component. Need to do case insensitive match on each known component.
|
123
|
+
if tags[:component].nil?
|
124
|
+
components.each do |c|
|
125
|
+
if c.match(/#{name}/i)
|
126
|
+
tags[:component] = c
|
127
|
+
break
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
debug "Built Tags: " + tags.inspect
|
134
|
+
|
135
|
+
tags
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'github_api'
|
3
|
+
require 'methadone'
|
4
|
+
|
5
|
+
module Git2bit
|
6
|
+
class GithubProxy
|
7
|
+
include Methadone::Main
|
8
|
+
include Methadone::CLILogging
|
9
|
+
|
10
|
+
def initialize(args)
|
11
|
+
@repo = args[:repo]
|
12
|
+
@user = args[:user]
|
13
|
+
begin
|
14
|
+
@conn = Github.new :basic_auth => "#{@user}:#{args[:pass]}"
|
15
|
+
@conn.repos.list user: @user
|
16
|
+
info "Successfully connected to Github #{@user}/#{@repo}"
|
17
|
+
rescue Exception => e
|
18
|
+
error = ["Error: Unable to connect to Github #{@user}/#{@repo}", e.message].push e.backtrace
|
19
|
+
exit_now!(error.join("\n"))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def each_issue(process_closed = true)
|
24
|
+
data = Array.new
|
25
|
+
data.push @conn.issues.list :filter => 'all', :state => 'open', :user => @user, :repo => @repo
|
26
|
+
if process_closed
|
27
|
+
data.push @conn.issues.list :filter => 'all', :state => 'closed', :user => @user, :repo => @repo
|
28
|
+
end
|
29
|
+
|
30
|
+
data.each do |result_set|
|
31
|
+
result_set.each_page do |page|
|
32
|
+
page.each {|issue| yield(issue)}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_comments(issue_id)
|
38
|
+
begin
|
39
|
+
# Get Github comments for issue ID
|
40
|
+
comments = @conn.issues.comments.all @user, @repo, issue_id: issue_id
|
41
|
+
debug "Github: got comments for issue ##{issue_id}: " + comments.inspect
|
42
|
+
rescue Exception => e
|
43
|
+
error "Error: Unable to get comments for Github issue ##{issue_id} - " + e.message
|
44
|
+
debug e.backtrace.join("\n")
|
45
|
+
end
|
46
|
+
comments
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
data/lib/git2bit.rb
ADDED
metadata
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: git2bit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Joe Workman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rdoc
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: aruba
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.9.2
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.9.2
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: methadone
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.2.4
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.2.4
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: github_api
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: bitbucket_rest_api
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description: git2bit migrates issues with their comments and milestones from a github
|
111
|
+
repo to a bitbucket repo
|
112
|
+
email:
|
113
|
+
- joe at workmanmail.com
|
114
|
+
executables:
|
115
|
+
- git2bit
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- .gitignore
|
120
|
+
- Gemfile
|
121
|
+
- Gemfile.lock
|
122
|
+
- LICENSE.txt
|
123
|
+
- README.md
|
124
|
+
- README.rdoc
|
125
|
+
- Rakefile
|
126
|
+
- bin/git2bit
|
127
|
+
- features/git2bit.feature
|
128
|
+
- features/step_definitions/git2bit_steps.rb
|
129
|
+
- features/support/env.rb
|
130
|
+
- git2bit.gemspec
|
131
|
+
- git2bit.rb
|
132
|
+
- lib/git2bit.rb
|
133
|
+
- lib/git2bit/bitbucket.rb
|
134
|
+
- lib/git2bit/github.rb
|
135
|
+
- lib/git2bit/version.rb
|
136
|
+
- test/tc_something.rb
|
137
|
+
homepage: https://bitbucket.org/joeworkman/git2bit
|
138
|
+
licenses: []
|
139
|
+
post_install_message:
|
140
|
+
rdoc_options: []
|
141
|
+
require_paths:
|
142
|
+
- lib
|
143
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
144
|
+
none: false
|
145
|
+
requirements:
|
146
|
+
- - ! '>='
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
segments:
|
150
|
+
- 0
|
151
|
+
hash: -4585294509181792167
|
152
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
segments:
|
159
|
+
- 0
|
160
|
+
hash: -4585294509181792167
|
161
|
+
requirements: []
|
162
|
+
rubyforge_project:
|
163
|
+
rubygems_version: 1.8.24
|
164
|
+
signing_key:
|
165
|
+
specification_version: 3
|
166
|
+
summary: git2bit migrates issues with their comments and milestones from a github
|
167
|
+
repo to a bitbucket repo
|
168
|
+
test_files:
|
169
|
+
- features/git2bit.feature
|
170
|
+
- features/step_definitions/git2bit_steps.rb
|
171
|
+
- features/support/env.rb
|
172
|
+
- test/tc_something.rb
|