capistrano-git-copy 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9843651d93a14f12fca78795fed3654ade0c30cc
4
- data.tar.gz: 706c6e4ae076521b794c31f0af831d9698ad1272
3
+ metadata.gz: 4071457c639166076052d94779f9fb7b78f67df6
4
+ data.tar.gz: 91fff803b0beaad67d114d7d9d9ba8def134abbd
5
5
  SHA512:
6
- metadata.gz: 0f63eb347f0d6ea7d28882e2d13e171e119c7108c7e9b528eb30553260c5decf2b56636bde0ff2e4e00ee81a58645d7164be496971061d082fdf5254ad1d404f
7
- data.tar.gz: 407bff892e724b1b0d257712477f9d2b974990855f3fd64f3e7227067b52dc5029a1fc4affec4b7a01f34eaf6fbbf2474b5325f9f11e1851af2960d9562d2829
6
+ metadata.gz: 715d74acefef227adf864d744af203628337ba5d229d7b93a0c6a2d99cf01f831d740eea0d0bd24d0172c6e5915ea92bb503e4232846c11974e4793e1d4cd892
7
+ data.tar.gz: bf787dbe3850e122baeda5f0eda224d561a6b2fe25e3ff9e95f4ceff2e97c93c5c4af9f67dfa92d691d084a78de0d8740846ef9f8eba23dc6967f94344dc4788
data/README.md CHANGED
@@ -6,30 +6,32 @@ Creates a tar archive from the locale git repository and uploads it to the remot
6
6
 
