git_duplicator 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +94 -16
- data/lib/git_duplicator.rb +9 -3
- data/lib/git_duplicator/{duplicator.rb → duplicator/duplicator.rb} +16 -13
- data/lib/git_duplicator/duplicator/mirror_duplicator.rb +18 -0
- data/lib/git_duplicator/duplicator/update_duplicator.rb +17 -0
- data/lib/git_duplicator/duplicators.rb +5 -0
- data/lib/git_duplicator/errors.rb +4 -0
- data/lib/git_duplicator/repositories.rb +1 -0
- data/lib/git_duplicator/repository/repository.rb +86 -10
- data/lib/git_duplicator/repository/service_repository.rb +3 -2
- data/lib/git_duplicator/services/bitbucket.rb +18 -17
- data/lib/git_duplicator/services/github.rb +15 -16
- data/lib/git_duplicator/version.rb +2 -2
- data/spec/git_duplicator/duplicator/duplicator_spec.rb +112 -0
- data/spec/git_duplicator/duplicator/mirror_duplicator_spec.rb +35 -0
- data/spec/git_duplicator/duplicator/update_duplicator_spec.rb +39 -0
- data/spec/git_duplicator/git_duplicator_acceptance_spec.rb +188 -0
- data/spec/git_duplicator/git_duplicator_spec.rb +22 -92
- data/spec/git_duplicator/helpers/authorization_header_spec.rb +29 -0
- data/spec/git_duplicator/repository/repository_spec.rb +81 -33
- data/spec/git_duplicator/repository/service_repository_spec.rb +17 -15
- data/spec/git_duplicator/services/bitbucket_spec.rb +48 -44
- data/spec/git_duplicator/services/github_spec.rb +50 -44
- metadata +11 -4
- data/spec/git_duplicator/duplicator_spec.rb +0 -82
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41ed03abddf02fd443578e3f337713da86e085ae
|
4
|
+
data.tar.gz: 065fb1a481102c0ebc59425d2cd2d34321cf7481
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55a30dabf88926510f0c62e81e1c5a6c35e806a7462714e8cfbef483bf99b0aff6e6e2880f4956234f0167fa1a0bd82ce78fd6956aaef779b22f2752c0e02692
|
7
|
+
data.tar.gz: 862524fa37e6105d6e8812d851bab25dfbf01104537e1641e8ec7d5f5b91e22e72295a4c9181201198a3eb5dbe8a62185780497fc4da6af15f9bada5ea54a86e
|
data/README.md
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# Git Duplicator
|
2
2
|
|
3
|
-
|
3
|
+
[](http://badge.fury.io/rb/git_duplicator) [](https://travis-ci.org/Startappz/git_duplicator) [](https://coveralls.io/r/Startappz/git_duplicator?branch=master) [](https://gemnasium.com/Startappz/git_duplicator) [](https://codeclimate.com/github/Startappz/git_duplicator)
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
|
6
|
+
Duplicating git repositories without forking. It depends on the flow described in [this](https://help.github.com/articles/duplicating-a-repository) article.
|
7
|
+
|
8
|
+
- [x] Duplicate any repository in two ways: one time usage and frequent updates.
|
9
|
+
- [x] Additional Github [support](https://github.com/Startappz/git_duplicator/blob/master/lib/git_duplicator/services/github.rb) to create and delete repositories.
|
10
|
+
- [x] Additional Bibucket [support](https://github.com/Startappz/git_duplicator/blob/master/lib/git_duplicator/services/bitbucket.rb) to create and delete repositories.
|
8
11
|
|
9
12
|
## Installation
|
10
13
|
|
@@ -22,30 +25,105 @@ Or install it yourself as:
|
|
22
25
|
|
23
26
|
## Usage
|
24
27
|
|
25
|
-
###
|
28
|
+
### Duplicate with no future updates
|
29
|
+
|
30
|
+
Duplicate a repository. Then you can turn off the source repo and move to the mirrored one.
|
26
31
|
|
27
|
-
|
32
|
+
#### Basic usage
|
33
|
+
|
34
|
+
It assumes that you have the destination repository initiated.
|
28
35
|
|
29
36
|
```ruby
|
30
37
|
require 'git_duplicator'
|
31
|
-
|
32
|
-
.new('source repo name', 'source repo url')
|
33
|
-
|
34
|
-
.new('
|
38
|
+
|
39
|
+
from = GitDuplicator::Repository.new('source repo name', 'source repo url')
|
40
|
+
|
41
|
+
to = GitDuplicator::Repository.new('mirrored repo name', 'mirrored repo url')
|
42
|
+
|
35
43
|
GitDuplicator.perform(from, to)
|
36
44
|
|
45
|
+
|
37
46
|
```
|
38
|
-
|
47
|
+
#### Advanced usage
|
39
48
|
- You can create the destination repository automatically. This needs you to provide the needed authentication credentials for the script.
|
40
49
|
- 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
50
|
|
42
51
|
```ruby
|
43
52
|
require 'git_duplicator'
|
44
|
-
|
45
|
-
.new('source repo name', 'source repo url')
|
46
|
-
|
47
|
-
.new(
|
48
|
-
|
53
|
+
|
54
|
+
from = GitDuplicator::Repository.new('source repo name', 'source repo url')
|
55
|
+
|
56
|
+
to = GitDuplicator::Services::GithubRepository.new(
|
57
|
+
'mirrored repo name', 'mirrored owner',
|
58
|
+
credentials: { oauth2_token: 'some token' },
|
59
|
+
remote_options: { has_issues: false, has_wiki: false }
|
60
|
+
)
|
61
|
+
|
62
|
+
GitDuplicator.perform(
|
63
|
+
from, to,
|
64
|
+
force_create_destination: true,
|
65
|
+
clone_path: 'path/to/clone/folder'
|
66
|
+
)
|
67
|
+
|
68
|
+
```
|
69
|
+
### Duplicate with future updates
|
70
|
+
|
71
|
+
Duplicate a repository. To update your mirror, fetch updates and push, which could be automated by running a cron job.
|
72
|
+
|
73
|
+
#### Basic usage
|
74
|
+
|
75
|
+
It assumes that you have the destination repository initiated.
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
require 'git_duplicator'
|
79
|
+
|
80
|
+
from = GitDuplicator::Repository.new('source repo name', 'source repo url')
|
81
|
+
|
82
|
+
to = GitDuplicator::Repository.new('mirrored repo name', 'mirrored repo url')
|
83
|
+
|
84
|
+
GitDuplicator.perform_for_update(from, to)
|
85
|
+
|
86
|
+
# Later on if you want to update the mirrored one
|
87
|
+
local_repo = GitDuplicator::Repository.new(
|
88
|
+
'source repo name',
|
89
|
+
'source repo url',
|
90
|
+
'path/to/working/directory'
|
91
|
+
)
|
92
|
+
|
93
|
+
local_repo.update_mirrored
|
94
|
+
|
95
|
+
```
|
96
|
+
#### Advanced usage
|
97
|
+
- You can create the destination repository automatically. This needs you to provide the needed authentication credentials for the script.
|
98
|
+
- You can set the clone working path locally for the script to work. It's a temporary workplace that will get swiped after finishing.
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
require 'git_duplicator'
|
102
|
+
|
103
|
+
from = GitDuplicator::Repository.new('source repo name', 'source repo url')
|
104
|
+
|
105
|
+
to = GitDuplicator::Services::GithubRepository.new(
|
106
|
+
'mirrored repo name',
|
107
|
+
'mirrored owner',
|
108
|
+
credentials: { oauth2_token: 'some token' },
|
109
|
+
remote_options: { has_issues: false, has_wiki: false }
|
110
|
+
)
|
111
|
+
|
112
|
+
GitDuplicator.perform_for_update(
|
113
|
+
from, to,
|
114
|
+
force_create_destination: true,
|
115
|
+
clone_path: 'path/to/clone/folder'
|
116
|
+
)
|
117
|
+
|
118
|
+
# Later on if you want to update the mirrored one
|
119
|
+
local_repo = GitDuplicator::Repository.new(
|
120
|
+
'source repo name',
|
121
|
+
'source repo url',
|
122
|
+
'path/to/working/directory'
|
123
|
+
)
|
124
|
+
|
125
|
+
local_repo.update_mirrored
|
126
|
+
|
49
127
|
```
|
50
128
|
|
51
129
|
### Available Services
|
data/lib/git_duplicator.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require_relative 'git_duplicator/version'
|
2
|
-
require_relative 'git_duplicator/
|
2
|
+
require_relative 'git_duplicator/duplicators'
|
3
3
|
require_relative 'git_duplicator/repositories'
|
4
4
|
require_relative 'git_duplicator/services'
|
5
5
|
|
@@ -7,9 +7,15 @@ require_relative 'git_duplicator/services'
|
|
7
7
|
module GitDuplicator
|
8
8
|
class << self
|
9
9
|
# Perform the duplication
|
10
|
-
# @see GitDuplicator::
|
10
|
+
# @see GitDuplicator::MirrorDuplicator
|
11
11
|
def perform(from, to, options)
|
12
|
-
|
12
|
+
MirrorDuplicator.new(from, to, options).perform
|
13
|
+
end
|
14
|
+
|
15
|
+
# Perform the duplication for updates
|
16
|
+
# @see GitDuplicator::UpdateDuplicator
|
17
|
+
def perform_for_update(from, to, options)
|
18
|
+
UpdateDuplicator.new(from, to, options).perform
|
13
19
|
end
|
14
20
|
end
|
15
21
|
end
|
@@ -1,7 +1,5 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
require_relative 'null_logger'
|
3
|
-
|
4
1
|
module GitDuplicator
|
2
|
+
# Abstract class
|
5
3
|
class Duplicator
|
6
4
|
attr_accessor :from, :to, :logger, :clone_path, :force_create_destination
|
7
5
|
|
@@ -33,10 +31,6 @@ module GitDuplicator
|
|
33
31
|
clean_up
|
34
32
|
end
|
35
33
|
|
36
|
-
private
|
37
|
-
|
38
|
-
attr_accessor :source_cloned
|
39
|
-
|
40
34
|
def recreate_destination
|
41
35
|
return unless force_create_destination
|
42
36
|
logger.info("Deleting existing destination repo: #{to.url}")
|
@@ -47,20 +41,29 @@ module GitDuplicator
|
|
47
41
|
|
48
42
|
def clone_source
|
49
43
|
logger.info("Cloning bare Gitorious repo: #{from.url}")
|
50
|
-
|
51
|
-
self.source_cloned = true
|
44
|
+
perform_clone_source
|
52
45
|
end
|
53
46
|
|
54
47
|
def mirror
|
55
48
|
logger.info("Mirroring Gitorious to Bitbucket: #{from.url}")
|
56
|
-
|
49
|
+
perform_mirror
|
57
50
|
end
|
58
51
|
|
59
52
|
def clean_up
|
60
|
-
return unless source_cloned
|
61
53
|
logger.info 'Clean local source repo'
|
62
|
-
|
63
|
-
|
54
|
+
perform_clean_up
|
55
|
+
end
|
56
|
+
|
57
|
+
def perform_clone_source
|
58
|
+
fail NotImplementedError
|
59
|
+
end
|
60
|
+
|
61
|
+
def perform_mirror
|
62
|
+
fail NotImplementedError
|
63
|
+
end
|
64
|
+
|
65
|
+
def perform_clean_up
|
66
|
+
fail NotImplementedError
|
64
67
|
end
|
65
68
|
end
|
66
69
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module GitDuplicator
|
2
|
+
# Mirror a repository
|
3
|
+
class MirrorDuplicator < Duplicator
|
4
|
+
protected
|
5
|
+
|
6
|
+
def perform_clone_source
|
7
|
+
from.bare_clone(clone_path)
|
8
|
+
end
|
9
|
+
|
10
|
+
def perform_mirror
|
11
|
+
from.mirror(to.url)
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform_clean_up
|
15
|
+
FileUtils.rm_rf("#{clone_path}/#{from.name}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module GitDuplicator
|
2
|
+
# Mirror a repository and end make it ready for updates
|
3
|
+
class UpdateDuplicator < Duplicator
|
4
|
+
protected
|
5
|
+
|
6
|
+
def perform_clone_source
|
7
|
+
from.mirror_clone(clone_path)
|
8
|
+
end
|
9
|
+
|
10
|
+
def perform_mirror
|
11
|
+
from.set_mirrored_remote(to.url)
|
12
|
+
from.update_mirrored
|
13
|
+
end
|
14
|
+
|
15
|
+
def perform_clean_up; end
|
16
|
+
end
|
17
|
+
end
|
@@ -8,4 +8,8 @@ module GitDuplicator
|
|
8
8
|
class RepositoryCloningError < MigrationError; end
|
9
9
|
|
10
10
|
class RepositoryMirorringError < MigrationError; end
|
11
|
+
|
12
|
+
class RepositoryMirorredUpdatingError < MigrationError; end
|
13
|
+
|
14
|
+
class RepositorySettingRemoteError < MigrationError; end
|
11
15
|
end
|
@@ -4,21 +4,21 @@ module GitDuplicator
|
|
4
4
|
# Basic Repostiroy
|
5
5
|
class Repository
|
6
6
|
attr_accessor :name, :url
|
7
|
-
attr_reader :
|
7
|
+
attr_reader :working_directory
|
8
8
|
|
9
9
|
# Initializer
|
10
10
|
# @param [String] name name of the repository
|
11
11
|
# @param [String] url URL of the repository
|
12
|
-
|
12
|
+
# @param [String] working_directory working directory of the repository
|
13
|
+
def initialize(name, url, working_directory = nil)
|
13
14
|
self.name = name
|
14
15
|
self.url = url
|
16
|
+
self.working_directory = working_directory
|
15
17
|
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
fail(TypeError) unless repository.is_a?(Git::Base)
|
21
|
-
@repo = repository
|
19
|
+
def working_directory=(value)
|
20
|
+
@working_directory = value
|
21
|
+
@repo = repo_from_path
|
22
22
|
end
|
23
23
|
|
24
24
|
# Bare clone the repository
|
@@ -29,13 +29,89 @@ module GitDuplicator
|
|
29
29
|
raise RepositoryCloningError, exception.message
|
30
30
|
end
|
31
31
|
|
32
|
+
# Mirror clone the repository
|
33
|
+
# @param [String] path_to_repo path to clone the repository to
|
34
|
+
def mirror_clone(path_to_repo)
|
35
|
+
path = File.join(path_to_repo, name)
|
36
|
+
command('clone', '--mirror', url, path)
|
37
|
+
self.repo = Git.bare(path)
|
38
|
+
rescue => exception
|
39
|
+
raise RepositoryCloningError, exception.message
|
40
|
+
end
|
41
|
+
|
32
42
|
# Mirror the repository
|
33
|
-
# @param [String]
|
34
|
-
def mirror(
|
43
|
+
# @param [String] mirrored_url URL of mirrored repository
|
44
|
+
def mirror(mirrored_url)
|
35
45
|
fail('No local repo defined. Set the "repo" attribute') unless repo
|
36
|
-
repo.push(
|
46
|
+
repo.push(mirrored_url, '--mirror')
|
37
47
|
rescue => exception
|
38
48
|
raise RepositoryMirorringError, exception.message
|
39
49
|
end
|
50
|
+
|
51
|
+
# Set the remote URL of the mirrored
|
52
|
+
# @param [String] mirrored_url URL of mirrored repository
|
53
|
+
def set_mirrored_remote(mirrored_url)
|
54
|
+
fail('No local repo defined. Set the "repo" attribute') unless repo
|
55
|
+
command('remote', 'set-url', '--push', 'origin', mirrored_url)
|
56
|
+
rescue => exception
|
57
|
+
raise RepositorySettingRemoteError, exception.message
|
58
|
+
end
|
59
|
+
|
60
|
+
# Update a mirrored repository
|
61
|
+
def update_mirrored
|
62
|
+
fail('No local repo defined. Set the "repo" attribute') unless repo
|
63
|
+
command('fetch', '-p', 'origin')
|
64
|
+
command('push', '--mirror')
|
65
|
+
rescue => exception
|
66
|
+
raise RepositoryMirorredUpdatingError, exception.message
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
attr_reader :repo # for testing
|
72
|
+
|
73
|
+
def repo=(value)
|
74
|
+
@repo = value
|
75
|
+
@working_directory = working_directory_from_repo
|
76
|
+
end
|
77
|
+
|
78
|
+
def repo_from_path
|
79
|
+
if working_directory
|
80
|
+
Git.open(working_directory) rescue Git.bare(working_directory)
|
81
|
+
else
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def working_directory_from_repo
|
87
|
+
return nil if repo.nil?
|
88
|
+
if repo.dir
|
89
|
+
repo.dir.path
|
90
|
+
elsif repo.repo
|
91
|
+
repo.repo.path
|
92
|
+
else
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def command(cmd, *opts)
|
98
|
+
git_cmd = "git #{cmd} #{opts.join(' ')}"
|
99
|
+
out = run_command(git_cmd)
|
100
|
+
if $?.exitstatus > 0
|
101
|
+
if $?.exitstatus == 1 && out == ''
|
102
|
+
return ''
|
103
|
+
end
|
104
|
+
fail(git_cmd + ': ' + out.to_s)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def run_command(cmd)
|
109
|
+
exec = "#{cmd} 2>&1"
|
110
|
+
if working_directory
|
111
|
+
Dir.chdir(working_directory) { `#{exec}` }
|
112
|
+
else
|
113
|
+
`#{exec}`
|
114
|
+
end.chomp
|
115
|
+
end
|
40
116
|
end
|
41
117
|
end
|
@@ -6,9 +6,10 @@ module GitDuplicator
|
|
6
6
|
# Initializer
|
7
7
|
# @param [String] name name of the repository
|
8
8
|
# @param [String] owner owner of the repository
|
9
|
-
|
9
|
+
# @param [String] working_directory working directory of the repository
|
10
|
+
def initialize(name, owner, working_directory = nil)
|
10
11
|
self.owner = owner
|
11
|
-
super(name, url)
|
12
|
+
super(name, url, working_directory)
|
12
13
|
end
|
13
14
|
|
14
15
|
# URL of the repositroy
|
@@ -1,30 +1,31 @@
|
|
1
1
|
require 'http'
|
2
|
-
require_relative '../helpers/authorization_header'
|
3
|
-
|
4
2
|
module GitDuplicator
|
5
3
|
module Services
|
6
4
|
# Bitbucket based repository
|
7
5
|
class BitbucketRepository < ServiceRepository
|
8
6
|
BASE_URI = 'https://api.bitbucket.org/2.0'
|
9
7
|
|
10
|
-
attr_accessor :credentials, :
|
8
|
+
attr_accessor :credentials, :remote_options
|
11
9
|
|
12
10
|
# Initializer
|
13
11
|
# @param [String] name name of the repository
|
14
12
|
# @param [String] owner owner of the repository
|
15
|
-
# @param [Hash]
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
self.
|
13
|
+
# @param [Hash] options
|
14
|
+
# * :credentials (Hash) credentials for remote service
|
15
|
+
# * :consumer_key (Symbol) used in oAuth authentication
|
16
|
+
# * :consumer_secret (Symbol) used in oAuth authentication
|
17
|
+
# * :token (Symbol) used in oAuth authentication
|
18
|
+
# * :token_secret (Symbol) used in oAuth authentication
|
19
|
+
# * :username (Symbol) used in basic authentication
|
20
|
+
# * :password (Symbol) used in basic authentication
|
21
|
+
# * :remote_options (Hash) creation options for remote service
|
22
|
+
# @see https://confluence.atlassian.com/display/BITBUCKET/repository+Resource#repositoryResource-POSTanewrepository
|
23
|
+
# * :working_directory (String) assing a working directory
|
24
|
+
def initialize(name, owner, options = {})
|
25
|
+
self.credentials = options.fetch(:credentials) { {} }
|
26
|
+
self.remote_options = options.fetch(:remote_options) { {} }
|
27
|
+
self.working_directory = options.fetch(:working_directory) { nil }
|
28
|
+
super(name, owner, working_directory)
|
28
29
|
end
|
29
30
|
|
30
31
|
# URL of the repositroy
|
@@ -37,7 +38,7 @@ module GitDuplicator
|
|
37
38
|
def create
|
38
39
|
request_url = BASE_URI + "/repositories/#{owner}/#{name}"
|
39
40
|
response = HTTP.with(headers(:post, request_url))
|
40
|
-
.post(request_url, json:
|
41
|
+
.post(request_url, json: remote_options)
|
41
42
|
code, body = response.code.to_i, response.body
|
42
43
|
fail(RepositoryCreationError, body) unless 200 == code
|
43
44
|
end
|