fig 1.1.0 → 1.2.0

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.
Files changed (46) hide show
  1. data/Changes +94 -0
  2. data/lib/fig.rb +1 -1
  3. data/lib/fig/command.rb +18 -7
  4. data/lib/fig/command/action/dump_package_definition_for_command_line.rb +2 -2
  5. data/lib/fig/command/action/dump_package_definition_parsed.rb +2 -2
  6. data/lib/fig/command/action/dump_package_definition_text.rb +2 -2
  7. data/lib/fig/command/action/source_package.rb +65 -0
  8. data/lib/fig/command/options.rb +64 -15
  9. data/lib/fig/command/options/parser.rb +24 -14
  10. data/lib/fig/command/package_applier.rb +32 -7
  11. data/lib/fig/command/package_loader.rb +16 -7
  12. data/lib/fig/external_program.rb +72 -0
  13. data/lib/fig/figrc.rb +1 -1
  14. data/lib/fig/grammar/v0.rb +2 -2
  15. data/lib/fig/grammar/v0.treetop +2 -2
  16. data/lib/fig/grammar/v1.rb +17 -1737
  17. data/lib/fig/grammar/v1.treetop +6 -217
  18. data/lib/fig/grammar/v1_base.rb +1750 -0
  19. data/lib/fig/grammar/v1_base.treetop +229 -0
  20. data/lib/fig/grammar/v2.rb +508 -0
  21. data/lib/fig/grammar/v2.treetop +65 -0
  22. data/lib/fig/grammar_monkey_patches.rb +7 -0
  23. data/lib/fig/no_such_package_config_error.rb +3 -1
  24. data/lib/fig/not_yet_parsed_package.rb +27 -0
  25. data/lib/fig/operating_system.rb +5 -5
  26. data/lib/fig/package.rb +20 -2
  27. data/lib/fig/package_definition_text_assembler.rb +2 -1
  28. data/lib/fig/package_descriptor.rb +11 -4
  29. data/lib/fig/parser.rb +44 -58
  30. data/lib/fig/parser_package_build_state.rb +39 -4
  31. data/lib/fig/protocol/file.rb +2 -2
  32. data/lib/fig/protocol/ftp.rb +15 -10
  33. data/lib/fig/protocol/http.rb +1 -1
  34. data/lib/fig/protocol/netrc_enabled.rb +29 -16
  35. data/lib/fig/protocol/sftp.rb +19 -12
  36. data/lib/fig/repository.rb +33 -21
  37. data/lib/fig/repository_package_publisher.rb +129 -8
  38. data/lib/fig/runtime_environment.rb +114 -28
  39. data/lib/fig/statement/include.rb +21 -4
  40. data/lib/fig/statement/include_file.rb +94 -0
  41. data/lib/fig/unparser.rb +15 -7
  42. data/lib/fig/unparser/v1.rb +2 -80
  43. data/lib/fig/unparser/v1_base.rb +85 -0
  44. data/lib/fig/unparser/v2.rb +55 -0
  45. data/lib/fig/working_directory_maintainer.rb +12 -0
  46. metadata +61 -51
@@ -28,7 +28,7 @@ class Fig::Protocol::File
28
28
 
29
29
  # Determine whether we need to update something. Returns nil to indicate
30
30
  # "don't know".
31
- def path_up_to_date?(uri, path)
31
+ def path_up_to_date?(uri, path, prompt_for_login)
32
32
  begin
33
33
  unescaped_path = CGI.unescape uri.path
34
34
  if ::File.mtime(unescaped_path) <= ::File.mtime(path)
@@ -43,7 +43,7 @@ class Fig::Protocol::File
43
43
 
44
44
  # Returns whether the file was not downloaded because the file already
45
45
  # exists and is already up-to-date.
46
- def download(uri, path)
46
+ def download(uri, path, prompt_for_login)
47
47
  begin
48
48
  unescaped_path = CGI.unescape uri.path
49
49
  FileUtils.cp(unescaped_path, path)
@@ -17,11 +17,12 @@ class Fig::Protocol::FTP
17
17
 
18
18
  def initialize(login)
19
19
  @login = login
20
+ initialize_netrc
20
21
  end
21
22
 
22
23
  def download_list(uri)
23
24
  ftp = Net::FTP.new(uri.host)
