capistrano-git-copy 1.5.2 → 1.5.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ff186491f9d22f79fab6c98f8e8f803240d94c3e364b74ecad60b0d358df60e
4
- data.tar.gz: fef778eafe2a6b6f188577fa6e36d7fc1b0caec838b854274a8e933b485ef8c4
3
+ metadata.gz: 100d26afc3422d8280d7ec211f0af3c9f2b2802c66aa5ffcdaa17d919de52292
4
+ data.tar.gz: 1d44966f13986f6ebd4f5c4d37a00b9fddb452ce0b82da0f8a863894bc320b40
5
5
  SHA512:
6
- metadata.gz: 80a5adb7628716d21644b2e7f5a14bde6737bc23443949f20656e10e0017d4751af50fe2cc8884f7f2172fb86d8c00db1fd30429f5901e6b0072edf7be144973
7
- data.tar.gz: 7e6f01a93481ca06c2a95e958b070187a46ab126403706b6e953183ee7176067cbb731a3e0bd67147231a1055d69523b194a5ff9a6f875897dde629b1e199ba7
6
+ metadata.gz: 3b38135587cf0d941be9664bbe3573544180398fcc13e9f466815ae47b4a1adcad2317fcd8ae4376a25f1e6d9413f264804045f8f5b4a805bea53fc798b015eb
7
+ data.tar.gz: a59f7e452bc4f0a54f8262c4b08576a3337c03c797b1531e3f36ed8dac88c86f972228580a14bf7b4da656fcfb3ed5eff2fb3c93e0febb45766de7da72a044a8
@@ -1,5 +1,9 @@
1
1
  # Change Log
2
2
 
3
+ ## 1.5.3 (2018-11-02)
4
+ ### Changes
5
+ - updated _git-archive-all_ to 1.19.1
6
+
3
7
  ## 1.5.2 (2018-10-07)
4
8
  ### Changes
5
9
  - updated _git-archive-all_ to 1.18.3
@@ -5,6 +5,6 @@ module Capistrano
5
5
  # GitCopy
6
6
  module GitCopy
7
7
  # gem version
8
- VERSION = '1.5.2'
8
+ VERSION = '1.5.3'
9
9
  end
10
10
  end
@@ -32,7 +32,7 @@ from subprocess import CalledProcessError, Popen, PIPE
32
32
  import sys
33
33
  import re
34
34
 
35
- __version__ = "1.18.3"
35
+ __version__ = "1.19.1"
36
36
 
37
37
 
38
38
  try:
@@ -121,7 +121,9 @@ class GitArchiver(object):
121
121
  self.force_sub = force_sub
122
122
  self.main_repo_abspath = main_repo_abspath
123
123
 
124
- def create(self, output_path, dry_run=False, output_format=None):
124
+ self._check_attr_gens = {}
125
+
126
+ def create(self, output_path, dry_run=False, output_format=None, compresslevel=None):
125
127
  """
126
128
  Create the archive at output_file_path.
127
129
 
@@ -147,7 +149,13 @@ class GitArchiver(object):
147
149
  if output_format in self.ZIPFILE_FORMATS:
148
150
  from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED
149
151
 
150
- archive = ZipFile(path.abspath(output_path), 'w')
152
+ if compresslevel is not None:
153
+ if sys.version_info > (3, 7):
154
+ archive = ZipFile(path.abspath(output_path), 'w', compresslevel=compresslevel)
155
+ else:
156
+ raise ValueError("Compression level for zip archives requires Python 3.7+")
157
+ else:
158
+ archive = ZipFile(path.abspath(output_path), 'w')
151
159
 
152
160
  def add_file(file_path, arcname):
153
161
  if not path.islink(file_path):
@@ -161,7 +169,14 @@ class GitArchiver(object):
161
169
  import tarfile
162
170
 
163
171
  mode = self.TARFILE_FORMATS[output_format]
164
- archive = tarfile.open(path.abspath(output_path), mode)
172
+
173
+ if compresslevel is not None:
174
+ try:
175
+ archive = tarfile.open(path.abspath(output_path), mode, compresslevel=compresslevel)
176
+ except TypeError:
177
+ raise ValueError("{0} cannot be compressed".format(output_format))
178
+ else:
179
+ archive = tarfile.open(path.abspath(output_path), mode)
165
180
 
166
181
  def add_file(file_path, arcname):
167
182
  archive.add(file_path, arcname)
@@ -195,15 +210,9 @@ class GitArchiver(object):
195
210
  @return: True if file should be excluded. Otherwise False.
196
211
  @rtype: bool
197
212
  """
198
- out = self.run_git_shell(
199
- 'git check-attr -z export-ignore -- %s' % quote(repo_file_path),
200
- cwd=repo_abspath
201
- ).split('\0')
202
-
203
- try:
204
- return out[2] == 'set'
205
- except IndexError:
206
- return False
213
+ next(self._check_attr_gens[repo_abspath])
214
+ attrs = self._check_attr_gens[repo_abspath].send(repo_file_path)
215
+ return attrs['export-ignore'] == 'set'
207
216
 