7
7
  * [git-archive-all](https://github.com/Kentzo/git-archive-all)
8
8
 
9
- ## Installation
9
+ ## Setup
10
10
 
11
- Add this line to your application's Gemfile:
11
+ Add the library to your `Gemfile`:
12
12
 
13
- gem 'capistrano', '~> 3.0.0'
14
- gem 'capistrano-git-copy'
13
+ ```ruby
14
+ group :development do
15
+ gem 'capistrano-git-copy', require: false
16
+ end
17
+ ```
15
18
 
16
- And then execute:
19
+ And load it in your `Capfile`:
17
20
 
18
- $ bundle
21
+ ```ruby
22
+ require 'capistrano/git/copy'
23
+ ```
19
24
 
20
- Or install it yourself as:
25
+ Now use `git_copy` as your SCM type in your `config/deploy.rb`:
21
26
 
22
- $ gem install capistrano-git-copy
23
-
24
- ## Usage
25
-
26
- Require in `Capfile` to use the default task:
27
+ set :scm, :git_copy
27
28
 
28
- require 'capistrano/git/copy'
29
+ ## Configuration
29
30
 
30
- Now use `git_copy` as your SCM type:
31
+ You can modify any of the following Capistrano variables in your deploy.rb config.
31
32
 
32
- set :scm, :git_copy
33
+ - `git_archive_all_bin` - Set git-archive-all command. Defaults to git-archive-all found in $PATH or to included version.
34
+ - `git_copy_tmp_path` - Temp path used to clone the repository and create archive.
33
35
 
34
36
  ## Contributing
35
37
 
@@ -1,2 +1,12 @@
1
+ module Capistrano
2
+ module Git
3
+ module Copy
4
+ def self.root_path
5
+ File.expand_path('../../../..', __FILE__)
6
+ end
7
+ end
8
+ end
9
+ end
10
+
1
11
  require 'capistrano/git/copy/version'
2
12
  require 'capistrano/git/copy/deploy'
@@ -1 +1,3 @@
1
+ require 'tmpdir'
2
+
1
3
  load File.expand_path("../tasks/deploy.cap", __FILE__)
@@ -1,14 +1,19 @@
1
- require 'tmpdir'
1
+ namespace :load do
2
+ task :defaults do
3
+ set :git_archive_all_bin, -> {
4
+ bin = `which git-archive-all`
5
+ bin.strip.length > 0 ? bin : File.join(Capistrano::Git::Copy.root_path, 'vendor', 'bin', 'git-archive-all')
6
+ }
7
+
8
+ set :git_copy_tmp_path, -> { File.join(Dir.tmpdir, "#{fetch(:application)}_#{fetch(:stage)}") }
9
+ end
10
+ end
2
11
 
3
12
  namespace :git_copy do
4
13
 
5
14
  task :wrapper do
6
15
  on roles :all do
7
- set :git_archive_all_bin, (fetch(:git_archive_all_bin) || `which git-archive-all`)
8
- fail "git-archive-all binary not found" if fetch(:git_archive_all_bin).strip == ''
9
-
10
- set :local_repo_path, File.join(Dir.tmpdir, "#{fetch(:application)}")
11
- set :local_tar_file, File.join(Dir.tmpdir, "#{fetch(:application)}.tar.gz")
16
+ fail "git-archive-all binary not found" if fetch(:git_archive_all_bin).strip.length == 0
12
17
  end
13
18
  end
14
19
 
@@ -17,25 +22,31 @@ namespace :git_copy do
17
22
 
18
23
  desc 'Create tar and upload to server'
19
24
  task update: :'git_copy:wrapper' do
20
- system("rm -rf #{fetch(:local_repo_path)}")
21
- system("git clone #{fetch(:repo_url)} #{fetch(:local_repo_path)} > /dev/null 2> /dev/null")
25
+ local_repo_path = File.join(fetch(:git_copy_tmp_path), 'repo')
26
+ local_tar_file = File.join(fetch(:git_copy_tmp_path), 'archive.tar.gz')
27
+
28
+ system("rm -rf #{fetch(:git_copy_tmp_path)}")
29
+ system("mkdir -p #{fetch(:git_copy_tmp_path)}")
30
+
31
+ system("git clone #{fetch(:repo_url)} #{local_repo_path}")
22
32
 
23
33
  on roles :all do
24
- system("cd #{fetch(:local_repo_path)} && git checkout #{fetch(:branch)} && git submodule init && git submodule update && git-archive-all __tmp.tar")
25
- system("cd #{fetch(:local_repo_path)} && tar -xf __tmp.tar && cd __tmp && tar -czf #{fetch(:local_tar_file)} .")
34
+ system("cd #{local_repo_path} && git checkout #{fetch(:branch)} && git submodule init && git submodule update && git-archive-all __tmp.tar")
35
+ system("cd #{local_repo_path} && tar -xf __tmp.tar && cd __tmp && tar -czf #{local_tar_file} .")
26
36
 
27
- upload! fetch(:local_tar_file), "#{fetch(:tmp_dir)}/#{File.basename(fetch(:local_tar_file))}"
37
+ upload!(local_tar_file, "#{fetch(:tmp_dir)}/#{fetch(:application)}_#{fetch(:stage)}.tar.gz")
28
38
 
29
- system("rm -rf #{fetch(:local_repo_path)}")
30
- system("rm -rf #{fetch(:local_tar_file)}")
39
+ system("rm -rf #{local_repo_path}")
40
+ system("rm -rf #{local_tar_file}")
31
41
  end
32
42
  end
33
43
 
34
44
  desc 'Extract tar to release path'
35
45
  task create_release: :'git_copy:update' do
36
46
  on roles :all do
37
- execute :mkdir, '-p', release_path
38
- execute :tar, '-f', "#{fetch(:tmp_dir)}/#{File.basename(fetch(:local_tar_file))}", '-x -C', release_path
47
+ execute(:mkdir, '-p', release_path)
48
+ execute(:tar, '-f', "#{fetch(:tmp_dir)}/#{fetch(:application)}_#{fetch(:stage)}.tar.gz", '-x -C', release_path)
49
+ execute(:rm, '-f', "#{fetch(:tmp_dir)}/#{fetch(:application)}_#{fetch(:stage)}.tar.gz")
39
50
  end
40
51
  end
41
52
  end
@@ -1,7 +1,7 @@
1
1
  module Capistrano
2
2
  module Git
3
3
  module Copy
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,494 @@
1
+ #! /usr/bin/env python
2
+ # coding=utf-8
3
+
4
+ from __future__ import print_function
5
+ from __future__ import unicode_literals
6
+
7
+ __version__ = "1.8"
8
+
9
+ import logging
10
+ from os import extsep, path, readlink
11
+ from subprocess import CalledProcessError, Popen, PIPE
12
+ import sys
13
+ import tarfile
14
+ from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED
15
+
16
+
17
+ class GitArchiver(object):
18
+ """
19
+ GitArchiver
20
+
21
+ Scan a git repository and export all tracked files, and submodules.
22
+ Checks for .gitattributes files in each directory and uses 'export-ignore'
23
+ pattern entries for ignore files in the archive.
24
+
25
+ >>> archiver = GitArchiver(main_repo_abspath='my/repo/path')
26
+ >>> archiver.create('output.zip')
27
+ """
28
+ LOG = logging.getLogger('GitArchiver')
29
+
30
+ def __init__(self, prefix='', exclude=True, force_sub=False, extra=None, main_repo_abspath=None):
31
+ """
32
+ @param prefix: Prefix used to prepend all paths in the resulting archive.
33
+ Extra file paths are only prefixed if they are not relative.
34
+ E.g. if prefix is 'foo' and extra is ['bar', '/baz'] the resulting archive will look like this:
35
+ /
36
+ baz
37
+ foo/
38
+ bar
39
+ @type prefix: string
40
+
41
+ @param exclude: Determines whether archiver should follow rules specified in .gitattributes files.
42
+ @type exclude: bool
43
+
44
+ @param force_sub: Determines whether submodules are initialized and updated before archiving.
45
+ @type force_sub: bool
46
+
47
+ @param extra: List of extra paths to include in the resulting archive.
48
+ @type extra: list
49
+
50
+ @param main_repo_abspath: Absolute path to the main repository (or one of subdirectories).
51
+ If given path is path to a subdirectory (but not a submodule directory!) it will be replaced
52
+ with abspath to top-level directory of the repository.
53
+ If None, current cwd is used.
54
+ @type main_repo_abspath: string
55
+ """
56
+ if extra is None:
57
+ extra = []
58
+
59
+ if main_repo_abspath is None:
60
+ main_repo_abspath = path.abspath('')
61
+ elif not path.isabs(main_repo_abspath):
62
+ raise ValueError("You MUST pass absolute path to the main git repository.")
63
+
64
+ try:
65
+ self.run_shell("[ -d .git ] || git rev-parse --git-dir > /dev/null 2>&1", main_repo_abspath)
66
+ except Exception as e:
67
+ raise ValueError("Not a git repository (or any of the parent directories).".format(path=main_repo_abspath))
68
+
69
+ main_repo_abspath = path.abspath(self.read_git_shell('git rev-parse --show-toplevel', main_repo_abspath).rstrip())
70
+
71
+ self.prefix = prefix
72
+ self.exclude = exclude
73
+ self.extra = extra
74
+ self.force_sub = force_sub
75
+ self.main_repo_abspath = main_repo_abspath
76
+
77
+ def create(self, output_path, dry_run=False, output_format=None):
78
+ """
79
+ Create the archive at output_file_path.
80
+
81
+ Type of the archive is determined either by extension of output_file_path or by output_format.
82
+ Supported formats are: gz, zip, bz2, tar, tgz
83
+
84
+ @param output_path: Output file path.
85
+ @type output_path: string
86
+
87
+ @param dry_run: Determines whether create should do nothing but print what it would archive.
88
+ @type dry_run: bool
89
+
90
+ @param output_format: Determines format of the output archive. If None, format is determined from extension
91
+ of output_file_path.
92
+ @type output_format: string
93
+ """
94
+ if output_format is None:
95
+ file_name, file_ext = path.splitext(output_path)
96
+ output_format = file_ext[len(extsep):].lower()
97
+ self.LOG.debug("Output format is not explicitly set, determined format is {0}.".format(output_format))
98
+
99
+ if not dry_run:
100
+ if output_format == 'zip':
101
+ archive = ZipFile(path.abspath(output_path), 'w')
102
+
103
+ def add_file(file_path, arcname):
104
+ if not path.islink(file_path):
105
+ archive.write(file_path, arcname, ZIP_DEFLATED)
106
+ else:
107
+ i = ZipInfo(arcname)
108
+ i.create_system = 3
109
+ i.external_attr = 0xA1ED0000
110
+ archive.writestr(i, readlink(file_path))
111
+ elif output_format in ['tar', 'bz2', 'gz', 'tgz']:
112
+ if output_format == 'tar':
113
+ t_mode = 'w'
114
+ elif output_format == 'tgz':
115
+ t_mode = 'w:gz'
116
+ else:
117
+ t_mode = 'w:{0}'.format(output_format)
118
+
119
+ archive = tarfile.open(path.abspath(output_path), t_mode)
120
+ add_file = lambda file_path, arcname: archive.add(file_path, arcname)
121
+ else:
122
+ raise RuntimeError("Unknown format: {0}".format(output_format))
123
+
124
+ def archiver(file_path, arcname):
125
+ self.LOG.debug("Compressing {0} => {1}...".format(file_path, arcname))
126
+ add_file(file_path, arcname)
127
+ else:
128
+ archive = None
129
+ archiver = lambda file_path, arcname: self.LOG.info("{0} => {1}".format(file_path, arcname))
130
+
131
+ self.archive_all_files(archiver)
132
+
133
+ if archive is not None:
134
+ archive.close()
135
+
136
+ def get_exclude_patterns(self, repo_abspath, repo_file_paths):
137
+ """
138
+ Returns exclude patterns for a given repo. It looks for .gitattributes files in repo_file_paths.
139
+
140
+ Resulting dictionary will contain exclude patterns per path (relative to the repo_abspath).
141
+ E.g. {('.', 'Catalyst', 'Editions', 'Base'), ['Foo*', '*Bar']}
142
+
143
+ @type repo_abspath: string
144
+ @param repo_abspath: Absolute path to the git repository.
145
+
146
+ @type repo_file_paths: list
147
+ @param repo_file_paths: List of paths relative to the repo_abspath that are under git control.
148
+
149
+ @rtype: dict
150
+ @return: Dictionary representing exclude patterns.
151
+ Keys are tuples of strings. Values are lists of strings.
152
+ Returns None if self.exclude is not set.
153
+ """
154
+ if not self.exclude:
155
+ return None
156
+
157
+ def read_attributes(attributes_abspath):
158
+ patterns = []
159
+ if path.isfile(attributes_abspath):
160
+ attributes = open(attributes_abspath, 'r').readlines()
161
+ patterns = []
162
+ for line in attributes:
163
+ tokens = line.strip().split()
164
+ if "export-ignore" in tokens[1:]:
165
+ patterns.append(tokens[0])
166
+ return patterns
167
+
168
+ exclude_patterns = {(): []}
169
+
170
+ # There may be no gitattributes.
171
+ try:
172
+ global_attributes_abspath = self.read_shell("git config --get core.attributesfile", repo_abspath).rstrip()
173
+ exclude_patterns[()] = read_attributes(global_attributes_abspath)
174
+ except:
175
+ # And it's valid to not have them.
176
+ pass
177
+
178
+ for attributes_abspath in [path.join(repo_abspath, f) for f in repo_file_paths if f.endswith(".gitattributes")]:
179
+ # Each .gitattributes affects only files within its directory.
180
+ key = tuple(self.get_path_components(repo_abspath, path.dirname(attributes_abspath)))
181
+ exclude_patterns[key] = read_attributes(attributes_abspath)
182
+
183
+ local_attributes_abspath = path.join(repo_abspath, ".git", "info", "attributes")
184
+ key = tuple(self.get_path_components(repo_abspath, repo_abspath))
185
+
186
+ if key in exclude_patterns:
187
+ exclude_patterns[key].extend(read_attributes(local_attributes_abspath))
188
+ else:
189
+ exclude_patterns[key] = read_attributes(local_attributes_abspath)
190
+
191
+ return exclude_patterns
192
+
193
+ def is_file_excluded(self, repo_abspath, repo_file_path, exclude_patterns):
194
+ """
195
+ Checks whether file at a given path is excluded.
196
+
197
+ @type repo_abspath: string
198
+ @param repo_abspath: Absolute path to the git repository.
199
+
200
+ @type repo_file_path: string
201
+ @param repo_file_path: Path to a file within repo_abspath.
202
+
203
+ @type exclude_patterns: dict
204
+ @param exclude_patterns: Exclude patterns with format specified for get_exclude_patterns.
205
+
206
+ @rtype: bool
207
+ @return: True if file should be excluded. Otherwise False.
208
+ """
209
+ if exclude_patterns is None or not len(exclude_patterns):
210
+ return False
211
+
212
+ from fnmatch import fnmatch
213
+
214
+ file_name = path.basename(repo_file_path)
215
+ components = self.get_path_components(repo_abspath, path.join(repo_abspath, path.dirname(repo_file_path)))
216
+
217
+ is_excluded = False
218
+ # We should check all patterns specified in intermediate directories to the given file.
219
+ # At the end we should also check for the global patterns (key '()' or empty tuple).
220
+ while not is_excluded:
221
+ key = tuple(components)
222
+ if key in exclude_patterns:
223
+ patterns = exclude_patterns[key]
224
+ for p in patterns:
225
+ if fnmatch(file_name, p) or fnmatch(repo_file_path, p):
226
+ self.LOG.debug("Exclude pattern matched {pattern}: {path}".format(pattern=p, path=repo_file_path))
227
+ is_excluded = True
228
+
229
+ if not len(components):
230
+ break
231
+
232
+ components.pop()
233
+
234
+ return is_excluded
235
+
236
+ def archive_all_files(self, archiver):
237
+ """
238
+ Archive all files using archiver.
239
+
240
+ @param archiver: Function that accepts 2 arguments: abspath to file on the system and relative path within archive.
241
+ """
242
+ for file_path in self.extra:
243
+ archiver(path.abspath(file_path), path.join(self.prefix, file_path))
244
+
245
+ for file_path in self.walk_git_files():
246
+ archiver(path.join(self.main_repo_abspath, file_path), path.join(self.prefix, file_path))
247
+
248
+ def walk_git_files(self, repo_path=''):
249
+ """
250
+ An iterator method that yields a file path relative to main_repo_abspath
251
+ for each file that should be included in the archive.
252
+ Skips those that match the exclusion patterns found in
253
+ any discovered .gitattributes files along the way.
254
+
255
+ Recurs into submodules as well.
256
+
257
+ @type repo_path: string
258
+ @param repo_path: Path to the git submodule repository relative to main_repo_abspath.
259
+
260
+ @rtype: iterator
261
+ @return: Iterator to traverse files under git control relative to main_repo_abspath.
262
+ """
263
+ repo_abspath = path.join(self.main_repo_abspath, repo_path)
264
+ repo_file_paths = self.read_git_shell("git ls-files --cached --full-name --no-empty-directory", repo_abspath).splitlines()
265
+ exclude_patterns = self.get_exclude_patterns(repo_abspath, repo_file_paths)
266
+
267
+ for repo_file_path in repo_file_paths:
268
+ # Git puts path in quotes if file path has unicode characters.
269
+ repo_file_path = repo_file_path.strip('"') # file path relative to current repo
270
+ file_name = path.basename(repo_file_path)
271
+
272
+ # Only list symlinks and files that don't start with git.
273
+ if file_name.startswith(".git") or (not path.islink(repo_file_path) and path.isdir(repo_file_path)):
274
+ continue
275
+
276
+ main_repo_file_path = path.join(repo_path, repo_file_path) # file path relative to the main repo
277
+
278
+ if self.is_file_excluded(repo_abspath, repo_file_path, exclude_patterns):
279
+ continue
280
+
281
+ yield main_repo_file_path
282
+
283
+ if self.force_sub:
284
+ self.run_shell("git submodule init", repo_abspath)
285
+ self.run_shell("git submodule update", repo_abspath)
286
+
287
+ for submodule_path in self.read_shell("git submodule --quiet foreach 'pwd'", repo_abspath).splitlines():
288
+ # Shell command returns absolute paths to submodules.
289
+ submodule_path = path.relpath(submodule_path, self.main_repo_abspath)
290
+ for file_path in self.walk_git_files(submodule_path):
291
+ yield file_path
292
+
293
+ @staticmethod
294
+ def get_path_components(repo_abspath, abspath):
295
+ """
296
+ Split given abspath into components relative to repo_abspath.
297
+ These components are primarily used as unique keys of files and folders within a repository.
298
+
299
+ E.g. if repo_abspath is '/Documents/Hobby/ParaView/' and abspath is
300
+ '/Documents/Hobby/ParaView/Catalyst/Editions/Base/', function will return:
301
+ ['.', 'Catalyst', 'Editions', 'Base']
302
+
303
+ First element is always '.' (concrete symbol depends on OS).
304
+
305
+ @param repo_abspath: Absolute path to the git repository. Normalized via os.path.normpath.
306
+ @type repo_abspath: string
307
+
308
+ @param abspath: Absolute path to a file within repo_abspath. Normalized via os.path.normpath.
309
+ @type abspath: string
310
+
311
+ @return: List of path components.
312
+ @rtype: list
313
+ """
314
+ repo_abspath = path.normpath(repo_abspath)
315
+ abspath = path.normpath(abspath)
316
+
317
+ if not path.isabs(repo_abspath):
318
+ raise ValueError("repo_abspath MUST be absolute path.")
319
+
320
+ if not path.isabs(abspath):
321
+ raise ValueError("abspath MUST be absoulte path.")
322
+
323
+ if not path.commonprefix([repo_abspath, abspath]):
324
+ raise ValueError("abspath (\"{0}\") MUST have common prefix with repo_abspath (\"{1}\")".format(abspath, repo_abspath))
325
+
326
+ components = []
327
+
328
+ while not abspath == repo_abspath:
329
+ abspath, tail = path.split(abspath)
330
+
331
+ if tail:
332
+ components.insert(0, tail)
333
+
334
+ # Get portable version of '.'
335
+ components.insert(0, path.relpath(repo_abspath, repo_abspath))
336
+ return components
337
+
338
+ @staticmethod
339
+ def run_shell(cmd, cwd=None):
340
+ """
341
+ Runs shell command.
342
+
343
+ @type cmd: string
344
+ @param cmd: Command to be executed.
345
+
346
+ @type cwd: string
347
+ @param cwd: Working directory.
348
+
349
+ @rtype: int
350
+ @return: Return code of the command.
351
+
352
+ @raise CalledProcessError: Raises exception if return code of the command is non-zero.
353
+ """
354
+ p = Popen(cmd, shell=True, cwd=cwd)
355
+ p.wait()
356
+
357
+ if p.returncode:
358
+ raise CalledProcessError(returncode=p.returncode, cmd=cmd)
359
+
360
+ return p.returncode
361
+
362
+ @staticmethod
363
+ def read_shell(cmd, cwd=None, encoding='utf-8'):
364
+ """
365
+ Runs shell command and reads output.
366
+
367
+ @type cmd: string
368
+ @param cmd: Command to be executed.
369
+
370
+ @type cwd: string
371
+ @param cwd: Working directory.
372
+
373
+ @type encoding: string
374
+ @param encoding: Encoding used to decode bytes returned by Popen into string.
375
+
376
+ @rtype: string
377
+ @return: Output of the command.
378
+
379
+ @raise CalledProcessError: Raises exception if return code of the command is non-zero.
380
+ """
381
+ p = Popen(cmd, shell=True, stdout=PIPE, cwd=cwd)
382
+ output, _ = p.communicate()
383
+ output = output.decode(encoding)
384
+
385
+ if p.returncode:
386
+ if sys.version_info > (2,6):
387
+ raise CalledProcessError(returncode=p.returncode, cmd=cmd, output=output)
388
+ else:
389
+ raise CalledProcessError(returncode=p.returncode, cmd=cmd)
390
+
391
+ return output
392
+
393
+ @staticmethod
394
+ def read_git_shell(cmd, cwd=None):
395
+ """
396
+ Runs git shell command, reads output and decodes it into unicode string
397
+
398
+ @type cmd: string
399
+ @param cmd: Command to be executed.
400
+
401
+ @type cwd: string
402
+ @param cwd: Working directory.
403
+
404
+ @rtype: string
405
+ @return: Output of the command.
406
+
407
+ @raise CalledProcessError: Raises exception if return code of the command is non-zero.
408
+ """
409
+ p = Popen(cmd, shell=True, stdout=PIPE, cwd=cwd)
410
+ output, _ = p.communicate()
411
+ output = output.decode('unicode_escape').encode('raw_unicode_escape').decode('utf-8')
412
+
413
+ if p.returncode:
414
+ if sys.version_info > (2,6):
415
+ raise CalledProcessError(returncode=p.returncode, cmd=cmd, output=output)
416
+ else:
417
+ raise CalledProcessError(returncode=p.returncode, cmd=cmd)
418
+
419
+ return output
420
+
421
+
422
+ if __name__ == '__main__':
423
+ from optparse import OptionParser
424
+
425
+ parser = OptionParser(usage="usage: %prog [-v] [--prefix PREFIX] [--no-exclude] [--force-submodules] [--extra EXTRA1 [EXTRA2]] [--dry-run] OUTPUT_FILE",
426
+ version="%prog {version}".format(version=__version__))
427
+
428
+ parser.add_option('--prefix',
429
+ type='string',
430
+ dest='prefix',
431
+ default='',
432
+ help="prepend PREFIX to each filename in the archive. OUTPUT_FILE name is used by default to avoid tarbomb")
433
+
434
+ parser.add_option('-v', '--verbose',
435
+ action='store_true',
436
+ dest='verbose',
437
+ help='enable verbose mode')
438
+
439
+ parser.add_option('--no-exclude',
440
+ action='store_false',
441
+ dest='exclude',
442
+ default=True,
443
+ help="don't read .gitattributes files for patterns containing export-ignore attrib")
444
+
445
+ parser.add_option('--force-submodules',
446
+ action='store_true',
447
+ dest='force_sub',
448
+ help="force a git submodule init && git submodule update at each level before iterating submodules")
449
+
450
+ parser.add_option('--extra',
451
+ action='append',
452
+ dest='extra',
453
+ default=[],
454
+ help="any additional files to include in the archive")
455
+
456
+ parser.add_option('--dry-run',
457
+ action='store_true',
458
+ dest='dry_run',
459
+ help="don't actually archive anything, just show what would be done")
460
+
461
+ options, args = parser.parse_args()
462
+
463
+ if len(args) != 1:
464
+ parser.error("You must specify exactly one output file")
465
+
466
+ output_file_path = args[0]
467
+
468
+ if path.isdir(output_file_path):
469
+ parser.error("You cannot use directory as output")
470
+
471
+ # avoid tarbomb
472
+ if options.prefix:
473
+ options.prefix = path.join(options.prefix, '')
474
+ else:
475
+ import re
476
+
477
+ output_name = path.basename(output_file_path)
478
+ output_name = re.sub('(\.zip|\.tar|\.tgz|\.gz|\.bz2|\.tar\.gz|\.tar\.bz2)$', '', output_name) or "Archive"
479
+ options.prefix = path.join(output_name, '')
480
+
481
+ try:
482
+ handler = logging.StreamHandler(sys.stdout)
483
+ handler.setFormatter(logging.Formatter('%(message)s'))
484
+ GitArchiver.LOG.addHandler(handler)
485
+ GitArchiver.LOG.setLevel(logging.DEBUG if options.verbose else logging.INFO)
486
+ archiver = GitArchiver(options.prefix,
487
+ options.exclude,
488
+ options.force_sub,
489
+ options.extra)
490
+ archiver.create(output_file_path, options.dry_run)
491
+ except Exception as e:
492
+ parser.exit(2, "{0}\n".format(e))
493
+
494
+ sys.exit(0)
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: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Schwab
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-09 00:00:00.000000000 Z
11
+ date: 2014-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: capistrano
@@ -71,6 +71,7 @@ files:
71
71
  - lib/capistrano/git/copy/tasks/deploy.cap
72
72
  - lib/capistrano/git/copy/version.rb
73
73
  - lib/capistrano/git_copy.rb
74
+ - vendor/bin/git-archive-all
74
75
  homepage: https://github.com/ydkn/capistrano-git-copy
75
76
  licenses:
76
77
  - MIT