git_duplicator 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/.test.env.example +13 -0
- data/.travis.yml +21 -0
- data/.yardopts +5 -0
- data/Gemfile +21 -0
- data/Guardfile +5 -0
- data/LICENSE.md +16 -0
- data/README.md +70 -0
- data/Rakefile +10 -0
- data/git_duplicator.gemspec +23 -0
- data/lib/git_duplicator/duplicator.rb +66 -0
- data/lib/git_duplicator/errors.rb +11 -0
- data/lib/git_duplicator/helpers/authorization_header.rb +71 -0
- data/lib/git_duplicator/null_logger.rb +14 -0
- data/lib/git_duplicator/repositories.rb +3 -0
- data/lib/git_duplicator/repository/repository.rb +41 -0
- data/lib/git_duplicator/repository/service_repository.rb +29 -0
- data/lib/git_duplicator/services/bitbucket.rb +67 -0
- data/lib/git_duplicator/services/github.rb +63 -0
- data/lib/git_duplicator/services.rb +15 -0
- data/lib/git_duplicator/version.rb +15 -0
- data/lib/git_duplicator.rb +15 -0
- data/spec/git_duplicator/duplicator_spec.rb +82 -0
- data/spec/git_duplicator/git_duplicator_spec.rb +102 -0
- data/spec/git_duplicator/repository/repository_spec.rb +47 -0
- data/spec/git_duplicator/repository/service_repository_spec.rb +25 -0
- data/spec/git_duplicator/services/bitbucket_spec.rb +51 -0
- data/spec/git_duplicator/services/github_spec.rb +52 -0
- data/spec/git_duplicator/version_spec.rb +9 -0
- data/spec/helper.rb +33 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c56bf9a2478b996375f297102452f577a678625f
|
4
|
+
data.tar.gz: c9dcca68935f1f25245686697d9e1ee191811b33
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 43b7d1d5308dcfbe6b812166463282c7ad47f6d0dbcd87b550ff38dab6eefaf36c17b481210c09af1d775dde79c4a05f82b1a6168399ac06497815f058f2d41f
|
7
|
+
data.tar.gz: a21c7b71c3b1269b19be4593c646873f05ade3fd6cf4e1dda211ffc1586e507dec80480d9bdbf0fe637936d1c401d144415dd89651e49d077e7713a364529ea9
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
*.env
|
data/.rspec
ADDED
data/.test.env.example
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
bundler_args: "--without development"
|
2
|
+
language: ruby
|
3
|
+
rvm:
|
4
|
+
- 1.9.3
|
5
|
+
- 2.0.0
|
6
|
+
- 2.1
|
7
|
+
- rbx-2
|
8
|
+
- jruby-19mode
|
9
|
+
- ruby-head
|
10
|
+
- jruby-head
|
11
|
+
matrix:
|
12
|
+
include:
|
13
|
+
- rvm: jruby-19mode
|
14
|
+
env: JRUBY_OPTS="$JRUBY_OPTS --debug"
|
15
|
+
- rvm: jruby-head
|
16
|
+
env: JRUBY_OPTS="$JRUBY_OPTS --debug"
|
17
|
+
allow_failures:
|
18
|
+
- rvm: jruby-head
|
19
|
+
- rvm: rbx-2
|
20
|
+
- rvm: ruby-head
|
21
|
+
fast_finish: true
|
data/.yardopts
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'rake'
|
4
|
+
|
5
|
+
group :development do
|
6
|
+
gem 'pry'
|
7
|
+
end
|
8
|
+
|
9
|
+
group :test do
|
10
|
+
gem 'coveralls', '~> 0.7', :require => false
|
11
|
+
gem 'rspec', '>= 2.14'
|
12
|
+
gem 'rspec-nc'
|
13
|
+
gem 'rubocop', '>= 0.2', :platforms => [:ruby_19, :ruby_20, :ruby_21]
|
14
|
+
gem 'simplecov', '~> 0.7.1', :require => false
|
15
|
+
gem 'guard', '~> 2.6'
|
16
|
+
gem 'guard-rspec', '~> 4.2'
|
17
|
+
gem 'webmock', '~> 1.1'
|
18
|
+
gem 'dotenv'
|
19
|
+
end
|
20
|
+
|
21
|
+
gemspec
|
data/Guardfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Copyright (c) 2014 Khaled alHabache
|
2
|
+
|
3
|
+
LGPL-3.0 License
|
4
|
+
|
5
|
+
This library is free software; you can redistribute it and/or
|
6
|
+
modify it under the terms of the GNU Lesser General Public
|
7
|
+
License as published by the Free Software Foundation; either
|
8
|
+
version 3.0 of the License, or (at your option) any later version.
|
9
|
+
|
10
|
+
This library is distributed in the hope that it will be useful,
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
+
Lesser General Public License for more details.
|
14
|
+
|
15
|
+
You should have received a copy of the GNU Lesser General Public
|
16
|
+
License along with this library.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Git Duplicator
|
2
|
+
|
3
|
+
Duplicating git repositories without forking.
|
4
|
+
|
5
|
+
- [x] Duplicate any repository
|
6
|
+
- [x] Additional Github support
|
7
|
+
- [x] Additional Bibucket support
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'git_duplicator'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install git_duplicator
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### Basic usage
|
26
|
+
|
27
|
+
Duplicate a repository. It assumes that you have the destination repository initiated.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'git_duplicator'
|
31
|
+
from = GitDuplicator::Repository
|
32
|
+
.new('source repo name', 'source repo url')
|
33
|
+
to = GitDuplicator::Repository
|
34
|
+
.new('destination repo name', 'destination repo url')
|
35
|
+
GitDuplicator.perform(from, to)
|
36
|
+
|
37
|
+
```
|
38
|
+
### Advanced usage
|
39
|
+
- You can create the destination repository automatically. This needs you to provide the needed authentication credentials for the script.
|
40
|
+
- You can set the clone working path locally for the script to work. It's a temporary workplace that will get swiped after finishing.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
require 'git_duplicator'
|
44
|
+
from = GitDuplicator::Repository
|
45
|
+
.new('source repo name', 'source repo url')
|
46
|
+
to = GitDuplicator::Services::GithubRepository
|
47
|
+
.new('destination repo name', 'destination owner', {auth2_token: 'some token'})
|
48
|
+
GitDuplicator.perform(from, to, force_create_destination: true, clone_path: 'path/to/tmp')
|
49
|
+
```
|
50
|
+
|
51
|
+
### Available Services
|
52
|
+
|
53
|
+
The script works with any repository that you have access to. However, right now, there are 2 services that are implemented to help you initiate an empty destination repository. That way, you don't need to create them manaullay before doing the duplicaton. The services are Github and Bitbucket.
|
54
|
+
|
55
|
+
### Adding new Services
|
56
|
+
|
57
|
+
Adding new service, if needed, should be straight forward. You can look at the source of the Bitbucket or Github services implementations, where they inherit a base abstract class that you need to inherit as well.
|
58
|
+
|
59
|
+
### Running tests
|
60
|
+
|
61
|
+
To be able to run the tests, specially the acceptance ones, you need to rename the `.test.env.example` to `.test.env` and add the needed credentials for your accounts.
|
62
|
+
|
63
|
+
|
64
|
+
## Contributing
|
65
|
+
|
66
|
+
1. Fork it ( https://github.com/Startappz/git_duplicator/fork )
|
67
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
68
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
69
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
70
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'dotenv/tasks'
|
4
|
+
# Default directory to look in is `/specs`
|
5
|
+
# Run with `rake spec`
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |task|
|
7
|
+
task.rspec_opts = ['--color', '--format', 'doc']
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => :spec
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'git_duplicator/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "git_duplicator"
|
8
|
+
gem.version = GitDuplicator::Version
|
9
|
+
gem.authors = ["Khaled alHabache"]
|
10
|
+
gem.email = ["khellls@gmail.com"]
|
11
|
+
gem.summary = %q{Duplicating git repositories without forking.}
|
12
|
+
gem.description = %q{Duplicating git repositories without forking.}
|
13
|
+
gem.homepage = "https://github.com/Startappz/git_duplicator"
|
14
|
+
gem.license = 'LGPL-3'
|
15
|
+
gem.required_ruby_version = '>= 1.9.3'
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
gem.test_files = Dir.glob('gem/**/*')
|
19
|
+
gem.require_paths = ['lib']
|
20
|
+
gem.add_dependency 'http', '~> 0.6'
|
21
|
+
gem.add_dependency 'git', '~> 1.2'
|
22
|
+
gem.add_dependency 'simple_oauth', '~> 0.2'
|
23
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require_relative 'null_logger'
|
3
|
+
|
4
|
+
module GitDuplicator
|
5
|
+
class Duplicator
|
6
|
+
attr_accessor :from, :to, :logger, :clone_path, :force_create_destination
|
7
|
+
|
8
|
+
# @param [GitDuplicator::Repository, GitDuplicator::ServiceRepository]
|
9
|
+
# from repository to mirror
|
10
|
+
# @param [GitDuplicator::Repository, GitDuplicator::ServiceRepository]
|
11
|
+
# to repository to mirror to
|
12
|
+
# @param [Hash] options
|
13
|
+
# @option options [String] :clone_path
|
14
|
+
# path to clone the repository to
|
15
|
+
# @option options [Boolean] :force_create_destination
|
16
|
+
# delete the exisiting service repo then create it
|
17
|
+
# @option options [#info] :logger log what's going on
|
18
|
+
def initialize(from, to, options = {})
|
19
|
+
self.from = from
|
20
|
+
self.to = to
|
21
|
+
self.clone_path = options.fetch(:clone_path) { '/tmp' }
|
22
|
+
self.force_create_destination =
|
23
|
+
options.fetch(:force_create_destination) { false }
|
24
|
+
self.logger = options.fetch(:logger) { NullLogger.new }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Perform the duplication
|
28
|
+
def perform
|
29
|
+
recreate_destination
|
30
|
+
clone_source
|
31
|
+
mirror
|
32
|
+
ensure
|
33
|
+
clean_up
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_accessor :source_cloned
|
39
|
+
|
40
|
+
def recreate_destination
|
41
|
+
return unless force_create_destination
|
42
|
+
logger.info("Deleting existing destination repo: #{to.url}")
|
43
|
+
to.delete
|
44
|
+
logger.info("Creating destination repo: #{to.url}")
|
45
|
+
to.create
|
46
|
+
end
|
47
|
+
|
48
|
+
def clone_source
|
49
|
+
logger.info("Cloning bare Gitorious repo: #{from.url}")
|
50
|
+
from.bare_clone(clone_path)
|
51
|
+
self.source_cloned = true
|
52
|
+
end
|
53
|
+
|
54
|
+
def mirror
|
55
|
+
logger.info("Mirroring Gitorious to Bitbucket: #{from.url}")
|
56
|
+
from.mirror(to.url)
|
57
|
+
end
|
58
|
+
|
59
|
+
def clean_up
|
60
|
+
return unless source_cloned
|
61
|
+
logger.info 'Clean local source repo'
|
62
|
+
FileUtils.rm_rf("#{clone_path}/#{from.name}")
|
63
|
+
@cloned = nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module GitDuplicator
|
2
|
+
class MigrationError < StandardError; end
|
3
|
+
|
4
|
+
class RepositoryDeletionError < MigrationError; end
|
5
|
+
|
6
|
+
class RepositoryCreationError < MigrationError; end
|
7
|
+
|
8
|
+
class RepositoryCloningError < MigrationError; end
|
9
|
+
|
10
|
+
class RepositoryMirorringError < MigrationError; end
|
11
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'simple_oauth'
|
3
|
+
|
4
|
+
module GitDuplicator
|
5
|
+
module Helpers
|
6
|
+
# Generates authentication header
|
7
|
+
class AuthorizationHeader
|
8
|
+
OAUTH2_KEYS = [:oauth2_token]
|
9
|
+
OAUTH_KEYS = [:consumer_key, :consumer_secret, :token, :token_secret]
|
10
|
+
BASIC_KEYS = [:username, :password]
|
11
|
+
|
12
|
+
# @param [Hash] credentials
|
13
|
+
# @option credentials [Symbol] :oauth2_token used in oAuth2 authentication
|
14
|
+
# @option credentials [Symbol] :consumer_key used in oAuth authentication
|
15
|
+
# @option credentials [Symbol] :consumer_secret used in oAuth authentication
|
16
|
+
# @option credentials [Symbol] :token used in oAuth authentication
|
17
|
+
# @option credentials [Symbol] :token_secret used in oAuth authentication
|
18
|
+
# @option credentials [Symbol] :username used in basic authentication
|
19
|
+
# @option credentials [Symbol] :password used in basic authentication
|
20
|
+
# @param [Hash] request used in generating oauth headers
|
21
|
+
# @option request [Symbol] :method
|
22
|
+
# @option request [String] :url
|
23
|
+
# @option request [Array] :params
|
24
|
+
def initialize(credentials, request)
|
25
|
+
self.credentials = credentials
|
26
|
+
self.request_method = request.fetch(:method)
|
27
|
+
self.request_url = request.fetch(:url)
|
28
|
+
self.request_params = request.fetch(:params) { [] }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Generate the header
|
32
|
+
def generate
|
33
|
+
if exists?(self.class::OAUTH2_KEYS)
|
34
|
+
oauth2_header
|
35
|
+
elsif exists?(self.class::OAUTH_KEYS)
|
36
|
+
oauth_header
|
37
|
+
elsif exists?(self.class::BASIC_KEYS)
|
38
|
+
basic_authenticaion_header
|
39
|
+
else
|
40
|
+
fail ArgumentError, 'Proper authentication keys are missing'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_accessor :credentials, :request_method, :request_url, :request_params
|
47
|
+
|
48
|
+
def exists?(list)
|
49
|
+
list.all? { |value| credentials.keys.include?(value) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def oauth2_header
|
53
|
+
"token #{credentials[:oauth2_token]}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def basic_authenticaion_header
|
57
|
+
"Basic #{base64_username_password}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def oauth_header
|
61
|
+
SimpleOAuth::Header.new(request_method, request_url,
|
62
|
+
request_params, credentials).to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
def base64_username_password
|
66
|
+
Base64.encode64("#{credentials[:username]}" \
|
67
|
+
":#{credentials[:password]}")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module GitDuplicator
|
4
|
+
# Basic Repostiroy
|
5
|
+
class Repository
|
6
|
+
attr_accessor :name, :url
|
7
|
+
attr_reader :repo
|
8
|
+
|
9
|
+
# Initializer
|
10
|
+
# @param [String] name name of the repository
|
11
|
+
# @param [String] url URL of the repository
|
12
|
+
def initialize(name, url)
|
13
|
+
self.name = name
|
14
|
+
self.url = url
|
15
|
+
end
|
16
|
+
|
17
|
+
# Repository attribute setter
|
18
|
+
# @param [Git::Base] repository
|
19
|
+
def repo=(repository)
|
20
|
+
fail(TypeError) unless repository.is_a?(Git::Base)
|
21
|
+
@repo = repository
|
22
|
+
end
|
23
|
+
|
24
|
+
# Bare clone the repository
|
25
|
+
# @param [String] path_to_repo path to clone the repository to
|
26
|
+
def bare_clone(path_to_repo)
|
27
|
+
self.repo = Git.clone(url, name, bare: true, path: path_to_repo)
|
28
|
+
rescue => exception
|
29
|
+
raise RepositoryCloningError, exception.message
|
30
|
+
end
|
31
|
+
|
32
|
+
# Mirror the repository
|
33
|
+
# @param [String] destination_url URL of destination repository
|
34
|
+
def mirror(destination_url)
|
35
|
+
fail('No local repo defined. Set the "repo" attribute') unless repo
|
36
|
+
repo.push(destination_url, '--mirror')
|
37
|
+
rescue => exception
|
38
|
+
raise RepositoryMirorringError, exception.message
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module GitDuplicator
|
2
|
+
# Abstract class to use when defining new Git service provider
|
3
|
+
class ServiceRepository < Repository
|
4
|
+
attr_accessor :owner
|
5
|
+
|
6
|
+
# Initializer
|
7
|
+
# @param [String] name name of the repository
|
8
|
+
# @param [String] owner owner of the repository
|
9
|
+
def initialize(name, owner)
|
10
|
+
self.owner = owner
|
11
|
+
super(name, url)
|
12
|
+
end
|
13
|
+
|
14
|
+
# URL of the repositroy
|
15
|
+
def url
|
16
|
+
fail NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
# Create the repositroy
|
20
|
+
def create
|
21
|
+
fail NotImplementedError
|
22
|
+
end
|
23
|
+
|
24
|
+
# Delete the repositroy
|
25
|
+
def delete
|
26
|
+
fail NotImplementedError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'http'
|
2
|
+
require_relative '../helpers/authorization_header'
|
3
|
+
|
4
|
+
module GitDuplicator
|
5
|
+
module Services
|
6
|
+
# Bitbucket based repository
|
7
|
+
class BitbucketRepository < ServiceRepository
|
8
|
+
BASE_URI = 'https://api.bitbucket.org/2.0'
|
9
|
+
|
10
|
+
attr_accessor :credentials, :options
|
11
|
+
|
12
|
+
# Initializer
|
13
|
+
# @param [String] name name of the repository
|
14
|
+
# @param [String] owner owner of the repository
|
15
|
+
# @param [Hash] credentials
|
16
|
+
# @option credentials [Symbol] :consumer_key used in oAuth authentication
|
17
|
+
# @option credentials [Symbol] :consumer_secret used in oAuth authentication
|
18
|
+
# @option credentials [Symbol] :token used in oAuth authentication
|
19
|
+
# @option credentials [Symbol] :token_secret used in oAuth authentication
|
20
|
+
# @option credentials [Symbol] :username used in basic authentication
|
21
|
+
# @option credentials [Symbol] :password used in basic authentication
|
22
|
+
# @param [Hash] options options for creation
|
23
|
+
# @see https://confluence.atlassian.com/display/BITBUCKET/repository+Resource#repositoryResource-POSTanewrepository
|
24
|
+
def initialize(name, owner, credentials = {}, options = {})
|
25
|
+
super(name, owner)
|
26
|
+
self.credentials = credentials
|
27
|
+
self.options = options
|
28
|
+
end
|
29
|
+
|
30
|
+
# URL of the repositroy
|
31
|
+
def url
|
32
|
+
"git@bitbucket.org:#{owner}/#{name}.git"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Create the repositroy
|
36
|
+
# @see https://confluence.atlassian.com/display/BITBUCKET/repository+Resource#repositoryResource-POSTanewrepository
|
37
|
+
def create
|
38
|
+
request_url = BASE_URI + "/repositories/#{owner}/#{name}"
|
39
|
+
response = HTTP.with(headers(:post, request_url))
|
40
|
+
.post(request_url, json: options)
|
41
|
+
code, body = response.code.to_i, response.body
|
42
|
+
fail(RepositoryCreationError, body) unless 200 == code
|
43
|
+
end
|
44
|
+
|
45
|
+
# Delete the repositroy
|
46
|
+
# @see https://confluence.atlassian.com/display/BITBUCKET/repository+Resource#repositoryResource-DELETEarepository
|
47
|
+
def delete
|
48
|
+
request_url = BASE_URI + "/repositories/#{owner}/#{name}"
|
49
|
+
response = HTTP.with(headers(:delete, request_url)).delete(request_url)
|
50
|
+
code, body = response.code.to_i, response.body
|
51
|
+
fail(RepositoryDeletionError, body) unless [204, 404].include?(code)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def headers(method, url)
|
57
|
+
{
|
58
|
+
'Content-Type' => 'application/json',
|
59
|
+
'Authorization' =>
|
60
|
+
Helpers::AuthorizationHeader.new(
|
61
|
+
credentials, method: method, url: url
|
62
|
+
).generate
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'http'
|
2
|
+
require_relative '../helpers/authorization_header'
|
3
|
+
|
4
|
+
module GitDuplicator
|
5
|
+
module Services
|
6
|
+
class GithubRepository < ServiceRepository
|
7
|
+
BASE_URI = 'https://api.github.com'
|
8
|
+
|
9
|
+
attr_accessor :credentials, :options
|
10
|
+
|
11
|
+
# Initializer
|
12
|
+
# @param [String] name name of the repository
|
13
|
+
# @param [String] owner owner of the repository
|
14
|
+
# @param [Hash] credentials
|
15
|
+
# @option credentials [Symbol] :oauth2_token used in oAuth2 authentication
|
16
|
+
# @option credentials [Symbol] :username used in basic authentication
|
17
|
+
# @option credentials [Symbol] :password used in basic authentication
|
18
|
+
# @param [Hash] options options for creation
|
19
|
+
# @see https://developer.github.com/v3/repos/#create
|
20
|
+
def initialize(name, owner, credentials = {}, options = {})
|
21
|
+
super(name, owner)
|
22
|
+
self.credentials = credentials
|
23
|
+
self.options = options
|
24
|
+
end
|
25
|
+
|
26
|
+
# URL of the repositroy
|
27
|
+
def url
|
28
|
+
"git@github.com:#{owner}/#{name}.git"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create the repository
|
32
|
+
# @see https://developer.github.com/v3/repos/#create
|
33
|
+
def create
|
34
|
+
request_url = BASE_URI + '/user/repos'
|
35
|
+
response = HTTP.with(headers(:post, request_url))
|
36
|
+
.post(request_url, json: options.merge(name: name))
|
37
|
+
code, body = response.code.to_i, response.body
|
38
|
+
fail(RepositoryCreationError, body) unless 201 == code
|
39
|
+
end
|
40
|
+
|
41
|
+
# Delete the repositroy
|
42
|
+
# @see https://developer.github.com/v3/repos/#delete-a-repository
|
43
|
+
def delete
|
44
|
+
request_url = BASE_URI + "/repos/#{owner}/#{name}"
|
45
|
+
response = HTTP.with(headers(:delete, request_url)).delete(request_url)
|
46
|
+
code, body = response.code.to_i, response.body
|
47
|
+
fail(RepositoryDeletionError, body) unless [204, 404].include?(code)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def headers(method, url)
|
53
|
+
{
|
54
|
+
'Accept' => 'application/vnd.github.v3+json',
|
55
|
+
'Authorization' =>
|
56
|
+
Helpers::AuthorizationHeader.new(
|
57
|
+
credentials, method: method, url: url
|
58
|
+
).generate
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module GitDuplicator
|
2
|
+
# Load all implemented services
|
3
|
+
module Services
|
4
|
+
Dir[File.dirname(__FILE__) + '/services/**/*.rb'].each do |file|
|
5
|
+
# Get camelized class name
|
6
|
+
filename = File.basename(file, '.rb')
|
7
|
+
# Add _gateway suffix
|
8
|
+
gateway_name = filename + '_repository'
|
9
|
+
# Camelize the string to get the class name
|
10
|
+
gateway_class = gateway_name.split('_').map(&:capitalize).join
|
11
|
+
# Register for autoloading
|
12
|
+
autoload gateway_class, file
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'git_duplicator/version'
|
2
|
+
require_relative 'git_duplicator/duplicator'
|
3
|
+
require_relative 'git_duplicator/repositories'
|
4
|
+
require_relative 'git_duplicator/services'
|
5
|
+
|
6
|
+
# Main module
|
7
|
+
module GitDuplicator
|
8
|
+
class << self
|
9
|
+
# Perform the duplication
|
10
|
+
# @see GitDuplicator::Duplicator
|
11
|
+
def perform(from, to, options)
|
12
|
+
Duplicator.new(from, to, options).perform
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require_relative '../helper'
|
2
|
+
|
3
|
+
describe GitDuplicator::Duplicator do
|
4
|
+
let(:path) { '/somepath' }
|
5
|
+
let(:from) do
|
6
|
+
double(name: 'somename', bare_clone: nil, mirror: nil, url: 'somewhere1')
|
7
|
+
end
|
8
|
+
let(:to) { double(delete: nil, create: nil, url: 'somewhere2') }
|
9
|
+
let(:duplicator) { GitDuplicator::Duplicator.new(from, to, options) }
|
10
|
+
|
11
|
+
describe '#perform' do
|
12
|
+
context 'force_create_destination is set' do
|
13
|
+
let(:options) { { clone_path: path, force_create_destination: true } }
|
14
|
+
|
15
|
+
it 'deletes existing destination repo' do
|
16
|
+
expect(to).to receive(:delete)
|
17
|
+
duplicator.perform
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'creates the destination repo' do
|
21
|
+
expect(to).to receive(:create)
|
22
|
+
duplicator.perform
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'bare clones the source repo' do
|
26
|
+
expect(from).to receive(:bare_clone).with(path)
|
27
|
+
duplicator.perform
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'mirrors from bare clone to destination' do
|
31
|
+
expect(from).to receive(:mirror).with(to.url)
|
32
|
+
duplicator.perform
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'cleans up' do
|
36
|
+
expect(FileUtils).to receive(:rm_rf)
|
37
|
+
.with("#{duplicator.clone_path}/#{from.name}")
|
38
|
+
duplicator.perform
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'logs 5 times' do
|
42
|
+
expect(duplicator.logger).to receive(:info).exactly(5).times
|
43
|
+
duplicator.perform
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'force_create_destination is false' do
|
48
|
+
let(:options) { { clone_path: path } }
|
49
|
+
|
50
|
+
it 'deletes existing destination repo' do
|
51
|
+
expect(to).not_to receive(:delete)
|
52
|
+
duplicator.perform
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'creates the destination repo' do
|
56
|
+
expect(to).not_to receive(:create)
|
57
|
+
duplicator.perform
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'bare clones the source repo' do
|
61
|
+
expect(from).to receive(:bare_clone).with(path)
|
62
|
+
duplicator.perform
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'mirrors from bare clone to destination' do
|
66
|
+
expect(from).to receive(:mirror).with(to.url)
|
67
|
+
duplicator.perform
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'cleans up' do
|
71
|
+
expect(FileUtils).to receive(:rm_rf)
|
72
|
+
.with("#{duplicator.clone_path}/#{from.name}")
|
73
|
+
duplicator.perform
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'logs 3 times' do
|
77
|
+
expect(duplicator.logger).to receive(:info).exactly(3).times
|
78
|
+
duplicator.perform
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require_relative '../helper'
|
2
|
+
|
3
|
+
def random_name
|
4
|
+
rand(36**5).to_s(36)
|
5
|
+
end
|
6
|
+
|
7
|
+
describe GitDuplicator do
|
8
|
+
before(:each) { WebMock.allow_net_connect! }
|
9
|
+
let(:clone_path) { '/tmp' }
|
10
|
+
|
11
|
+
describe '#perform' do
|
12
|
+
|
13
|
+
let(:credentials) do
|
14
|
+
{ consumer_key: ENV['BITBUCKET_CONSUMER_KEY'],
|
15
|
+
consumer_secret: ENV['BITBUCKET_CONSUMER_SECRET'],
|
16
|
+
token: ENV['BITBUCKET_TOKEN'],
|
17
|
+
token_secret: ENV['BITBUCKET_TOKEN_SECRET'] }
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'mirrors from service to service without ' \
|
21
|
+
'force_create_destination option' do
|
22
|
+
skip
|
23
|
+
# Setup
|
24
|
+
from = GitDuplicator::Services::GithubRepository
|
25
|
+
.new('naught', 'avdi')
|
26
|
+
to = GitDuplicator::Services::BitbucketRepository
|
27
|
+
.new(ENV['TESTING_REPO'], ENV['BITBUCKET_USER'], credentials)
|
28
|
+
to.create
|
29
|
+
GitDuplicator.perform(from, to, clone_path: clone_path)
|
30
|
+
mirrored_name = random_name
|
31
|
+
result_name = random_name
|
32
|
+
|
33
|
+
# Exercise
|
34
|
+
mirrored = Git.clone(from.url, mirrored_name, path: clone_path)
|
35
|
+
result = Git.clone(from.url, result_name, path: clone_path)
|
36
|
+
|
37
|
+
# Verify
|
38
|
+
expect(result.object('HEAD^').to_s).to eq(mirrored.object('HEAD^').to_s)
|
39
|
+
|
40
|
+
# Teardown
|
41
|
+
FileUtils.rm_rf("#{clone_path}/#{mirrored_name}")
|
42
|
+
FileUtils.rm_rf("#{clone_path}/#{result_name}")
|
43
|
+
to.delete
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'mirrors from service to service with ' \
|
47
|
+
'force_create_destination option' do
|
48
|
+
skip
|
49
|
+
# Setup
|
50
|
+
from = GitDuplicator::Services::GithubRepository
|
51
|
+
.new('naught', 'avdi')
|
52
|
+
to = GitDuplicator::Services::BitbucketRepository
|
53
|
+
.new(ENV['TESTING_REPO'], ENV['BITBUCKET_USER'], credentials)
|
54
|
+
GitDuplicator.perform(from, to,
|
55
|
+
clone_path: clone_path,
|
56
|
+
force_create_destination: true)
|
57
|
+
mirrored_name = random_name
|
58
|
+
result_name = random_name
|
59
|
+
|
60
|
+
# Exercise
|
61
|
+
mirrored = Git.clone(from.url, mirrored_name, path: clone_path)
|
62
|
+
result = Git.clone(from.url, result_name, path: clone_path)
|
63
|
+
|
64
|
+
# Verify
|
65
|
+
expect(result.object('HEAD^').to_s).to eq(mirrored.object('HEAD^').to_s)
|
66
|
+
|
67
|
+
# Teardown
|
68
|
+
FileUtils.rm_rf("#{clone_path}/#{mirrored_name}")
|
69
|
+
FileUtils.rm_rf("#{clone_path}/#{result_name}")
|
70
|
+
to.delete
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'mirrors from basic to service with ' \
|
74
|
+
'force_create_destination option' do
|
75
|
+
skip
|
76
|
+
# Setup
|
77
|
+
from = GitDuplicator::Repository
|
78
|
+
.new('naught', 'https://github.com/avdi/naught.git')
|
79
|
+
to = GitDuplicator::Services::BitbucketRepository
|
80
|
+
.new(ENV['TESTING_REPO'], ENV['BITBUCKET_USER'], credentials)
|
81
|
+
GitDuplicator.perform(from, to,
|
82
|
+
clone_path: clone_path,
|
83
|
+
force_create_destination: true)
|
84
|
+
mirrored_name = random_name
|
85
|
+
result_name = random_name
|
86
|
+
|
87
|
+
# Exercise
|
88
|
+
mirrored = Git.clone(from.url, mirrored_name, path: clone_path)
|
89
|
+
result = Git.clone(from.url, result_name, path: clone_path)
|
90
|
+
|
91
|
+
# Verify
|
92
|
+
expect(result.object('HEAD^').to_s).to eq(mirrored.object('HEAD^').to_s)
|
93
|
+
|
94
|
+
# Teardown
|
95
|
+
FileUtils.rm_rf("#{clone_path}/#{mirrored_name}")
|
96
|
+
FileUtils.rm_rf("#{clone_path}/#{result_name}")
|
97
|
+
to.delete
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative '../../helper'
|
2
|
+
|
3
|
+
describe GitDuplicator::Repository do
|
4
|
+
let(:path) { 'somepath' }
|
5
|
+
let(:name) { 'something' }
|
6
|
+
let(:url) { 'someurl' }
|
7
|
+
let(:to_url) { 'someurl' }
|
8
|
+
let(:repo) { described_class.new(name, url) }
|
9
|
+
|
10
|
+
describe '#repo=' do
|
11
|
+
it 'assignes the attribute in case of Git::Base type' do
|
12
|
+
val = Git::Base.new
|
13
|
+
repo.repo = val
|
14
|
+
expect(repo.repo).to eq(val)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'raise ArgumentError in case the attribute type is not Git::Base' do
|
18
|
+
expect { repo.repo = 1 }.to raise_error(TypeError)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#bare_clone' do
|
23
|
+
before(:each) { allow(repo).to receive(:repo=) }
|
24
|
+
it 'clones a repo' do
|
25
|
+
expect(Git).to receive(:clone).with(url, name, path: path, bare: true)
|
26
|
+
repo.bare_clone(path)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'raises an exception in case of failure' do
|
30
|
+
expect { repo.bare_clone('') }
|
31
|
+
.to raise_error(GitDuplicator::RepositoryCloningError)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#mirror' do
|
36
|
+
it 'mirrors a repo' do
|
37
|
+
repo.repo = Git::Base.new
|
38
|
+
expect(repo.repo).to receive(:push).with(to_url, '--mirror')
|
39
|
+
repo.mirror(to_url)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'raises an exception in case of failure' do
|
43
|
+
expect { repo.mirror('') }
|
44
|
+
.to raise_error(GitDuplicator::RepositoryMirorringError)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative '../../helper'
|
2
|
+
|
3
|
+
describe GitDuplicator::ServiceRepository do
|
4
|
+
let(:name) { 'something' }
|
5
|
+
let(:owner) { 'someone' }
|
6
|
+
let(:repo) { described_class.new(name, owner) }
|
7
|
+
|
8
|
+
describe '#delete' do
|
9
|
+
it 'raises an exception in case of not defined' do
|
10
|
+
expect { repo.delete }.to raise_error(NotImplementedError)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#create' do
|
15
|
+
it 'raises an exception in case of not defined' do
|
16
|
+
expect { repo.create }.to raise_error(NotImplementedError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#url' do
|
21
|
+
it 'raises an exception in case of not defined' do
|
22
|
+
expect { repo.url }.to raise_error(NotImplementedError)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative '../../helper'
|
2
|
+
|
3
|
+
describe GitDuplicator::Services::BitbucketRepository do
|
4
|
+
let(:credentials) do
|
5
|
+
{ consumer_key: ENV['BITBUCKET_CONSUMER_KEY'],
|
6
|
+
consumer_secret: ENV['BITBUCKET_CONSUMER_SECRET'],
|
7
|
+
token: ENV['BITBUCKET_TOKEN'],
|
8
|
+
token_secret: ENV['BITBUCKET_TOKEN_SECRET'] }
|
9
|
+
end
|
10
|
+
let(:name) { ENV['TESTING_REPO'] }
|
11
|
+
let(:owner) { ENV['BITBUCKET_USER'] }
|
12
|
+
let(:options) do
|
13
|
+
{ scm: 'git', is_private: true, fork_policy: 'no_public_forks' }
|
14
|
+
end
|
15
|
+
let(:repo) { described_class.new(name, owner, credentials, options) }
|
16
|
+
|
17
|
+
describe '#delete' do
|
18
|
+
it 'deletes the repo in case it exists' do
|
19
|
+
stub_request(:delete, described_class::BASE_URI +
|
20
|
+
"/repositories/#{owner}/#{name}")
|
21
|
+
.to_return(body: '', status: 204)
|
22
|
+
expect { repo.delete }.not_to raise_error
|
23
|
+
end
|
24
|
+
it 'raises an exception in case of unknow problem' do
|
25
|
+
stub_request(:delete, described_class::BASE_URI +
|
26
|
+
"/repositories/#{owner}/#{name}")
|
27
|
+
.to_return(body: 'something wrong', status: 401)
|
28
|
+
expect { repo.delete }
|
29
|
+
.to raise_error(GitDuplicator::RepositoryDeletionError, /something wrong/)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#create' do
|
34
|
+
it 'creates the repository in case of not defined' do
|
35
|
+
stub_request(:post, described_class::BASE_URI +
|
36
|
+
"/repositories/#{owner}/#{name}")
|
37
|
+
.with(body: options.to_json)
|
38
|
+
.to_return(body: '', status: 200)
|
39
|
+
expect { repo.create }.not_to raise_error
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'raises an exception in case of errors' do
|
43
|
+
stub_request(:post, described_class::BASE_URI +
|
44
|
+
"/repositories/#{owner}/#{name}")
|
45
|
+
.with(body: options.to_json)
|
46
|
+
.to_return(body: 'something wrong', status: 401)
|
47
|
+
expect { repo.create }
|
48
|
+
.to raise_error(GitDuplicator::RepositoryCreationError, /something wrong/)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative '../../helper'
|
2
|
+
|
3
|
+
describe GitDuplicator::Services::GithubRepository do
|
4
|
+
let(:credentials) do
|
5
|
+
{ oauth2_token: ENV['GITHUB_ACCESS_TOKEN'] }
|
6
|
+
end
|
7
|
+
let(:name) { ENV['TESTING_REPO'] }
|
8
|
+
let(:owner) { ENV['GITHUB_USER'] }
|
9
|
+
let(:options) do
|
10
|
+
{ has_issues: false, has_wiki: false }
|
11
|
+
end
|
12
|
+
let(:repo) { described_class.new(name, owner, credentials, options) }
|
13
|
+
|
14
|
+
describe '#delete' do
|
15
|
+
it 'deletes the repo in case it exists' do
|
16
|
+
stub_request(:delete, described_class::BASE_URI +
|
17
|
+
"/repos/#{owner}/#{name}")
|
18
|
+
.with(
|
19
|
+
headers: { 'Authorization' => "token #{credentials[:oauth2_token]}" }
|
20
|
+
)
|
21
|
+
.to_return(body: '', status: 204)
|
22
|
+
expect { repo.delete }.not_to raise_error
|
23
|
+
end
|
24
|
+
it 'raises an exception in case of unknow problem' do
|
25
|
+
stub_request(:delete, described_class::BASE_URI +
|
26
|
+
"/repos/#{owner}/#{name}")
|
27
|
+
.to_return(body: 'something wrong', status: 401)
|
28
|
+
expect { repo.delete }
|
29
|
+
.to raise_error(GitDuplicator::RepositoryDeletionError)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#create' do
|
34
|
+
it 'creates the repository in case of not defined' do
|
35
|
+
stub_request(:post, described_class::BASE_URI + '/user/repos')
|
36
|
+
.with(
|
37
|
+
body: options.merge(name: name).to_json,
|
38
|
+
headers: { 'Authorization' => "token #{credentials[:oauth2_token]}" }
|
39
|
+
)
|
40
|
+
.to_return(body: '', status: 201)
|
41
|
+
expect { repo.create }.not_to raise_error
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'raises an exception in case of errors' do
|
45
|
+
stub_request(:post, described_class::BASE_URI + '/user/repos')
|
46
|
+
.with(body: options.merge(name: name).to_json)
|
47
|
+
.to_return(body: 'something wrong', status: 401)
|
48
|
+
expect { repo.create }
|
49
|
+
.to raise_error(GitDuplicator::RepositoryCreationError)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'webmock/rspec'
|
3
|
+
require 'simplecov'
|
4
|
+
require 'coveralls'
|
5
|
+
require 'dotenv'
|
6
|
+
|
7
|
+
Dotenv.load File.expand_path(
|
8
|
+
File.join(File.dirname(__FILE__), '../', '.test.env')
|
9
|
+
)
|
10
|
+
|
11
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
12
|
+
SimpleCov::Formatter::HTMLFormatter,
|
13
|
+
Coveralls::SimpleCov::Formatter
|
14
|
+
]
|
15
|
+
|
16
|
+
SimpleCov.start do
|
17
|
+
add_filter '/spec/'
|
18
|
+
minimum_coverage(95.0)
|
19
|
+
end
|
20
|
+
|
21
|
+
require_relative '../lib/git_duplicator.rb'
|
22
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
23
|
+
RSpec.configure do |config|
|
24
|
+
config.run_all_when_everything_filtered = true
|
25
|
+
# Run specs in random order to surface order dependencies. If you find an
|
26
|
+
# order dependency and want to debug it, you can fix the order by providing
|
27
|
+
# the seed, which is printed after each run.
|
28
|
+
# --seed 1234
|
29
|
+
config.order = 'random'
|
30
|
+
config.expect_with :rspec do |c|
|
31
|
+
c.syntax = :expect
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: git_duplicator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Khaled alHabache
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: http
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: git
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simple_oauth
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.2'
|
55
|
+
description: Duplicating git repositories without forking.
|
56
|
+
email:
|
57
|
+
- khellls@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- ".test.env.example"
|
65
|
+
- ".travis.yml"
|
66
|
+
- ".yardopts"
|
67
|
+
- Gemfile
|
68
|
+
- Guardfile
|
69
|
+
- LICENSE.md
|
70
|
+
- README.md
|
71
|
+
- Rakefile
|
72
|
+
- git_duplicator.gemspec
|
73
|
+
- lib/git_duplicator.rb
|
74
|
+
- lib/git_duplicator/duplicator.rb
|
75
|
+
- lib/git_duplicator/errors.rb
|
76
|
+
- lib/git_duplicator/helpers/authorization_header.rb
|
77
|
+
- lib/git_duplicator/null_logger.rb
|
78
|
+
- lib/git_duplicator/repositories.rb
|
79
|
+
- lib/git_duplicator/repository/repository.rb
|
80
|
+
- lib/git_duplicator/repository/service_repository.rb
|
81
|
+
- lib/git_duplicator/services.rb
|
82
|
+
- lib/git_duplicator/services/bitbucket.rb
|
83
|
+
- lib/git_duplicator/services/github.rb
|
84
|
+
- lib/git_duplicator/version.rb
|
85
|
+
- spec/git_duplicator/duplicator_spec.rb
|
86
|
+
- spec/git_duplicator/git_duplicator_spec.rb
|
87
|
+
- spec/git_duplicator/repository/repository_spec.rb
|
88
|
+
- spec/git_duplicator/repository/service_repository_spec.rb
|
89
|
+
- spec/git_duplicator/services/bitbucket_spec.rb
|
90
|
+
- spec/git_duplicator/services/github_spec.rb
|
91
|
+
- spec/git_duplicator/version_spec.rb
|
92
|
+
- spec/helper.rb
|
93
|
+
homepage: https://github.com/Startappz/git_duplicator
|
94
|
+
licenses:
|
95
|
+
- LGPL-3
|
96
|
+
metadata: {}
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: 1.9.3
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 2.4.1
|
114
|
+
signing_key:
|
115
|
+
specification_version: 4
|
116
|
+
summary: Duplicating git repositories without forking.
|
117
|
+
test_files: []
|
118
|
+
has_rdoc:
|