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 +4 -4
- data/README.md +17 -15
- data/lib/capistrano/git/copy.rb +10 -0
- data/lib/capistrano/git/copy/deploy.rb +2 -0
- data/lib/capistrano/git/copy/tasks/deploy.cap +26 -15
- data/lib/capistrano/git/copy/version.rb +1 -1
- data/vendor/bin/git-archive-all +494 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4071457c639166076052d94779f9fb7b78f67df6
|
4
|
+
data.tar.gz: 91fff803b0beaad67d114d7d9d9ba8def134abbd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
##
|
9
|
+
## Setup
|
10
10
|
|
11
|
-
Add
|
11
|
+
Add the library to your `Gemfile`:
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
```ruby
|
14
|
+
group :development do
|
15
|
+
gem 'capistrano-git-copy', require: false
|
16
|
+
end
|
17
|
+
```
|
15
18
|
|
16
|
-
And
|
19
|
+
And load it in your `Capfile`:
|
17
20
|
|
18
|
-
|
21
|
+
```ruby
|
22
|
+
require 'capistrano/git/copy'
|
23
|
+
```
|
19
24
|
|
20
|
-
|
25
|
+
Now use `git_copy` as your SCM type in your `config/deploy.rb`:
|
21
26
|
|
22
|
-
|
23
|
-
|
24
|
-
## Usage
|
25
|
-
|
26
|
-
Require in `Capfile` to use the default task:
|
27
|
+
set :scm, :git_copy
|
27
28
|
|
28
|
-
|
29
|
+
## Configuration
|
29
30
|
|
30
|
-
|
31
|
+
You can modify any of the following Capistrano variables in your deploy.rb config.
|
31
32
|
|
32
|
-
|
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
|
|
data/lib/capistrano/git/copy.rb
CHANGED
@@ -1,14 +1,19 @@
|
|
1
|
-
|
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
|
-
|
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
|
-
|
21
|
-
|
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 #{
|
25
|
-
system("cd #{
|
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!
|
37
|
+
upload!(local_tar_file, "#{fetch(:tmp_dir)}/#{fetch(:application)}_#{fetch(:stage)}.tar.gz")
|
28
38
|
|
29
|
-
system("rm -rf #{
|
30
|
-
system("rm -rf #{
|
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
|
38
|
-
execute
|
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
|
@@ -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.
|
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-
|
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
|