fig 0.1.67 → 0.1.69
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.
- data/Changes +63 -1
- data/lib/fig.rb +1 -1
- data/lib/fig/command.rb +59 -24
- data/lib/fig/command/action.rb +7 -0
- data/lib/fig/command/action/publish.rb +6 -2
- data/lib/fig/command/action/publish_local.rb +5 -1
- data/lib/fig/command/action/role/publish.rb +34 -13
- data/lib/fig/command/action/version.rb +5 -1
- data/lib/fig/command/options.rb +153 -261
- data/lib/fig/command/options/parser.rb +187 -0
- data/lib/fig/logging.rb +2 -2
- data/lib/fig/operating_system.rb +1 -1
- data/lib/fig/package.rb +6 -6
- data/lib/fig/parser.rb +4 -3
- data/lib/fig/repository.rb +33 -247
- data/lib/fig/repository_package_publisher.rb +318 -0
- data/lib/fig/update_lock.rb +68 -0
- data/lib/fig/working_directory_maintainer.rb +66 -32
- metadata +29 -26
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'set'
|
|
3
|
+
require 'socket'
|
|
4
|
+
require 'sys/admin'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
|
|
7
|
+
require 'fig'
|
|
8
|
+
require 'fig/at_exit'
|
|
9
|
+
require 'fig/logging'
|
|
10
|
+
require 'fig/not_found_error'
|
|
11
|
+
require 'fig/package_cache'
|
|
12
|
+
require 'fig/package_descriptor'
|
|
13
|
+
require 'fig/parser'
|
|
14
|
+
require 'fig/repository'
|
|
15
|
+
require 'fig/repository_error'
|
|
16
|
+
require 'fig/statement/archive'
|
|
17
|
+
require 'fig/statement/resource'
|
|
18
|
+
|
|
19
|
+
module Fig; end
|
|
20
|
+
|
|
21
|
+
class Fig::RepositoryPackagePublisher
|
|
22
|
+
attr_accessor :operating_system
|
|
23
|
+
attr_accessor :publish_listeners
|
|
24
|
+
attr_accessor :package_statements
|
|
25
|
+
attr_accessor :descriptor
|
|
26
|
+
attr_accessor :source_package
|
|
27
|
+
attr_accessor :was_forced
|
|
28
|
+
attr_accessor :base_temp_dir
|
|
29
|
+
attr_accessor :local_dir_for_package
|
|
30
|
+
attr_accessor :remote_dir_for_package
|
|
31
|
+
attr_accessor :local_fig_file_for_package
|
|
32
|
+
attr_accessor :remote_fig_file_for_package
|
|
33
|
+
attr_accessor :local_only
|
|
34
|
+
|
|
35
|
+
def publish_package()
|
|
36
|
+
derive_publish_metadata()
|
|
37
|
+
validate_asset_names()
|
|
38
|
+
|
|
39
|
+
temp_dir = publish_temp_dir()
|
|
40
|
+
@operating_system.delete_and_recreate_directory(temp_dir)
|
|
41
|
+
@operating_system.delete_and_recreate_directory(@local_dir_for_package)
|
|
42
|
+
|
|
43
|
+
fig_file = File.join(temp_dir, Fig::Repository::PACKAGE_FILE_IN_REPO)
|
|
44
|
+
content = publish_package_content_and_derive_definition_file()
|
|
45
|
+
@operating_system.write(fig_file, content)
|
|
46
|
+
|
|
47
|
+
if not @local_only
|
|
48
|
+
@operating_system.upload(fig_file, remote_fig_file_for_package())
|
|
49
|
+
end
|
|
50
|
+
@operating_system.copy(fig_file, local_fig_file_for_package())
|
|
51
|
+
|
|
52
|
+
notify_listeners
|
|
53
|
+
|
|
54
|
+
FileUtils.rm_rf(temp_dir)
|
|
55
|
+
|
|
56
|
+
return true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def validate_asset_names()
|
|
62
|
+
asset_statements = @package_statements.select { |s| s.is_asset? }
|
|
63
|
+
|
|
64
|
+
asset_names = Set.new()
|
|
65
|
+
asset_statements.each do
|
|
66
|
+
|statement|
|
|
67
|
+
|
|
68
|
+
asset_name = statement.asset_name()
|
|
69
|
+
if not asset_name.nil?
|
|
70
|
+
if asset_name == Fig::Repository::RESOURCES_FILE
|
|
71
|
+
Fig::Logging.fatal \
|
|
72
|
+
%Q<You cannot have an asset with the name "#{Fig::Repository::RESOURCES_FILE}"#{statement.position_string()} due to Fig implementation details.>
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if asset_names.include?(asset_name)
|
|
76
|
+
Fig::Logging.fatal \
|
|
77
|
+
%Q<Found multiple archives with the name "#{asset_name}"#{statement.position_string()}. If these were allowed, archives would overwrite each other.>
|
|
78
|
+
raise Fig::RepositoryError.new
|
|
79
|
+
else
|
|
80
|
+
asset_names.add(asset_name)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def derive_publish_metadata()
|
|
87
|
+
@publish_time = Time.now()
|
|
88
|
+
@publish_login = Sys::Admin.get_login()
|
|
89
|
+
@publish_host = Socket.gethostname()
|
|
90
|
+
|
|
91
|
+
return
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def publish_package_content_and_derive_definition_file()
|
|
95
|
+
@definition_file_lines = []
|
|
96
|
+
|
|
97
|
+
add_package_metadata_comments()
|
|
98
|
+
publish_package_content()
|
|
99
|
+
add_unparsed_text()
|
|
100
|
+
|
|
101
|
+
@definition_file_lines.flatten!
|
|
102
|
+
file_content = @definition_file_lines.join("\n")
|
|
103
|
+
file_content.gsub!(/\n{3,}/, "\n\n")
|
|
104
|
+
|
|
105
|
+
return file_content.strip() + "\n"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def add_package_metadata_comments()
|
|
109
|
+
@definition_file_lines <<
|
|
110
|
+
%Q<# Publishing information for #{@descriptor.to_string()}:>
|
|
111
|
+
@definition_file_lines << %q<#>
|
|
112
|
+
|
|
113
|
+
@definition_file_lines <<
|
|
114
|
+
%Q<# Time: #{@publish_time} (epoch: #{@publish_time.to_i()})>
|
|
115
|
+
|
|
116
|
+
@definition_file_lines << %Q<# User: #{@publish_login}>
|
|
117
|
+
@definition_file_lines << %Q<# Host: #{@publish_host}>
|
|
118
|
+
@definition_file_lines << %Q<# Args: "#{ARGV.join %q[", "]}">
|
|
119
|
+
@definition_file_lines << %Q<# Fig: v#{Fig::VERSION}>
|
|
120
|
+
@definition_file_lines << %q<#>
|
|
121
|
+
|
|
122
|
+
asset_statements =
|
|
123
|
+
@package_statements.select { |statement| statement.is_asset? }
|
|
124
|
+
asset_strings =
|
|
125
|
+
asset_statements.collect { |statement| statement.unparse('# ') }
|
|
126
|
+
|
|
127
|
+
if asset_strings.empty?
|
|
128
|
+
@definition_file_lines <<
|
|
129
|
+
%q<# There were no asset statements in the unpublished package definition.>
|
|
130
|
+
else
|
|
131
|
+
@definition_file_lines << %q<# Original asset statements: >
|
|
132
|
+
@definition_file_lines << %q<#>
|
|
133
|
+
@definition_file_lines << asset_strings
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
@definition_file_lines << %Q<\n>
|
|
137
|
+
|
|
138
|
+
return
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Deals with Archive and Resource statements. It downloads any remote
|
|
142
|
+
# files (those where the statement references a URL as opposed to a local
|
|
143
|
+
# file) and then copies all files into the local repository and the remote
|
|
144
|
+
# repository (if not a local-only publish).
|
|
145
|
+
def publish_package_content()
|
|
146
|
+
initialize_statements_to_publish()
|
|
147
|
+
create_resource_archive()
|
|
148
|
+
|
|
149
|
+
@statements_to_publish.each do
|
|
150
|
+
|statement|
|
|
151
|
+
|
|
152
|
+
if statement.is_asset?
|
|
153
|
+
publish_asset(statement)
|
|
154
|
+
else
|
|
155
|
+
@definition_file_lines << statement.unparse('')
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
return
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def initialize_statements_to_publish()
|
|
163
|
+
@resource_paths = []
|
|
164
|
+
|
|
165
|
+
@statements_to_publish = @package_statements.reject do |statement|
|
|
166
|
+
if (
|
|
167
|
+
statement.is_a?(Fig::Statement::Resource) &&
|
|
168
|
+
! Fig::Repository.is_url?(statement.url)
|
|
169
|
+
)
|
|
170
|
+
@resource_paths << statement.url
|
|
171
|
+
true
|
|
172
|
+
else
|
|
173
|
+
false
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
return
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def create_resource_archive()
|
|
181
|
+
if @resource_paths.size > 0
|
|
182
|
+
asset_paths = expand_globs_from(@resource_paths)
|
|
183
|
+
check_asset_paths(asset_paths)
|
|
184
|
+
|
|
185
|
+
file = Fig::Repository::RESOURCES_FILE
|
|
186
|
+
@operating_system.create_archive(file, asset_paths)
|
|
187
|
+
Fig::AtExit.add { File.delete(file) }
|
|
188
|
+
|
|
189
|
+
@statements_to_publish.unshift(
|
|
190
|
+
Fig::Statement::Archive.new(nil, nil, file)
|
|
191
|
+
)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
return
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def publish_asset(asset_statement)
|
|
198
|
+
asset_name = asset_statement.asset_name()
|
|
199
|
+
asset_remote = "#{remote_dir_for_package()}/#{asset_name}"
|
|
200
|
+
|
|
201
|
+
if Fig::Repository.is_url?(asset_statement.url)
|
|
202
|
+
asset_local = File.join(publish_temp_dir(), asset_name)
|
|
203
|
+
|
|
204
|
+
begin
|
|
205
|
+
@operating_system.download(asset_statement.url, asset_local)
|
|
206
|
+
rescue Fig::NotFoundError
|
|
207
|
+
Fig::Logging.fatal "Could not download #{asset_statement.url}."
|
|
208
|
+
raise Fig::RepositoryError.new
|
|
209
|
+
end
|
|
210
|
+
else
|
|
211
|
+
asset_local = asset_statement.url
|
|
212
|
+
check_asset_path(asset_local)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
if not @local_only
|
|
216
|
+
@operating_system.upload(
|
|
217
|
+
asset_local, asset_remote
|
|
218
|
+
)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
@operating_system.copy(
|
|
222
|
+
asset_local, @local_dir_for_package + '/' + asset_name
|
|
223
|
+
)
|
|
224
|
+
if asset_statement.is_a?(Fig::Statement::Archive)
|
|
225
|
+
@operating_system.unpack_archive(@local_dir_for_package, asset_name)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
@definition_file_lines <<
|
|
229
|
+
asset_statement.class.new(nil, nil, asset_name).unparse('')
|
|
230
|
+
|
|
231
|
+
return
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def add_unparsed_text()
|
|
235
|
+
if @source_package && @source_package.unparsed_text
|
|
236
|
+
|
|
237
|
+
@definition_file_lines << ''
|
|
238
|
+
@definition_file_lines << '# Original, unparsed package text:'
|
|
239
|
+
@definition_file_lines << '# '
|
|
240
|
+
@definition_file_lines << @source_package.unparsed_text.gsub(/^/, '# ')
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
return
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def notify_listeners()
|
|
247
|
+
publish_information = {}
|
|
248
|
+
publish_information[:descriptor] = @descriptor
|
|
249
|
+
publish_information[:time] = @publish_time
|
|
250
|
+
publish_information[:login] = @publish_login
|
|
251
|
+
publish_information[:host] = @publish_host
|
|
252
|
+
|
|
253
|
+
# Ensure that we've really got booleans and not merely true or false
|
|
254
|
+
# values.
|
|
255
|
+
publish_information[:was_forced] = @was_forced ? true : false
|
|
256
|
+
publish_information[:local_only] = @local_only ? true : false
|
|
257
|
+
|
|
258
|
+
publish_information[:local_destination] = @local_dir_for_package
|
|
259
|
+
publish_information[:remote_destination] = @remote_dir_for_package
|
|
260
|
+
|
|
261
|
+
@publish_listeners.each do
|
|
262
|
+
|listener|
|
|
263
|
+
|
|
264
|
+
listener.published(publish_information)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
return
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def publish_temp_dir()
|
|
271
|
+
File.join(base_temp_dir(), 'publish')
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def check_asset_path(asset_path)
|
|
275
|
+
if not File.exist?(asset_path)
|
|
276
|
+
Fig::Logging.fatal "Could not find file #{asset_path}."
|
|
277
|
+
raise Fig::RepositoryError.new
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
return
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def check_asset_paths(asset_paths)
|
|
284
|
+
non_existing_paths =
|
|
285
|
+
asset_paths.select {|path| ! File.exist?(path) && ! File.symlink?(path) }
|
|
286
|
+
|
|
287
|
+
if not non_existing_paths.empty?
|
|
288
|
+
if non_existing_paths.size > 1
|
|
289
|
+
Fig::Logging.fatal "Could not find files: #{ non_existing_paths.join(', ') }"
|
|
290
|
+
else
|
|
291
|
+
Fig::Logging.fatal "Could not find file #{non_existing_paths[0]}."
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
raise Fig::RepositoryError.new
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
return
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# 'resources' is an Array of fileglob patterns: ['tmp/foo/file1',
|
|
301
|
+
# 'tmp/foo/*.jar']
|
|
302
|
+
def expand_globs_from(resources)
|
|
303
|
+
expanded_files = []
|
|
304
|
+
|
|
305
|
+
resources.each do
|
|
306
|
+
|path|
|
|
307
|
+
|
|
308
|
+
globbed_files = Dir.glob(path)
|
|
309
|
+
if globbed_files.empty?
|
|
310
|
+
expanded_files << path
|
|
311
|
+
else
|
|
312
|
+
expanded_files.concat(globbed_files)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
return expanded_files
|
|
317
|
+
end
|
|
318
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
require 'fig/user_input_error'
|
|
4
|
+
|
|
5
|
+
module Fig; end
|
|
6
|
+
|
|
7
|
+
class Fig::UpdateLock
|
|
8
|
+
def initialize(lock_directory, response)
|
|
9
|
+
set_up_lock(lock_directory, response)
|
|
10
|
+
|
|
11
|
+
return
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def close()
|
|
15
|
+
@lock.close
|
|
16
|
+
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def set_up_lock(lock_directory, response)
|
|
23
|
+
FileUtils.mkdir_p(lock_directory)
|
|
24
|
+
|
|
25
|
+
# Tried using the directory itself as the lock, but Windows is
|
|
26
|
+
# non-cooperative.
|
|
27
|
+
lock_file = lock_directory + '/lock'
|
|
28
|
+
|
|
29
|
+
# Yes, there's a race condition here, but with the way Windows file locking
|
|
30
|
+
# works, it's better than a boot to the head.
|
|
31
|
+
if ! File.exists? lock_file
|
|
32
|
+
created_file = File.new(lock_file, 'w')
|
|
33
|
+
created_file.close
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
@lock = File.new(lock_file)
|
|
37
|
+
|
|
38
|
+
# *sigh* Ruby 1.8 doesn't support close_on_exec(), but we'll still use it
|
|
39
|
+
# if we can as a better attempt at safety.
|
|
40
|
+
if @lock.respond_to? :close_on_exec=
|
|
41
|
+
@lock.close_on_exec = true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if response == :wait
|
|
45
|
+
@lock.flock(File::LOCK_EX)
|
|
46
|
+
else
|
|
47
|
+
if ! @lock.flock(File::LOCK_EX | File::LOCK_NB)
|
|
48
|
+
raise_lock_usage_error(lock_directory)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
return
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def raise_lock_usage_error(lock_directory)
|
|
56
|
+
raise Fig::UserInputError.new(<<-END_MESSAGE)
|
|
57
|
+
Cannot update while another instance of Fig is updating #{lock_directory}.
|
|
58
|
+
|
|
59
|
+
You can tell Fig to wait for update with
|
|
60
|
+
|
|
61
|
+
fig --update --update-lock-response wait ...
|
|
62
|
+
|
|
63
|
+
or you can throw caution to the wind with
|
|
64
|
+
|
|
65
|
+
fig --update --update-lock-response ignore ...
|
|
66
|
+
END_MESSAGE
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -95,44 +95,78 @@ class Fig::WorkingDirectoryMaintainer
|
|
|
95
95
|
|
|
96
96
|
def copy(source, relpath)
|
|
97
97
|
target = File.join(@base_dir, relpath)
|
|
98
|
+
|
|
99
|
+
if source_and_target_are_same?(source, target)
|
|
100
|
+
# Actually happened: Retrieve and "set" both set to ".". Victim's current
|
|
101
|
+
# directory included a ".git" directory. Update was done and then later,
|
|
102
|
+
# an update with different dependencies. Fig proceeded to delete all
|
|
103
|
+
# files that had previously existed in the current directory, including
|
|
104
|
+
# out of the git repo. Whoops.
|
|
105
|
+
Fig::Logging.warn %Q<Skipping copying "#{source}" to itself.>
|
|
106
|
+
return
|
|
107
|
+
end
|
|
108
|
+
|
|
98
109
|
if File.directory?(source)
|
|
99
|
-
|
|
100
|
-
Fig::Logging.debug "Copying directory #{source} to #{target}."
|
|
101
|
-
Dir.foreach(source) do |child|
|
|
102
|
-
if child != '.' and child != '..'
|
|
103
|
-
source_file = File.join(source, child)
|
|
104
|
-
target_file = File.join(relpath, child)
|
|
105
|
-
copy(source_file, target_file)
|
|
106
|
-
end
|
|
107
|
-
end
|
|
110
|
+
copy_directory(source, relpath, target)
|
|
108
111
|
else
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
Fig::Logging.debug \
|
|
112
|
-
"Copying file from #{source} to #{target}."
|
|
113
|
-
else
|
|
114
|
-
Fig::Logging.info(
|
|
115
|
-
Fig::Logging::Colorizable.new(
|
|
116
|
-
"+ [#{formatted_meta()}] #{relpath}",
|
|
117
|
-
:green,
|
|
118
|
-
nil
|
|
119
|
-
)
|
|
120
|
-
)
|
|
121
|
-
end
|
|
122
|
-
FileUtils.mkdir_p(File.dirname(target))
|
|
112
|
+
copy_file(source, relpath, target)
|
|
113
|
+
end
|
|
123
114
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
preserve = File.exist?(source) && ! File.symlink?(source)
|
|
115
|
+
return
|
|
116
|
+
end
|
|
127
117
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
118
|
+
def source_and_target_are_same?(source, target)
|
|
119
|
+
# Ruby 1.8 doesn't have File.absolute_path(), so we have to fall back to
|
|
120
|
+
# .expand_path().
|
|
121
|
+
source_absolute = File.expand_path(source)
|
|
122
|
+
target_absolute = File.expand_path(target)
|
|
123
|
+
|
|
124
|
+
return source_absolute == target_absolute
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def copy_directory(source, relpath, target)
|
|
128
|
+
FileUtils.mkdir_p(target)
|
|
129
|
+
Fig::Logging.debug "Copying directory #{source} to #{target}."
|
|
130
|
+
|
|
131
|
+
Dir.foreach(source) do |child|
|
|
132
|
+
if child != '.' and child != '..'
|
|
133
|
+
source_file = File.join(source, child)
|
|
134
|
+
target_file = File.join(relpath, child)
|
|
135
|
+
copy(source_file, target_file)
|
|
131
136
|
end
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
return
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def copy_file(source, relpath, target)
|
|
143
|
+
if should_copy_file?(source, target)
|
|
144
|
+
if Fig::Logging.debug?
|
|
145
|
+
Fig::Logging.debug \
|
|
146
|
+
"Copying file from #{source} to #{target}."
|
|
147
|
+
else
|
|
148
|
+
Fig::Logging.info(
|
|
149
|
+
Fig::Logging::Colorizable.new(
|
|
150
|
+
"+ [#{formatted_meta()}] #{relpath}",
|
|
151
|
+
:green,
|
|
152
|
+
nil
|
|
153
|
+
)
|
|
154
|
+
)
|
|
135
155
|
end
|
|
156
|
+
FileUtils.mkdir_p(File.dirname(target))
|
|
157
|
+
|
|
158
|
+
# If the source is a dangling symlink, then there's no time, etc. to
|
|
159
|
+
# preserve.
|
|
160
|
+
preserve = File.exist?(source) && ! File.symlink?(source)
|
|
161
|
+
|
|
162
|
+
FileUtils.copy_entry(
|
|
163
|
+
source, target, preserve, false, :remove_destination
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
if @package_meta
|
|
168
|
+
@package_meta.add_file(relpath)
|
|
169
|
+
@package_meta.mark_as_retrieved()
|
|
136
170
|
end
|
|
137
171
|
|
|
138
172
|
return
|