24
- ftp_login(ftp, uri.host)
25
+ ftp_login(ftp, uri.host, :prompt_for_login)
25
26
  ftp.chdir(uri.path)
26
27
  dirs = ftp.nlst
27
28
  ftp.close
@@ -31,10 +32,10 @@ class Fig::Protocol::FTP
31
32
 
32
33
  # Determine whether we need to update something. Returns nil to indicate
33
34
  # "don't know".
34
- def path_up_to_date?(uri, path)
35
+ def path_up_to_date?(uri, path, prompt_for_login)
35
36
  begin
36
37
  ftp = Net::FTP.new(uri.host)
37
- ftp_login(ftp, uri.host)
38
+ ftp_login(ftp, uri.host, prompt_for_login)
38
39
 
39
40
  if ftp.mtime(uri.path) <= ::File.mtime(path)
40
41
  return true
@@ -52,10 +53,10 @@ class Fig::Protocol::FTP
52
53
 
53
54
  # Returns whether the file was not downloaded because the file already
54
55
  # exists and is already up-to-date.
55
- def download(uri, path)
56
+ def download(uri, path, prompt_for_login)
56
57
  begin
57
58
  ftp = Net::FTP.new(uri.host)
58
- ftp_login(ftp, uri.host)
59
+ ftp_login(ftp, uri.host, prompt_for_login)
59
60
 
60
61
  if ::File.exist?(path) && ftp.mtime(uri.path) <= ::File.mtime(path)
61
62
  Fig::Logging.debug "#{path} is up to date."
@@ -87,7 +88,7 @@ class Fig::Protocol::FTP
87
88
  # to, i.e. [1,2,3] - [2,3,4] = [1]
88
89
  remote_project_dirs = remote_publish_dirs - ftp_root_dirs
89
90
  Net::FTP.open(uri.host) do |ftp|
90
- ftp_login(ftp, uri.host)
91
+ ftp_login(ftp, uri.host, :prompt_for_login)
91
92
  # Assume that the FIG_REMOTE_URL path exists.
92
93
  ftp.chdir(ftp_root_path)
93
94
  remote_project_dirs.each do |dir|
@@ -105,11 +106,15 @@ class Fig::Protocol::FTP
105
106
 
106
107
  private
107
108
 
108
- def ftp_login(ftp, host)
109
+ def ftp_login(ftp, host, prompt_for_login)
109
110
  begin
110
111
  if @login
111
- load_authentication_for host
112
- ftp.login get_username, get_password
112
+ authentication = get_authentication_for host, prompt_for_login
113
+ if authentication
114
+ ftp.login authentication.username, authentication.password
115
+ else
116
+ ftp.login
117
+ end
113
118
  else
114
119
  ftp.login
115
120
  end
@@ -131,7 +136,7 @@ class Fig::Protocol::FTP
131
136
  threads << Thread.new do
132
137
  packages = all_packages[num]
133
138
  ftp = Net::FTP.new(uri.host)
134
- ftp_login(ftp, uri.host)
139
+ ftp_login(ftp, uri.host, :prompt_for_login)
135
140
  ftp.chdir(uri.path)
136
141
  pos = num
137
142
  while pos < dirs.length
@@ -15,7 +15,7 @@ class Fig::Protocol::HTTP
15
15
 
16
16
  # Returns whether the file was not downloaded because the file already
17
17
  # exists and is already up-to-date.
18
- def download(uri, path)
18
+ def download(uri, path, prompt_for_login)
19
19
  log_download(uri, path)
20
20
  ::File.open(path, 'wb') do |file|
21
21
  file.binmode
@@ -10,33 +10,46 @@ module Fig::Protocol; end
10
10
  module Fig::Protocol::NetRCEnabled
11
11
  private
12
12
 
13
- def get_username()
14
- @username ||= HighLine.new.ask('Username: ') { |q| q.echo = true }
15
- return @username
16
- end
13
+ NetRCEntry = Struct.new :username, :password
17
14
 
18
- def get_password()
19
- @password ||= HighLine.new.ask('Password: ') { |q| q.echo = false }
20
- return @password
21
- end
15
+ def initialize_netrc()
16
+ @netrc_entries_by_host = {}
22
17
 
23
- def load_authentication_for(host)
24
- return if @username || @password
18
+ return
19
+ end
25
20
 
