git-fastclone 1.0.16 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/git-fastclone.rb +52 -11
- data/lib/git-fastclone/version.rb +2 -1
- data/spec/git_fastclone_runner_spec.rb +27 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a48b08fe2a5e4f22ce995ab3c7801c93fb0562f
|
4
|
+
data.tar.gz: 43cc697ec456e6d699415fee25977f824677aff6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6aea5e53d2b7c774fe5c03c6cfebff3eb95747281f18e52daa81685e4a298919d85bc288f39421d9079e1bf17351636edbb64b3fd41c7577b9ea958aca9b90e3
|
7
|
+
data.tar.gz: 465b9bd78a32f22994c584391a08d20277ff968913337e08ffdd6073f0ca9267eac0338f418aa39a45bbc39c0c32b7bfa4255ff3567b1064cccff879d7aa00d2
|
data/lib/git-fastclone.rb
CHANGED
@@ -16,6 +16,7 @@ require 'optparse'
|
|
16
16
|
require 'fileutils'
|
17
17
|
require 'logger'
|
18
18
|
require 'cocaine'
|
19
|
+
require 'timeout'
|
19
20
|
|
20
21
|
# Contains helper module UrlHelper and execution class GitFastClone::Runner
|
21
22
|
module GitFastClone
|
@@ -32,7 +33,7 @@ module GitFastClone
|
|
32
33
|
module_function :parse_update_info
|
33
34
|
|
34
35
|
def reference_repo_name(url)
|
35
|
-
|
36
|
+
url.gsub(%r{^.*://}, '').gsub(/^[^@]*@/, '').tr('/', '-').tr(':', '-').to_s
|
36
37
|
end
|
37
38
|
module_function :reference_repo_name
|
38
39
|
|
@@ -49,6 +50,12 @@ module GitFastClone
|
|
49
50
|
"#{reference_repo_dir(url, reference_dir, using_local_repo)}:submodules.txt"
|
50
51
|
end
|
51
52
|
module_function :reference_repo_submodule_file
|
53
|
+
|
54
|
+
def reference_repo_lock_file(url, reference_dir, using_local_repo)
|
55
|
+
lock_file_name = "#{reference_repo_dir(url, reference_dir, using_local_repo)}:lock"
|
56
|
+
File.open(lock_file_name, File::RDWR | File::CREAT, 0644)
|
57
|
+
end
|
58
|
+
module_function :reference_repo_lock_file
|
52
59
|
end
|
53
60
|
|
54
61
|
# Spawns one thread per submodule, and updates them in parallel. They will be
|
@@ -60,12 +67,13 @@ module GitFastClone
|
|
60
67
|
|
61
68
|
include GitFastClone::UrlHelper
|
62
69
|
|
63
|
-
DEFAULT_REFERENCE_REPO_DIR = '/var/tmp/git-fastclone/reference'
|
70
|
+
DEFAULT_REFERENCE_REPO_DIR = '/var/tmp/git-fastclone/reference'.freeze
|
64
71
|
|
65
|
-
DEFAULT_GIT_ALLOW_PROTOCOL = 'file:git:http:https:ssh'
|
72
|
+
DEFAULT_GIT_ALLOW_PROTOCOL = 'file:git:http:https:ssh'.freeze
|
66
73
|
|
67
|
-
attr_accessor :reference_dir, :prefetch_submodules, :
|
68
|
-
:options, :logger, :abs_clone_path, :using_local_repo, :verbose, :color
|
74
|
+
attr_accessor :reference_dir, :prefetch_submodules, :reference_updated, :reference_mutex,
|
75
|
+
:options, :logger, :abs_clone_path, :using_local_repo, :verbose, :color,
|
76
|
+
:flock_timeout_secs
|
69
77
|
|
70
78
|
def initialize
|
71
79
|
# Prefetch reference repos for submodules we've seen before
|
@@ -93,6 +101,8 @@ module GitFastClone
|
|
93
101
|
self.verbose = false
|
94
102
|
|
95
103
|
self.color = false
|
104
|
+
|
105
|
+
self.flock_timeout_secs = 0
|
96
106
|
end
|
97
107
|
|
98
108
|
def run
|
@@ -112,7 +122,7 @@ module GitFastClone
|
|
112
122
|
clone(url, options[:branch], path)
|
113
123
|
end
|
114
124
|
|
115
|
-
def
|
125
|
+
def parse_options
|
116
126
|
usage = 'Usage: git fastclone [options] <git-url> [path]'
|
117
127
|
|
118
128
|
# One option --branch=<branch> We're not as brittle as clone. That branch
|
@@ -137,7 +147,16 @@ module GitFastClone
|
|
137
147
|
opts.on('-c', '--color', 'Display colored output') do
|
138
148
|
self.color = true
|
139
149
|
end
|
150
|
+
|
151
|
+
opts.on('--lock-timeout N', 'Timeout in seconds to acquire a lock on any reference repo.
|
152
|
+
Default is 0 which waits indefinitely.') do |timeout_secs|
|
153
|
+
self.flock_timeout_secs = timeout_secs
|
154
|
+
end
|
140
155
|
end.parse!
|
156
|
+
end
|
157
|
+
|
158
|
+
def parse_inputs
|
159
|
+
parse_options
|
141
160
|
|
142
161
|
unless ARGV[0]
|
143
162
|
STDERR.puts usage
|
@@ -172,13 +191,15 @@ module GitFastClone
|
|
172
191
|
|
173
192
|
with_git_mirror(url) do |mirror|
|
174
193
|
Cocaine::CommandLine.new('git clone', '--quiet --reference :mirror :url :path')
|
175
|
-
.run(mirror:
|
194
|
+
.run(mirror: mirror.to_s,
|
195
|
+
url: url.to_s,
|
196
|
+
path: File.join(abs_clone_path, src_dir).to_s)
|
176
197
|
end
|
177
198
|
|
178
199
|
# Only checkout if we're changing branches to a non-default branch
|
179
200
|
if rev
|
180
201
|
Dir.chdir(File.join(abs_clone_path, src_dir)) do
|
181
|
-
Cocaine::CommandLine.new('git checkout', '--quiet :rev').run(rev:
|
202
|
+
Cocaine::CommandLine.new('git checkout', '--quiet :rev').run(rev: rev.to_s)
|
182
203
|
end
|
183
204
|
end
|
184
205
|
|
@@ -218,14 +239,34 @@ module GitFastClone
|
|
218
239
|
threads << Thread.new do
|
219
240
|
with_git_mirror(submodule_url) do |mirror|
|
220
241
|
Cocaine::CommandLine.new('cd', ':dir; git submodule update --quiet --reference :mirror :path')
|
221
|
-
.run(dir:
|
242
|
+
.run(dir: File.join(abs_clone_path, pwd).to_s,
|
243
|
+
mirror: mirror.to_s,
|
244
|
+
path: submodule_path.to_s)
|
222
245
|
end
|
223
246
|
|
224
247
|
update_submodules(File.join(pwd, submodule_path), submodule_url)
|
225
248
|
end
|
226
249
|
end
|
227
250
|
|
228
|
-
def with_reference_repo_lock(url)
|
251
|
+
def with_reference_repo_lock(url, &block)
|
252
|
+
# Sane POSIX implementations remove exclusive flocks when a process is terminated or killed
|
253
|
+
# We block here indefinitely. Waiting for other git-fastclone processes to release the lock.
|
254
|
+
# With the default timeout of 0 we will wait forever, this can be overridden on the command line.
|
255
|
+
lockfile = reference_repo_lock_file(url, reference_dir, using_local_repo)
|
256
|
+
Timeout.timeout(flock_timeout_secs) { lockfile.flock(File::LOCK_EX) }
|
257
|
+
with_reference_repo_thread_lock(url, &block)
|
258
|
+
ensure
|
259
|
+
# Not strictly necessary to do this unlock as an ensure. If ever exception is caught outside this
|
260
|
+
# primitive, ensure protection may come in handy.
|
261
|
+
lockfile.flock(File::LOCK_UN)
|
262
|
+
lockfile.close
|
263
|
+
end
|
264
|
+
|
265
|
+
def with_reference_repo_thread_lock(url)
|
266
|
+
# We also need thread level locking because pre-fetch means multiple threads can
|
267
|
+
# attempt to update the same repository from a single git-fastclone process
|
268
|
+
# file locks in posix are tracked per process, not per userland thread.
|
269
|
+
# This gives us the equivalent of pthread_mutex around these accesses.
|
229
270
|
reference_mutex[reference_repo_name(url)].synchronize do
|
230
271
|
yield
|
231
272
|
end
|
@@ -275,7 +316,7 @@ module GitFastClone
|
|
275
316
|
def store_updated_repo(url, mirror, repo_name, fail_hard)
|
276
317
|
unless Dir.exist?(mirror)
|
277
318
|
Cocaine::CommandLine.new('git clone', '--mirror :url :mirror')
|
278
|
-
.run(url:
|
319
|
+
.run(url: url.to_s, mirror: mirror.to_s)
|
279
320
|
end
|
280
321
|
|
281
322
|
Cocaine::CommandLine.new('cd', ':path; git remote update --prune').run(path: mirror)
|
@@ -22,6 +22,14 @@ describe GitFastClone::Runner do
|
|
22
22
|
let(:test_reference_repo_dir) { '/var/tmp/git-fastclone/reference/test_reference_dir' }
|
23
23
|
let(:placeholder_arg) { 'PH' }
|
24
24
|
|
25
|
+
let(:lockfile) do
|
26
|
+
lockfile = double
|
27
|
+
expect(lockfile).to receive(:flock).with(File::LOCK_EX).once
|
28
|
+
expect(lockfile).to receive(:flock).with(File::LOCK_UN).once
|
29
|
+
expect(lockfile).to receive(:close).once
|
30
|
+
lockfile
|
31
|
+
end
|
32
|
+
|
25
33
|
# Modified ARGV, watch out
|
26
34
|
ARGV = ['ssh://git@git.com/git-fastclone.git', 'test_reference_dir']
|
27
35
|
|
@@ -42,7 +50,7 @@ describe GitFastClone::Runner do
|
|
42
50
|
end
|
43
51
|
|
44
52
|
describe '.run' do
|
45
|
-
let(:options) { {:
|
53
|
+
let(:options) { { branch: placeholder_arg } }
|
46
54
|
|
47
55
|
it 'should run with the correct args' do
|
48
56
|
allow(subject).to receive(:parse_inputs) { [placeholder_arg, placeholder_arg, options] }
|
@@ -58,7 +66,7 @@ describe GitFastClone::Runner do
|
|
58
66
|
subject.options = {}
|
59
67
|
allow(FileUtils).to receive(:mkdir_p) {}
|
60
68
|
|
61
|
-
expect(subject.parse_inputs).to eq([test_url_valid, test_reference_dir, {:
|
69
|
+
expect(subject.parse_inputs).to eq([test_url_valid, test_reference_dir, { branch: nil }])
|
62
70
|
end
|
63
71
|
end
|
64
72
|
|
@@ -104,6 +112,7 @@ describe GitFastClone::Runner do
|
|
104
112
|
it 'should acquire a lock' do
|
105
113
|
allow(Mutex).to receive(:synchronize)
|
106
114
|
expect(Mutex).to respond_to(:synchronize)
|
115
|
+
expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
|
107
116
|
|
108
117
|
subject.with_reference_repo_lock(test_url_valid) do
|
109
118
|
yielded << test_url_valid
|
@@ -111,6 +120,17 @@ describe GitFastClone::Runner do
|
|
111
120
|
|
112
121
|
expect(yielded).to eq([test_url_valid])
|
113
122
|
end
|
123
|
+
it 'should un-flock on thrown exception' do
|
124
|
+
allow(Mutex).to receive(:synchronize)
|
125
|
+
expect(Mutex).to respond_to(:synchronize)
|
126
|
+
expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
|
127
|
+
|
128
|
+
expect do
|
129
|
+
subject.with_reference_repo_lock(test_url_valid) do
|
130
|
+
fail placeholder_arg
|
131
|
+
end
|
132
|
+
end.to raise_error(placeholder_arg)
|
133
|
+
end
|
114
134
|
end
|
115
135
|
|
116
136
|
describe '.update_submodule_reference' do
|
@@ -130,6 +150,7 @@ describe GitFastClone::Runner do
|
|
130
150
|
allow(subject).to receive(:reference_repo_name) {}
|
131
151
|
allow(subject).to receive(:reference_repo_submodule_file) {}
|
132
152
|
expect(File).to receive(:open)
|
153
|
+
expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
|
133
154
|
|
134
155
|
subject.update_submodule_reference(placeholder_arg, [placeholder_arg, placeholder_arg])
|
135
156
|
end
|
@@ -141,6 +162,7 @@ describe GitFastClone::Runner do
|
|
141
162
|
it 'should grab the children immediately and then store' do
|
142
163
|
expect(subject).to receive(:prefetch).once
|
143
164
|
expect(subject).to receive(:store_updated_repo).once
|
165
|
+
expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
|
144
166
|
|
145
167
|
allow(File).to receive(:exist?) { true }
|
146
168
|
subject.prefetch_submodules = true
|
@@ -153,6 +175,7 @@ describe GitFastClone::Runner do
|
|
153
175
|
it 'should store the updated repo' do
|
154
176
|
expect(subject).not_to receive(:prefetch)
|
155
177
|
expect(subject).to receive(:store_updated_repo).once
|
178
|
+
expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
|
156
179
|
|
157
180
|
allow(File).to receive(:exist?) { true }
|
158
181
|
subject.prefetch_submodules = false
|
@@ -180,6 +203,7 @@ describe GitFastClone::Runner do
|
|
180
203
|
it 'should store' do
|
181
204
|
placeholder_hash[placeholder_arg] = false
|
182
205
|
expect(subject).to receive(:store_updated_repo)
|
206
|
+
expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
|
183
207
|
|
184
208
|
allow(subject).to receive(:reference_repo_name) { placeholder_arg }
|
185
209
|
subject.reference_updated = placeholder_hash
|
@@ -244,6 +268,7 @@ describe GitFastClone::Runner do
|
|
244
268
|
it 'should yield properly' do
|
245
269
|
allow(subject).to receive(:update_reference_repo) {}
|
246
270
|
expect(subject).to receive(:reference_repo_dir)
|
271
|
+
expect(subject).to receive(:reference_repo_lock_file).and_return(lockfile)
|
247
272
|
|
248
273
|
subject.with_git_mirror(test_url_valid) do
|
249
274
|
yielded << test_url_valid
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git-fastclone
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Tauraso
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-03-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cocaine
|
@@ -78,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
78
|
version: '0'
|
79
79
|
requirements: []
|
80
80
|
rubyforge_project:
|
81
|
-
rubygems_version: 2.
|
81
|
+
rubygems_version: 2.2.2
|
82
82
|
signing_key:
|
83
83
|
specification_version: 4
|
84
84
|
summary: git-clone --recursive on steroids!
|