208
217
  def archive_all_files(self, archiver):
209
218
  """
@@ -235,55 +244,73 @@ class GitArchiver(object):
235
244
  @rtype: Iterable
236
245
  """
237
246
  repo_abspath = path.join(self.main_repo_abspath, repo_path)
238
- repo_file_paths = self.run_git_shell(
239
- 'git ls-files -z --cached --full-name --no-empty-directory',
240
- repo_abspath
241
- ).split('\0')[:-1]
247
+ assert repo_abspath not in self._check_attr_gens
248
+ self._check_attr_gens[repo_abspath] = self.check_attr(repo_abspath, ['export-ignore'])
242
249
 
243
- for repo_file_path in repo_file_paths:
244
- repo_file_abspath = path.join(repo_abspath, repo_file_path) # absolute file path
245
- main_repo_file_path = path.join(repo_path, repo_file_path) # file path relative to the main repo
250
+ try:
251
+ repo_file_paths = self.run_git_shell(
252
+ 'git ls-files -z --cached --full-name --no-empty-directory',
253
+ repo_abspath
254
+ ).split('\0')[:-1]
246
255
 
247
- # Only list symlinks and files.
248
- if not path.islink(repo_file_abspath) and path.isdir(repo_file_abspath):
249
- continue
256
+ for repo_file_path in repo_file_paths:
257
+ repo_file_abspath = path.join(repo_abspath, repo_file_path) # absolute file path
258
+ main_repo_file_path = path.join(repo_path, repo_file_path) # relative to main_repo_abspath
250
259
 
251
- if self.is_file_excluded(repo_abspath, repo_file_path):
252
- continue
260
+ # Only list symlinks and files.
261
+ if not path.islink(repo_file_abspath) and path.isdir(repo_file_abspath):
262
+ continue
253
263
 
254
- yield main_repo_file_path
264
+ if self.is_file_excluded(repo_abspath, repo_file_path):
265
+ continue
255
266
 
256
- if self.force_sub:
257
- self.run_git_shell('git submodule init', repo_abspath)
258
- self.run_git_shell('git submodule update', repo_abspath)
267
+ yield main_repo_file_path
259
268
 
260
- try:
261
- repo_gitmodules_abspath = path.join(repo_abspath, ".gitmodules")
269
+ if self.force_sub:
270
+ self.run_git_shell('git submodule init', repo_abspath)
271
+ self.run_git_shell('git submodule update', repo_abspath)
262
272
 
263
- with open(repo_gitmodules_abspath) as f:
264
- lines = f.readlines()
273
+ try:
274
+ repo_gitmodules_abspath = path.join(repo_abspath, ".gitmodules")
265
275
 
266
- for l in lines:
267
- m = re.match("^\\s*path\\s*=\\s*(.*)\\s*$", l)
276
+ with open(repo_gitmodules_abspath) as f:
277
+ lines = f.readlines()
268
278
 
269
- if m:
270
- repo_submodule_path = m.group(1) # relative to repo_path
271
- main_repo_submodule_path = path.join(repo_path, repo_submodule_path) # relative to main_repo_abspath
279
+ for l in lines:
280
+ m = re.match("^\\s*path\\s*=\\s*(.*)\\s*$", l)
272
281
 
273
- if self.is_file_excluded(repo_abspath, repo_submodule_path):
274
- continue
282
+ if m:
283
+ repo_submodule_path = m.group(1) # relative to repo_path
284
+ main_repo_submodule_path = path.join(repo_path, repo_submodule_path) # relative to main_repo_abspath
275
285
 
276
- for main_repo_submodule_file_path in self.walk_git_files(main_repo_submodule_path):
277
- repo_submodule_file_path = main_repo_submodule_file_path.replace(repo_path, "", 1).strip("/")
278
- if self.is_file_excluded(repo_abspath, repo_submodule_file_path):
286
+ if self.is_file_excluded(repo_abspath, repo_submodule_path):
279
287
  continue
280
288
 
281
- yield main_repo_submodule_file_path
282
- except IOError:
283
- pass
289
+ for main_repo_submodule_file_path in self.walk_git_files(main_repo_submodule_path):
290
+ repo_submodule_file_path = main_repo_submodule_file_path.replace(repo_path, "", 1).strip("/") # relative to repo_path
291
+ if self.is_file_excluded(repo_abspath, repo_submodule_file_path):
292
+ continue
293
+
294
+ yield main_repo_submodule_file_path
295
+ except IOError:
296
+ pass
297
+ finally:
298
+ self._check_attr_gens[repo_abspath].close()
299
+ del self._check_attr_gens[repo_abspath]
300
+
301
+ @classmethod
302
+ def decode_git_output(cls, output):
303
+ """
304
+ Decode Git's binary output handeling the way it escapes unicode characters.
305
+
306
+ @type output: bytes
307
+
308
+ @rtype: str
309
+ """
310
+ return output.decode('unicode_escape').encode('raw_unicode_escape').decode('utf-8')
284
311
 