26
- @username ||= ENV['FIG_USERNAME']
27
- @password ||= ENV['FIG_PASSWORD']
28
- return if @username || @password
21
+ def get_authentication_for(host, prompt_if_missing)
22
+ if @netrc_entries_by_host.include? host
23
+ return @netrc_entries_by_host[host]
24
+ end
29
25
 
26
+ entry = nil
30
27
  begin
31
28
  login_data = Net::Netrc.locate host
32
29
  if login_data
33
- @username = login_data.login
34
- @password = login_data.password
30
+ entry = NetRCEntry.new login_data.login, login_data.password
31
+ elsif prompt_if_missing
32
+ entry = get_authentication_from_user(host)
35
33
  end
36
34
  rescue SecurityError => error
37
35
  raise Fig::UserInputError.new error.message
38
36
  end
39
37
 
40
- return
38
+ @netrc_entries_by_host[host] = entry
39
+
40
+ return entry
41
+ end
42
+
43
+ def get_authentication_from_user(host)
44
+ username =
45
+ ENV['FIG_USERNAME'] ||
46
+ HighLine.new.ask("Username for #{host}: ") { |q| q.echo = true }
47
+ password =
48
+ ENV['FIG_PASSWORD'] ||
49
+ HighLine.new.ask("Password for #{username}@#{host}: ") {
50
+ |q| q.echo = false
51
+ }
52
+
53
+ return NetRCEntry.new username, password
41
54
  end
42
55
  end
@@ -14,10 +14,14 @@ class Fig::Protocol::SFTP
14
14
  include Fig::Protocol
15
15
  include Fig::Protocol::NetRCEnabled
16
16
 
17
+ def initialize()
18
+ initialize_netrc
19
+ end
20
+
17
21
  def download_list(uri)
18
22
  package_versions = []
19
23
 
20
- sftp_run(uri) do
24
+ sftp_run(uri, :prompt_for_login) do
21
25
  |connection|
22
26
 
23
27
  connection.dir.foreach uri.path do
@@ -48,11 +52,11 @@ class Fig::Protocol::SFTP
48
52
 
49
53
  # Determine whether we need to update something. Returns nil to indicate
50
54
  # "don't know".
51
- def path_up_to_date?(uri, path)
52
- sftp_run(uri) do
55
+ def path_up_to_date?(uri, path, prompt_for_login)
56
+ sftp_run(uri, prompt_for_login) do
53
57
  |connection|
54
58
 
55
- return connection.stat!(uri.path).mtime <= ::File.mtime(path)
59
+ return connection.stat!(uri.path).mtime.to_f <= ::File.mtime(path).to_f
56
60
  end
57
61
 
58
62
  return nil
@@ -60,8 +64,8 @@ class Fig::Protocol::SFTP
60
64
 
61
65
  # Returns whether the file was not downloaded because the file already
62
66
  # exists and is already up-to-date.
63
- def download(uri, path)
64
- sftp_run(uri) do
67
+ def download(uri, path, prompt_for_login)
68
+ sftp_run(uri, prompt_for_login) do
65
69
  |connection|
66
70
 
67
71
  begin
@@ -70,7 +74,7 @@ class Fig::Protocol::SFTP
70
74
  # when the remote path does not exist.
71
75
  stat = connection.stat!(uri.path)
72
76
 
73
- if ::File.exist?(path) && stat.mtime <= ::File.mtime(path)
77
+ if ::File.exist?(path) && stat.mtime.to_f <= ::File.mtime(path).to_f
74
78
  Fig::Logging.debug "#{path} is up to date."
75
79
  return false
76
80
  else
@@ -91,7 +95,7 @@ class Fig::Protocol::SFTP
91
95
  end
92
96
 
93
97
  def upload(local_file, uri)
94
- sftp_run(uri) do
98
+ sftp_run(uri, :prompt_for_login) do
95
99
  |connection|
96
100
 
97
101
  ensure_directory_exists connection, ::File.dirname(uri.path)
@@ -103,19 +107,22 @@ class Fig::Protocol::SFTP
103
107
 
104
108
  private
105
109
 
106
- def sftp_run(uri, &block)
110
+ def sftp_run(uri, prompt_for_login, &block)
107
111
  host = uri.host
108
112
 
109
- load_authentication_for host
113
+ authentication = get_authentication_for host, prompt_for_login
114
+ if ! authentication
115
+ raise Fig::NetworkError.new "No authentication information for #{host}."
116
+ end
110
117
 
111
118
  begin
112
- options = {:password => get_password}
119
+ options = {:password => authentication.password}
113
120
  port = uri.port
114
121
  if port
115
122
  options[:port] = port
116
123
  end
117
124
 
118
- Net::SFTP.start(host, get_username, options, &block)
125
+ Net::SFTP.start(host, authentication.username, options, &block)
119
126
  rescue Net::SSH::Exception => error
120
127
  raise Fig::NetworkError.new error.message
121
128
  rescue Net::SFTP::Exception => error
@@ -8,6 +8,7 @@ require 'fig'
8
8
  require 'fig/at_exit'
9
9
  require 'fig/file_not_found_error'
10
10
  require 'fig/logging'
11
+ require 'fig/not_yet_parsed_package'
11
12
  require 'fig/package_cache'
12
13
  require 'fig/package_descriptor'
13
14
  require 'fig/parser'
@@ -28,19 +29,22 @@ class Fig::Repository
28
29
  REMOTE_VERSION_SUPPORTED = 1
29
30
 
30
31
  def initialize(
31
- os,
32
+ application_configuration,
33
+ options,
34
+ operating_system,
32
35
  local_repository_directory,
33
- application_config,
34
- publish_listeners,
35
- check_include_versions
36
+ remote_repository_url,
37
+ parser,
38
+ publish_listeners
36
39
  )
37
- @operating_system = os
40
+ @application_configuration = application_configuration
41
+ @options = options
42
+ @operating_system = operating_system
38
43
  @local_repository_directory = local_repository_directory
39
- @application_config = application_config
44
+ @remote_repository_url = remote_repository_url
45
+ @parser = parser
40
46
  @publish_listeners = publish_listeners
41
47
 
42
- @parser = Fig::Parser.new(application_config, check_include_versions)
43
-
44
48
  initialize_local_repository()
45
49
  reset_cached_data()
46
50
  end
@@ -134,6 +138,8 @@ class Fig::Repository
134
138
  end
135
139
 
136
140
  publisher = Fig::RepositoryPackagePublisher.new
141
+ publisher.application_configuration = @application_configuration
142
+ publisher.options = @options
137
143
  publisher.operating_system = @operating_system
138
144
  publisher.publish_listeners = @publish_listeners
139
145
  publisher.package_statements = package_statements
@@ -165,6 +171,8 @@ class Fig::Repository
165
171
 
166
172
  private
167
173
 
174
+ attr_reader :remote_repository_url
175
+
168
176
  def initialize_local_repository()
169
177
  FileUtils.mkdir_p(@local_repository_directory)
170
178
 
@@ -237,7 +245,9 @@ class Fig::Repository
237
245
  )
238
246
  local_version_file = File.join(temp_dir, "remote-#{VERSION_FILE_NAME}")
239
247
  begin
240
- @operating_system.download(remote_version_file, local_version_file)
248
+ @operating_system.download(
249
+ remote_version_file, local_version_file, :prompt_for_login
250
+ )
241
251
  rescue Fig::FileNotFoundError
242
252
  # The download may create an empty file, so get rid of it.
243
253
  if File.exist?(local_version_file)
@@ -268,10 +278,6 @@ class Fig::Repository
268
278
  return version_string.to_i()
269
279
  end
270
280
 
271
- def remote_repository_url()
272
- return @application_config.remote_repository_url()
273
- end
274
-
275
281
  def should_update?(descriptor)
276
282
  return true if @update_condition == :unconditionally
277
283
 
@@ -308,7 +314,9 @@ class Fig::Repository
308
314
  package_directory = local_directory_for_package(descriptor)
309
315
  local_fig_file = fig_file_for_package_download(package_directory)
310
316
 
311
- if @operating_system.path_up_to_date? remote_fig_file, local_fig_file
317
+ if @operating_system.path_up_to_date?(
318
+ remote_fig_file, local_fig_file, :prompt_for_login
319
+ )
312
320
  Fig::Logging.debug \
313
321
  "Skipping update of #{descriptor.to_string} because it looks like #{local_fig_file} is up-to-date."
314
322
  return
@@ -328,7 +336,9 @@ class Fig::Repository
328
336
  FileUtils.mkdir_p temporary_package
329
337
  end
330
338
 
331
- return if ! @operating_system.download(remote_fig_file, temp_fig_file)
339
+ return if ! @operating_system.download(
340
+ remote_fig_file, temp_fig_file, :prompt_for_login
341
+ )
332
342
 
333
343
  package = read_package_from_directory(temporary_package, descriptor)
334
344
 
@@ -402,12 +412,14 @@ class Fig::Repository
402
412
  end
403
413
  content = File.read(file_name)
404
414
 
405
- package = @parser.parse_package(
406
- descriptor,
407
- runtime_for_package(descriptor),
408
- descriptor.to_string(),
409
- content
410
- )
415
+ unparsed_package = Fig::NotYetParsedPackage.new
416
+ unparsed_package.descriptor = descriptor
417
+ unparsed_package.working_directory = unparsed_package.base_directory =
418
+ runtime_for_package(descriptor)
419
+ unparsed_package.source_description = descriptor.to_string()
420
+ unparsed_package.unparsed_text = content
421
+
422
+ package = @parser.parse_package(unparsed_package)
411
423
 
412
424
  @package_cache.add_package(package)
413
425
 
@@ -4,11 +4,12 @@ require 'socket'
4
4
  require 'sys/admin'
5
5
  require 'tmpdir'
6
6
 
7
- require 'fig/statement/synthetic_raw_text'
8
7
  require 'fig'
9
8
  require 'fig/at_exit'
9
+ require 'fig/external_program'
10
10
  require 'fig/file_not_found_error'
11
11
  require 'fig/logging'
12
+ require 'fig/not_yet_parsed_package'
12
13
  require 'fig/package_cache'
13
14
  require 'fig/package_definition_text_assembler'
14
15
  require 'fig/package_descriptor'
@@ -19,12 +20,16 @@ require 'fig/repository_error'
19
20
  require 'fig/statement/archive'
20
21
  require 'fig/statement/grammar_version'
21
22
  require 'fig/statement/resource'
23
+ require 'fig/statement/synthetic_raw_text'
22
24
  require 'fig/url'
25
+ require 'fig/user_input_error'
23
26
 
24
27
  module Fig; end
25
28
 
26
29
  # Handles package publishing for the Repository.
27
30
  class Fig::RepositoryPackagePublisher
31
+ attr_writer :application_configuration
32
+ attr_writer :options
28
33
  attr_writer :operating_system
29
34
  attr_writer :publish_listeners
30
35
  attr_writer :descriptor
@@ -123,12 +128,15 @@ class Fig::RepositoryPackagePublisher
123
128
 
124
129
  published_package = nil
125
130
  begin
126
- published_package = Fig::Parser.new(nil, false).parse_package(
127
- @descriptor,
128
- @runtime_for_package,
129
- '<package to be published>',
130
- file_content
131
- )
131
+ unparsed_package = Fig::NotYetParsedPackage.new
132
+ unparsed_package.descriptor = @descriptor
133
+ unparsed_package.working_directory = unparsed_package.base_directory =
134
+ @runtime_for_package
135
+ unparsed_package.source_description = '<package to be published>'
136
+ unparsed_package.unparsed_text = file_content
137
+
138
+ published_package =
139
+ Fig::Parser.new(nil, false).parse_package(unparsed_package)
132
140
  rescue Fig::PackageParseError => error
133
141
  raise \
134
142
  "Bug in code! Could not parse package definition to be published.\n" +
@@ -152,11 +160,124 @@ class Fig::RepositoryPackagePublisher
152
160
  @text_assembler.add_header %Q<# Host: #{@publish_host}>
153
161
  @text_assembler.add_header %Q<# Args: "#{ARGV.join %q[", "]}">
154
162
  @text_assembler.add_header %Q<# Fig: v#{Fig::VERSION}>
163
+
164
+ add_environment_variables_to_package_metadata
165
+ add_version_control_to_package_metadata
166
+
155
167
  @text_assembler.add_header %Q<\n>
156
168
 
157
169
  return
158
170
  end
159
171
 
