aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Melnichenko <mpeterval@gmail.com>2016-11-01 14:18:16 +0300
committerPeter Melnichenko <mpeterval@gmail.com>2016-11-01 23:00:51 +0300
commitcee2d3abfc1c06f382787d7bf7d2110a52ca0e76 (patch)
tree5e68eab6d55f48687b2169a5b8a267f9682d163c
parent933386b16d944eac3f93100886b03c3ae745b802 (diff)
downloadluarocks-cee2d3abfc1c06f382787d7bf7d2110a52ca0e76.tar.gz
luarocks-cee2d3abfc1c06f382787d7bf7d2110a52ca0e76.tar.bz2
luarocks-cee2d3abfc1c06f382787d7bf7d2110a52ca0e76.zip
Fix and refactor conflict resolution on deploy/delete
Refactor repos.deploy_files and repos.delete_version to make relationships between properties of deployed files clearer and to avoid converting back and forth between related properties. Location of each deployable file in its rock manifest is pair deploy_type - the first subtree name ("bin", "lib", or "lua") and file_path - remaining path from the subtree to the file. These components determine where each file is physically located. Conflicts are considered based on two other properties: type and name of an item a file provides. Type can be "command" or "module". For items deployed using non-versioned names pairs (type, name) should be unique. Conversion from (deploy_type, file_path) to (item_type, item_name) is obvious, using path.path_to_module() for modules. Reversing this conversion is necessary for moving files between versioned and non-versioned locations on conflicts, and also for path.which function used in luarocks.show. However, rock tree manifest only allows to get file_path, which is not enough for modules - deploy_type can be both "lua" and "lib". Currently path.which infers deploy_type based on extension, falling back to "lib" if it's unknown, causing luarocks.show to display wrong paths (#424). This commit does not address that but adds relevant funcionality. Currently conflict resolution assumes that both files in conflict have same deploy_type and errors on conflict between a C module and a Lua module. This commit fixes that, inferring deploy_type for files with unknown extension using rock manifest.
-rw-r--r--src/luarocks/manif.lua132
-rw-r--r--src/luarocks/repos.lua256
2 files changed, 205 insertions, 183 deletions
diff --git a/src/luarocks/manif.lua b/src/luarocks/manif.lua
index 86209d06..88a13f10 100644
--- a/src/luarocks/manif.lua
+++ b/src/luarocks/manif.lua
@@ -540,85 +540,89 @@ function manif.zip_manifests()
540 end 540 end
541end 541end
542 542
543local function relative_path(from_dir, to_file) 543--- Get type and name of an item (a module or a command) provided by a file.
544 -- It is assumed that `from_dir` is prefix of `to_file`. 544-- @param deploy_type string: rock manifest subtree the file comes from ("bin", "lua", or "lib").
545 return (to_file:sub(#from_dir + 1):gsub("^[\\/]*", "")) 545-- @param file_path string: path to the file relatively to deploy_type subdirectory.
546-- @return (string, string): item type ("module" or "command") and name.
547function manif.get_provided_item(deploy_type, file_path)
548 assert(type(deploy_type) == "string")
549 assert(type(file_path) == "string")
550 local item_type = deploy_type == "bin" and "command" or "module"
551 local item_name = item_type == "command" and file_path or path.path_to_module(file_path)
552 return item_type, item_name
546end 553end
547 554
548local function file_manifest_coordinates(manifest, file, root) 555local function get_providers(item_type, item_name, repo)
549 local deploy_bin = path.deploy_bin_dir(root) 556 assert(type(item_type) == "string")
550 local deploy_lua = path.deploy_lua_dir(root) 557 assert(type(item_name) == "string")
551 local deploy_lib = path.deploy_lib_dir(root) 558 local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
552 559 local manifest = manif_core.load_local_manifest(rocks_dir)
553 if util.starts_with(file, deploy_lua) then 560 return manifest and manifest[item_type .. "s"][item_name]
554 return "modules", path.path_to_module(relative_path(deploy_lua, file):gsub("\\", "/")), deploy_lua
555 elseif util.starts_with(file, deploy_lib) then
556 return "modules", path.path_to_module(relative_path(deploy_lib, file):gsub("\\", "/")), deploy_lib
557 elseif util.starts_with(file, deploy_bin) then
558 return "commands", relative_path(deploy_bin, file), deploy_bin
559 else
560 assert(false, "Assertion failed: '"..file.."' is not a deployed file.")
561 end
562end 561end
563 562
564local function find_providers(file, root) 563--- Given a name of a module or a command, figure out which rock name and version
565 assert(type(file) == "string") 564-- correspond to it in the rock tree manifest.
566 root = root or cfg.root_dir 565-- @param item_type string: "module" or "command".
567 566-- @param item_name string: module or command name.
568 local manifest, err = manif_core.load_local_manifest(path.rocks_dir(root)) 567-- @param root string or nil: A local root dir for a rocks tree. If not given, the default is used.
569 if not manifest then 568-- @return (string, string) or nil: name and version of the provider rock or nil if there
570 return nil, "untracked" 569-- is no provider.
570function manif.get_current_provider(item_type, item_name, repo)
571 local providers = get_providers(item_type, item_name, repo)
572 if providers then
573 return providers[1]:match("([^/]*)/([^/]*)")
571 end 574 end
575end
572 576
573 local type_key, key = file_manifest_coordinates(manifest, file, root) 577function manif.get_next_provider(item_type, item_name, repo)
574 578 local providers = get_providers(item_type, item_name, repo)
575 local providers = manifest[type_key][key] 579 if providers and providers[2] then
576 if not providers then 580 return providers[2]:match("([^/]*)/([^/]*)")
577 return nil, "untracked"
578 end 581 end
579 return providers
580end 582end
581 583
582--- Given a path of a deployed file, figure out which rock name and version 584--- Given a name of a module or a command provided by a package, figure out
583-- correspond to it in the tree manifest. 585-- which file provides it.
584-- @param file string: The full path of a deployed file. 586-- @param name string: package name.
587-- @param version string: package version.
588-- @param item_type string: "module" or "command".
589-- @param item_name string: module or command name.
585-- @param root string or nil: A local root dir for a rocks tree. If not given, the default is used. 590-- @param root string or nil: A local root dir for a rocks tree. If not given, the default is used.
586-- @return string, string: name and version of the provider rock. 591-- @return (string, string): rock manifest subtree the file comes from ("bin", "lua", or "lib")
587function manif.find_current_provider(file, root) 592-- and path to the providing file relatively to that subtree.
588 local providers, err = find_providers(file, root) 593function manif.get_providing_file(name, version, item_type, item_name, repo)
589 if not providers then return nil, err end 594 local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
590 return providers[1]:match("([^/]*)/([^/]*)") 595 local manifest = manif_core.load_local_manifest(rocks_dir)
591end
592 596
593function manif.find_next_provider(file, root) 597 local entry_table = manifest.repository[name][version][1]
594 local providers, err = find_providers(file, root) 598 local file_path = entry_table[item_type .. "s"][item_name]
595 if not providers then return nil, err end 599
596 if providers[2] then 600 if item_type == "command" then
597 return providers[2]:match("([^/]*)/([^/]*)") 601 return "bin", file_path
598 else
599 return nil
600 end 602 end
601end
602 603
603--- Given a file conflicting with a module or command 604 -- A module can be in "lua" or "lib". Decide based on extension first:
604-- provided by a version of a package, return which file 605 -- most likely Lua modules are in "lua/" and C modules are in "lib/".
605-- in that version corresponds to the conflicting item. 606 if file_path:match("%." .. cfg.lua_extension .. "$") then
606-- @param name string: name of the package with conflicting module or command. 607 return "lua", file_path
607-- @param version string: version of the package with conflicting module or command. 608 elseif file_path:match("%." .. cfg.lib_extension .. "$") then
608-- @param file string: full, unversioned path to a deployed file. 609 return "lib", file_path
609-- @return string: full, unversioned path to a deployed file in
610-- given package that conflicts with given file.
611function manif.find_conflicting_file(name, version, file, root)
612 root = root or cfg.root_dir
613
614 local manifest = manif_core.load_local_manifest(path.rocks_dir(root))
615 if not manifest then
616 return
617 end 610 end
618 611
619 local entry_table = manifest.repository[name][version][1] 612 -- Fallback to rock manifest scanning.
620 local type_key, key, deploy_dir = file_manifest_coordinates(manifest, file, root) 613 local rock_manifest = manif.load_rock_manifest(name, version)
621 return dir.path(deploy_dir, entry_table[type_key][key]) 614 local subtree = rock_manifest.lib
615
616 for path_part in file_path:gmatch("[^/]+") do
617 if type(subtree) == "table" then
618 subtree = subtree[path_part]
619 else
620 -- Assume it's in "lua/" if it's not in "lib/".
621 return "lua", file_path
622 end
623 end
624
625 return type(subtree) == "string" and "lib" or "lua", file_path
622end 626end
623 627
624return manif 628return manif
diff --git a/src/luarocks/repos.lua b/src/luarocks/repos.lua
index d4d9694e..5d5eac70 100644
--- a/src/luarocks/repos.lua
+++ b/src/luarocks/repos.lua
@@ -11,6 +11,23 @@ local dir = require("luarocks.dir")
11local manif = require("luarocks.manif") 11local manif = require("luarocks.manif")
12local deps = require("luarocks.deps") 12local deps = require("luarocks.deps")
13 13
14-- Tree of files installed by a package are stored
15-- in its rock manifest. Some of these files have to
16-- be deployed to locations where Lua can load them as
17-- modules or where they can be used as commands.
18-- These files are characterised by pair
19-- (deploy_type, file_path), where deploy_type is the first
20-- component of the file path and file_path is the rest of the
21-- path. Only files with deploy_type in {"lua", "lib", "bin"}
22-- are deployed somewhere.
23-- Each deployed file provides an "item". An item is
24-- characterised by pair (item_type, item_name).
25-- item_type is "command" for files with deploy_type
26-- "bin" and "module" for deploy_type in {"lua", "lib"}.
27-- item_name is same as file_path for commands
28-- and is produced using path.path_to_module(file_path)
29-- for modules.
30
14--- Get all installed versions of a package. 31--- Get all installed versions of a package.
15-- @param name string: a package name. 32-- @param name string: a package name.
16-- @return table or nil: An array of strings listing installed 33-- @return table or nil: An array of strings listing installed
@@ -192,44 +209,56 @@ end
192local function delete_suffixed(file, suffix) 209local function delete_suffixed(file, suffix)
193 local suffixed_file, err = find_suffixed(file, suffix) 210 local suffixed_file, err = find_suffixed(file, suffix)
194 if not suffixed_file then 211 if not suffixed_file then
195 return nil, "Could not remove " .. file .. ": " .. err, "not found" 212 return nil, "Could not remove " .. file .. ": " .. err
196 end 213 end
197 214
198 fs.delete(suffixed_file) 215 fs.delete(suffixed_file)
199 if fs.exists(suffixed_file) then 216 if fs.exists(suffixed_file) then
200 return nil, "Failed deleting " .. suffixed_file .. ": file still exists", "fail" 217 return nil, "Failed deleting " .. suffixed_file .. ": file still exists"
201 end 218 end
202 219
203 return true 220 return true
204end 221end
205 222
206local function resolve_conflict(target, deploy_dir, name, version, cur_name, cur_version, suffix) 223-- Files can be deployed using versioned and non-versioned names.
207 if name < cur_name or (name == cur_name and deps.compare_versions(version, cur_version)) then 224-- Several items with same type and name can exist if they are
225-- provided by different packages or versions. In any case
226-- item from the newest version of lexicographically smallest package
227-- is deployed using non-versioned name and others use versioned names.
228
229local function get_deploy_paths(name, version, deploy_type, file_path)
230 local deploy_dir = cfg["deploy_" .. deploy_type .. "_dir"]
231 local non_versioned = dir.path(deploy_dir, file_path)
232 local versioned = path.versioned_name(non_versioned, deploy_dir, name, version)
233 return non_versioned, versioned
234end
235
236local function prepare_target(name, version, deploy_type, file_path, suffix)
237 local non_versioned, versioned = get_deploy_paths(name, version, deploy_type, file_path)
238 local item_type, item_name = manif.get_provided_item(deploy_type, file_path)
239 local cur_name, cur_version = manif.get_current_provider(item_type, item_name)
240
241 if not cur_name then
242 return non_versioned
243 elseif name < cur_name or (name == cur_name and deps.compare_versions(version, cur_version)) then
208 -- New version has priority. Move currently provided version back using versioned name. 244 -- New version has priority. Move currently provided version back using versioned name.
209 local cur_target = manif.find_conflicting_file(cur_name, cur_version, target) 245 local cur_deploy_type, cur_file_path = manif.get_providing_file(cur_name, cur_version, item_type, item_name)
210 local versioned = path.versioned_name(cur_target, deploy_dir, cur_name, cur_version) 246 local cur_non_versioned, cur_versioned = get_deploy_paths(cur_name, cur_version, cur_deploy_type, cur_file_path)
211 247
212 local ok, err = fs.make_dir(dir.dir_name(versioned)) 248 local dir_ok, dir_err = fs.make_dir(dir.dir_name(cur_versioned))
213 if not ok then 249 if not dir_ok then return nil, dir_err end
214 return nil, err
215 end
216 250
217 ok, err = move_suffixed(cur_target, versioned, suffix) 251 local move_ok, move_err = move_suffixed(cur_non_versioned, cur_versioned, suffix)
218 if not ok then 252 if not move_ok then return nil, move_err end
219 return nil, err
220 end
221 253
222 return target 254 return non_versioned
223 else 255 else
224 -- Current version has priority, deploy new version using versioned name. 256 -- Current version has priority, deploy new version using versioned name.
225 return path.versioned_name(target, deploy_dir, name, version) 257 return versioned
226 end 258 end
227end 259end
228 260
229--- Deploy a package from the rocks subdirectory. 261--- Deploy a package from the rocks subdirectory.
230-- It is maintained that for each module and command the one that is provided
231-- by the newest version of the lexicographically smallest package
232-- is installed using unversioned name, and other versions use versioned names.
233-- @param name string: name of package 262-- @param name string: name of package
234-- @param version string: exact package version in string format 263-- @param version string: exact package version in string format
235-- @param wrap_bin_scripts bool: whether commands written in Lua should be wrapped. 264-- @param wrap_bin_scripts bool: whether commands written in Lua should be wrapped.
@@ -241,49 +270,44 @@ function repos.deploy_files(name, version, wrap_bin_scripts, deps_mode)
241 assert(type(version) == "string") 270 assert(type(version) == "string")
242 assert(type(wrap_bin_scripts) == "boolean") 271 assert(type(wrap_bin_scripts) == "boolean")
243 272
244 local function deploy_file_tree(file_tree, path_fn, deploy_dir, move_fn, suffix) 273 local rock_manifest = manif.load_rock_manifest(name, version)
245 local source_dir = path_fn(name, version)
246 return recurse_rock_manifest_tree(file_tree,
247 function(parent_path, parent_module, file)
248 local source = dir.path(source_dir, parent_path, file)
249 local target = dir.path(deploy_dir, parent_path, file)
250
251 local cur_name, cur_version = manif.find_current_provider(target)
252 if cur_name then
253 local resolve_err
254 target, resolve_err = resolve_conflict(target, deploy_dir, name, version, cur_name, cur_version, suffix)
255 if not target then
256 return nil, resolve_err
257 end
258 end
259 274
260 local ok, err = fs.make_dir(dir.dir_name(target)) 275 local function deploy_file_tree(deploy_type, source_dir, move_fn, suffix)
261 if not ok then return nil, err end 276 if not rock_manifest[deploy_type] then
277 return true
278 end
262 279
263 local suffixed_target, mover = move_fn(source, target, name, version) 280 return recurse_rock_manifest_tree(rock_manifest[deploy_type], function(parent_path, parent_module, file)
264 if fs.exists(suffixed_target) then 281 local file_path = parent_path .. file
265 local backup = suffixed_target 282 local source = dir.path(source_dir, file_path)
266 repeat
267 backup = backup.."~"
268 until not fs.exists(backup) -- Slight race condition here, but shouldn't be a problem.
269
270 util.printerr("Warning: "..suffixed_target.." is not tracked by this installation of LuaRocks. Moving it to "..backup)
271 local ok, err = fs.move(suffixed_target, backup)
272 if not ok then
273 return nil, err
274 end
275 end
276 283
277 ok, err = mover() 284 local target, prepare_err = prepare_target(name, version, deploy_type, file_path, suffix)
278 fs.remove_dir_tree_if_empty(dir.dir_name(source)) 285 if not target then return nil, prepare_err end
279 return ok, err 286
287 local dir_ok, dir_err = fs.make_dir(dir.dir_name(target))
288 if not dir_ok then return nil, dir_err end
289
290 local suffixed_target, mover = move_fn(source, target)
291 if fs.exists(suffixed_target) then
292 local backup = suffixed_target
293 repeat
294 backup = backup.."~"
295 until not fs.exists(backup) -- Slight race condition here, but shouldn't be a problem.
296
297 util.printerr("Warning: "..suffixed_target.." is not tracked by this installation of LuaRocks. Moving it to "..backup)
298 local move_ok, move_err = fs.move(suffixed_target, backup)
299 if not move_ok then return nil, move_err end
280 end 300 end
281 )
282 end
283 301
284 local rock_manifest = manif.load_rock_manifest(name, version) 302 local move_ok, move_err = mover()
303 if not move_ok then return nil, move_err end
304
305 fs.remove_dir_tree_if_empty(dir.dir_name(source))
306 return true
307 end)
308 end
285 309
286 local function install_binary(source, target, name, version) 310 local function install_binary(source, target)
287 if wrap_bin_scripts and fs.is_lua(source) then 311 if wrap_bin_scripts and fs.is_lua(source) then
288 return target .. (cfg.wrapper_suffix or ""), function() return fs.wrap_script(source, target, name, version) end 312 return target .. (cfg.wrapper_suffix or ""), function() return fs.wrap_script(source, target, name, version) end
289 else 313 else
@@ -297,28 +321,19 @@ function repos.deploy_files(name, version, wrap_bin_scripts, deps_mode)
297 end 321 end
298 end 322 end
299 323
300 local ok, err = true 324 local ok, err = deploy_file_tree("bin", path.bin_dir(name, version), install_binary, cfg.wrapper_suffix)
301 if rock_manifest.bin then 325 if not ok then return nil, err end
302 ok, err = deploy_file_tree(rock_manifest.bin, path.bin_dir, cfg.deploy_bin_dir, install_binary, cfg.wrapper_suffix)
303 end
304 if ok and rock_manifest.lua then
305 ok, err = deploy_file_tree(rock_manifest.lua, path.lua_dir, cfg.deploy_lua_dir, make_mover(cfg.perm_read))
306 end
307 if ok and rock_manifest.lib then
308 ok, err = deploy_file_tree(rock_manifest.lib, path.lib_dir, cfg.deploy_lib_dir, make_mover(cfg.perm_exec))
309 end
310 326
311 if not ok then 327 ok, err = deploy_file_tree("lua", path.lua_dir(name, version), make_mover(cfg.perm_read))
312 return nil, err 328 if not ok then return nil, err end
313 end 329
330 ok, err = deploy_file_tree("lib", path.lib_dir(name, version), make_mover(cfg.perm_exec))
331 if not ok then return nil, err end
314 332
315 return manif.add_to_manifest(name, version, nil, deps_mode) 333 return manif.add_to_manifest(name, version, nil, deps_mode)
316end 334end
317 335
318--- Delete a package from the local repository. 336--- Delete a package from the local repository.
319-- It is maintained that for each module and command the one that is provided
320-- by the newest version of the lexicographically smallest package
321-- is installed using unversioned name, and other versions use versioned names.
322-- @param name string: name of package 337-- @param name string: name of package
323-- @param version string: exact package version in string format 338-- @param version string: exact package version in string format
324-- @param deps_mode: string: Which trees to check dependencies for: 339-- @param deps_mode: string: Which trees to check dependencies for:
@@ -333,67 +348,70 @@ function repos.delete_version(name, version, deps_mode, quick)
333 assert(type(version) == "string") 348 assert(type(version) == "string")
334 assert(type(deps_mode) == "string") 349 assert(type(deps_mode) == "string")
335 350
336 local function delete_deployed_file_tree(file_tree, deploy_dir, suffix) 351 local rock_manifest = manif.load_rock_manifest(name, version)
337 return recurse_rock_manifest_tree(file_tree, 352 if not rock_manifest then
338 function(parent_path, parent_module, file) 353 return nil, "rock_manifest file not found for "..name.." "..version.." - not a LuaRocks 2 tree?"
339 local target = dir.path(deploy_dir, parent_path, file) 354 end
340 local versioned = path.versioned_name(target, deploy_dir, name, version)
341
342 local ok, err, err_type = delete_suffixed(versioned, suffix)
343 if ok then
344 fs.remove_dir_tree_if_empty(dir.dir_name(versioned))
345 return true
346 elseif err_type == "fail" then
347 return nil, err
348 end
349 355
350 ok, err = delete_suffixed(target, suffix) 356 local function delete_deployed_file_tree(deploy_type, suffix)
351 if not ok then 357 if not rock_manifest[deploy_type] then
352 return nil, err 358 return true
353 end 359 end
360
361 return recurse_rock_manifest_tree(rock_manifest[deploy_type], function(parent_path, parent_module, file)
362 local file_path = parent_path .. file
363 local non_versioned, versioned = get_deploy_paths(name, version, deploy_type, file_path)
364
365 -- Figure out if the file is deployed using versioned or non-versioned name.
366 local target
367 local item_type, item_name = manif.get_provided_item(deploy_type, file_path)
368 local cur_name, cur_version = manif.get_current_provider(item_type, item_name)
354 369
355 if not quick then 370 if cur_name == name and cur_version == version then
356 local next_name, next_version = manif.find_next_provider(target) 371 -- This package has highest priority, should be in non-versioned location.
357 if next_name then 372 target = non_versioned
358 local next_target = manif.find_conflicting_file(next_name, next_version, target) 373 else
359 local next_versioned = path.versioned_name(next_target, deploy_dir, next_name, next_version) 374 target = versioned
375 end
376
377 local ok, err = delete_suffixed(target, suffix)
378 if not ok then return nil, err end
379
380 if not quick and target == non_versioned then
381 -- If another package provides this file, move its version
382 -- into non-versioned location instead.
383 local next_name, next_version = manif.get_next_provider(item_type, item_name)
360 384
361 ok, err = move_suffixed(next_versioned, next_target, suffix) 385 if next_name then
362 if not ok then 386 local next_deploy_type, next_file_path = manif.get_providing_file(next_name, next_version, item_type, item_name)
363 return nil, err 387 local next_non_versioned, next_versioned = get_deploy_paths(next_name, next_version, next_deploy_type, next_file_path)
364 end
365 388
366 fs.remove_dir_tree_if_empty(dir.dir_name(versioned)) 389 local move_ok, move_err = move_suffixed(next_versioned, next_non_versioned, suffix)
367 end 390 if not move_ok then return nil, move_err end
391
392 fs.remove_dir_tree_if_empty(dir.dir_name(next_versioned))
368 end 393 end
369 fs.remove_dir_tree_if_empty(dir.dir_name(target))
370 return true
371 end 394 end
372 )
373 end
374 395
375 local rock_manifest = manif.load_rock_manifest(name, version) 396 fs.remove_dir_tree_if_empty(dir.dir_name(target))
376 if not rock_manifest then 397 return true
377 return nil, "rock_manifest file not found for "..name.." "..version.." - not a LuaRocks 2 tree?" 398 end)
378 end
379
380 local ok, err = true
381 if rock_manifest.bin then
382 ok, err = delete_deployed_file_tree(rock_manifest.bin, cfg.deploy_bin_dir, cfg.wrapper_suffix)
383 end
384 if ok and rock_manifest.lua then
385 ok, err = delete_deployed_file_tree(rock_manifest.lua, cfg.deploy_lua_dir)
386 end
387 if ok and rock_manifest.lib then
388 ok, err = delete_deployed_file_tree(rock_manifest.lib, cfg.deploy_lib_dir)
389 end 399 end
400
401 local ok, err = delete_deployed_file_tree("bin", cfg.wrapper_suffix)
402 if not ok then return nil, err end
403
404 ok, err = delete_deployed_file_tree("lua")
405 if not ok then return nil, err end
406
407 ok, err = delete_deployed_file_tree("lib")
390 if not ok then return nil, err end 408 if not ok then return nil, err end
391 409
392 fs.delete(path.install_dir(name, version)) 410 fs.delete(path.install_dir(name, version))
393 if not get_installed_versions(name) then 411 if not get_installed_versions(name) then
394 fs.delete(dir.path(cfg.rocks_dir, name)) 412 fs.delete(dir.path(cfg.rocks_dir, name))
395 end 413 end
396 414
397 if quick then 415 if quick then
398 return true 416 return true
399 end 417 end