285
- @staticmethod
286
- def run_git_shell(cmd, cwd=None):
312
+ @classmethod
313
+ def run_git_shell(cls, cmd, cwd=None):
287
314
  """
288
315
  Runs git shell command, reads output and decodes it into unicode string.
289
316
 
@@ -300,7 +327,7 @@ class GitArchiver(object):
300
327
  """
301
328
  p = Popen(cmd, shell=True, stdout=PIPE, cwd=cwd)
302
329
  output, _ = p.communicate()
303
- output = output.decode('unicode_escape').encode('raw_unicode_escape').decode('utf-8')
330
+ output = cls.decode_git_output(output)
304
331
 
305
332
  if p.returncode:
306
333
  if sys.version_info > (2, 6):
@@ -310,13 +337,82 @@ class GitArchiver(object):
310
337
 
311
338
  return output
312
339
 
340
+ @classmethod
341
+ def check_attr(cls, repo_abspath, attrs):
342
+ """
343
+ Generator that returns attributes for given paths relative to repo_abspath.
344
+
345
+ >>> g = GitArchiver.check_attr('repo_path', ['export-ignore'])
346
+ >>> next(g)
347
+ >>> attrs = g.send('relative_path')
348
+ >>> print(attrs['export-ignore'])
349
+
350
+ @param repo_abspath: Absolute path to a git repository.
351
+ @type repo_abspath: str
352
+
353
+ @param attrs: Attributes to check.
354
+ @type attrs: [str]
355
+
356
+ @rtype: generator
357
+ """
358
+ def make_process():
359
+ cmd = 'GIT_FLUSH=1 git check-attr --stdin -z {0}'.format(' '.join(attrs))
360
+ return Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, cwd=repo_abspath)
361
+
362
+ def read_attrs(process, repo_file_path):
363
+ process.stdin.write(repo_file_path.encode('utf-8') + b'\0')
364
+ process.stdin.flush()
365
+
366
+ # For every attribute check-attr will output: <path> NUL <attribute> NUL <info> NUL
367
+ path, attr, info = b'', b'', b''
368
+ nuls_count = 0
369
+ nuls_expected = 3 * len(attrs)
370
+
371
+ while nuls_count != nuls_expected:
372
+ b = process.stdout.read(1)
373
+
374
+ if b == b'' and process.poll() is not None:
375
+ raise RuntimeError("check-attr exited prematurely")
376
+ elif b == b'\0':
377
+ nuls_count += 1
378
+
379
+ if nuls_count % 3 == 0:
380
+ yield map(cls.decode_git_output, (path, attr, info))
381
+ path, attr, info = b'', b'', b''
382
+ elif nuls_count % 3 == 0:
383
+ path += b
384
+ elif nuls_count % 3 == 1:
385
+ attr += b
386
+ elif nuls_count % 3 == 2:
387
+ info += b
388
+
389
+ if not attrs:
390
+ return
391
+
392
+ process = make_process()
393
+
394
+ try:
395
+ while True:
396
+ repo_file_path = yield
397
+ repo_file_attrs = {}
398
+
399
+ for path, attr, value in read_attrs(process, repo_file_path):
400
+ assert path == repo_file_path
401
+ assert attr in attrs
402
+ repo_file_attrs[attr] = value
403
+
404
+ yield repo_file_attrs
405
+ finally:
406
+ process.stdin.close()
407
+ process.wait()
408
+
313
409
 
314
410
  def main():
315
- from optparse import OptionParser
411
+ from optparse import OptionParser, SUPPRESS_HELP
316
412
 
317
413
  parser = OptionParser(
318
414
  usage="usage: %prog [-v] [--prefix PREFIX] [--no-exclude] [--force-submodules]"
319
- " [--extra EXTRA1 [EXTRA2]] [--dry-run] OUTPUT_FILE",
415
+ " [--extra EXTRA1 ...] [--dry-run] [-0 | ... | -9] OUTPUT_FILE",
320
416
  version="%prog {0}".format(__version__)
321
417
  )
322
418
 
@@ -355,6 +451,13 @@ def main():
355
451
  dest='dry_run',
356
452
  help="don't actually archive anything, just show what would be done")
357
453
 
454
+ for i in range(10):
455
+ parser.add_option('-{0}'.format(i),
456
+ action='store_const',
457
+ const=i,
458
+ dest='compresslevel',
459
+ help=SUPPRESS_HELP)
460
+
358
461
  options, args = parser.parse_args()
359
462
 
360
463
  if len(args) != 1:
@@ -386,7 +489,7 @@ def main():
386
489
  options.exclude,
387
490
  options.force_sub,
388
491
  options.extra)
389
- archiver.create(output_file_path, options.dry_run)
492
+ archiver.create(output_file_path, options.dry_run, compresslevel=options.compresslevel)
390
493
  except Exception as e:
391
494
  parser.exit(2, "{0}\n".format(e))
392
495
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capistrano-git-copy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.2
4
+ version: 1.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Schwab
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-07 00:00:00.000000000 Z
11
+ date: 2018-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: capistrano