172
+ def add_environment_variables_to_package_metadata()
173
+ variables = @application_configuration[
174
+ 'environment variables to include in comments in published packages'
175
+ ]
176
+ return if ! variables || variables.empty?
177
+
178
+ @text_assembler.add_header %q<#>
179
+ @text_assembler.add_header %q<# Values of some environment variables at time of publish:>
180
+ @text_assembler.add_header %q<#>
181
+ variables.each do
182
+ |variable|
183
+
184
+ value = ENV[variable]
185
+ if value.nil?
186
+ value = ' was unset.'
187
+ else
188
+ value = "=#{value}"
189
+ end
190
+
191
+ @text_assembler.add_header %Q<# #{variable}#{value}>
192
+ end
193
+
194
+ return
195
+ end
196
+
197
+ def add_version_control_to_package_metadata()
198
+ return if @options.suppress_vcs_comments_in_published_packages?
199
+
200
+ add_subversion_url_to_package_metadata()
201
+ add_git_url_to_package_metadata()
202
+
203
+ return
204
+ end
205
+
206
+ def add_subversion_url_to_package_metadata()
207
+ output = get_subversion_working_directory_info
208
+ return if not output =~ /^URL: +(.*\S)\s*$/
209
+ url = $1
210
+
211
+ @text_assembler.add_header %q<#>
212
+ @text_assembler.add_header(
213
+ %Q<# Publish happened in a Subversion working directory from\n# #{url}.>
214
+ )
215
+
216
+ return
217
+ end
218
+
219
+ def get_subversion_working_directory_info()
220
+ executable =
221
+ get_version_control_executable('FIG_SVN_EXECUTABLE', 'svn') or return
222
+ return run_version_control_command(
223
+ [executable, 'info'], 'Subversion', 'FIG_SVN_EXECUTABLE'
224
+ )
225
+ end
226
+
227
+ def run_version_control_command(command, version_control_name, variable)
228
+ begin
229
+ output, errors, result = Fig::ExternalProgram.capture command
230
+ rescue Errno::ENOENT => error
231
+ Fig::Logging.warn(
232
+ %Q<Could not run "#{command.join ' '}": #{error.message}. Set #{variable} to the path to use for #{version_control_name} or to the empty string to suppress #{version_control_name} support.>
233
+ )
234
+ return
235
+ end
236
+
237
+ if result && ! result.success?
238
+ Fig::Logging.debug(
239
+ %Q<Could not run "#{command.join ' '}": #{result}: #{errors}>
240
+ )
241
+
242
+ return
243
+ end
244
+
245
+ return output
246
+ end
247
+
248
+ def get_version_control_executable(variable, default)
249
+ executable = ENV[variable]
250
+ if ! executable || executable.empty?
251
+ return if ENV.include? variable
252
+ return default
253
+ end
254
+
255
+ return executable
256
+ end
257
+
258
+ def add_git_url_to_package_metadata()
259
+ url = get_git_origin_url or return
260
+ url.strip!
261
+ return if url.empty?
262
+
263
+ @text_assembler.add_header %q<#>
264
+ @text_assembler.add_header(
265
+ %Q<# Publish happened in a Git working directory from\n# #{url}.>
266
+ )
267
+
268
+ return
269
+ end
270
+
271
+ def get_git_origin_url()
272
+ executable =
273
+ get_version_control_executable('FIG_GIT_EXECUTABLE', 'git') or return
274
+ return run_version_control_command(
275
+ [executable, 'config', '--get', 'remote.origin.url'],
276
+ 'Git',
277
+ 'FIG_GIT_EXECUTABLE'
278
+ )
279
+ end
280
+
160
281
  # Deals with Archive and Resource statements. It downloads any remote
161
282
  # files (those where the statement references a URL as opposed to a local
162
283
  # file) and then copies all files into the local repository and the remote
@@ -256,7 +377,7 @@ class Fig::RepositoryPackagePublisher
256
377
  asset_local = File.join(publish_temp_dir(), asset_name)
257
378
 
258
379
  begin
259
- @operating_system.download(asset_statement.location, asset_local)
380
+ @operating_system.download(asset_statement.location, asset_local, false)
260
381
  rescue Fig::FileNotFoundError
261
382
  Fig::Logging.fatal "Could not download #{asset_statement.location}."
262
383
  raise Fig::RepositoryError.new