git_duplicator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format=doc
data/.test.env.example ADDED
@@ -0,0 +1,13 @@
1
+ # General
2
+ TESTING_REPO=
3
+
4
+ # Bitbucket info
5
+ BITBUCKET_CONSUMER_KEY=
6
+ BITBUCKET_CONSUMER_SECRET=
7
+ BITBUCKET_TOKEN=
8
+ BITBUCKET_TOKEN_SECRET=
9
+ BITBUCKET_USER=
10
+
11
+ # Github info
12
+ GITHUB_ACCESS_TOKEN=
13
+ GITHUB_USER=
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
@@ -0,0 +1,5 @@
1
+ --no-private
2
+ --markup markdown
3
+ -
4
+ README.md
5
+ LICENSE.md
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
@@ -0,0 +1,5 @@
1
+ guard 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}){ |m| "spec/git_duplicator/#{m[1]}_spec.rb" }
4
+ watch('spec/helper.rb') { "spec" }
5
+ end
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,14 @@
1
+ module GitDuplicator
2
+ # Default logger
3
+ class NullLogger
4
+ def debug(*); end
5
+
6
+ def info(*); end
7
+
8
+ def warn(*); end
9
+
10
+ def error(*); end
11
+
12
+ def fatal(*); end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'errors'
2
+ require_relative 'repository/repository'
3
+ require_relative 'repository/service_repository'
@@ -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
+ module GitDuplicator
2
+ # Gem's version
3
+ class Version
4
+ MAJOR = 0
5
+ MINOR = 0
6
+ PATCH = 1
7
+
8
+ class << self
9
+ # @return [String]
10
+ def to_s
11
+ [MAJOR, MINOR, PATCH].compact.join('.')
12
+ end
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
@@ -0,0 +1,9 @@
1
+ require 'helper'
2
+
3
+ describe GitDuplicator do
4
+
5
+ it 'must be defined' do
6
+ expect(GitDuplicator::Version).to be
7
+ end
8
+
9
+ 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: