aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorV1K1NGbg <victor@ilchev.com>2024-08-13 20:00:47 +0300
committerV1K1NGbg <victor@ilchev.com>2024-08-13 20:00:47 +0300
commit2ab89328bd35d20e4ef990934abf8382adc95b82 (patch)
tree7ec0bf331cfc092f38dc32a8b8608c870dd429d0
parent9d4bebd145cbdf3185ed68ba1eb4140303eb093f (diff)
downloadluarocks-2ab89328bd35d20e4ef990934abf8382adc95b82.tar.gz
luarocks-2ab89328bd35d20e4ef990934abf8382adc95b82.tar.bz2
luarocks-2ab89328bd35d20e4ef990934abf8382adc95b82.zip
repos
-rw-r--r--src/luarocks/manif.tl2
-rw-r--r--src/luarocks/repos-original.lua681
-rw-r--r--src/luarocks/repos.lua266
-rw-r--r--src/luarocks/repos.tl24
4 files changed, 833 insertions, 140 deletions
diff --git a/src/luarocks/manif.tl b/src/luarocks/manif.tl
index 0a329fff..6fbf3fb2 100644
--- a/src/luarocks/manif.tl
+++ b/src/luarocks/manif.tl
@@ -184,7 +184,7 @@ function manif.get_current_provider(item_type: string, item_name: string, repo?:
184 end 184 end
185end 185end
186 186
187function manif.get_next_provider(item_type: string, item_name: string, repo: string | Tree): string, string 187function manif.get_next_provider(item_type: string, item_name: string, repo?: string | Tree): string, string
188 local providers = get_providers(item_type, item_name, repo) 188 local providers = get_providers(item_type, item_name, repo)
189 if providers and providers[2] then 189 if providers and providers[2] then
190 return providers[2]:match("([^/]*)/([^/]*)") 190 return providers[2]:match("([^/]*)/([^/]*)")
diff --git a/src/luarocks/repos-original.lua b/src/luarocks/repos-original.lua
new file mode 100644
index 00000000..2e71b3f7
--- /dev/null
+++ b/src/luarocks/repos-original.lua
@@ -0,0 +1,681 @@
1
2--- Functions for managing the repository on disk.
3local repos = {}
4
5local fs = require("luarocks.fs")
6local path = require("luarocks.path")
7local cfg = require("luarocks.core.cfg")
8local util = require("luarocks.util")
9local dir = require("luarocks.dir")
10local manif = require("luarocks.manif")
11local vers = require("luarocks.core.vers")
12
13local unpack = unpack or table.unpack -- luacheck: ignore 211
14
15-- Tree of files installed by a package are stored
16-- in its rock manifest. Some of these files have to
17-- be deployed to locations where Lua can load them as
18-- modules or where they can be used as commands.
19-- These files are characterised by pair
20-- (deploy_type, file_path), where deploy_type is the first
21-- component of the file path and file_path is the rest of the
22-- path. Only files with deploy_type in {"lua", "lib", "bin"}
23-- are deployed somewhere.
24-- Each deployed file provides an "item". An item is
25-- characterised by pair (item_type, item_name).
26-- item_type is "command" for files with deploy_type
27-- "bin" and "module" for deploy_type in {"lua", "lib"}.
28-- item_name is same as file_path for commands
29-- and is produced using path.path_to_module(file_path)
30-- for modules.
31
32--- Get all installed versions of a package.
33-- @param name string: a package name.
34-- @return table or nil: An array of strings listing installed
35-- versions of a package, or nil if none is available.
36local function get_installed_versions(name)
37 assert(type(name) == "string" and not name:match("/"))
38
39 local dirs = fs.list_dir(path.versions_dir(name))
40 return (dirs and #dirs > 0) and dirs or nil
41end
42
43--- Check if a package exists in a local repository.
44-- Version numbers are compared as exact string comparison.
45-- @param name string: name of package
46-- @param version string: package version in string format
47-- @return boolean: true if a package is installed,
48-- false otherwise.
49function repos.is_installed(name, version)
50 assert(type(name) == "string" and not name:match("/"))
51 assert(type(version) == "string")
52
53 return fs.is_dir(path.install_dir(name, version))
54end
55
56function repos.recurse_rock_manifest_entry(entry, action)
57 assert(type(action) == "function")
58
59 if entry == nil then
60 return true
61 end
62
63 local function do_recurse_rock_manifest_entry(tree, parent_path)
64
65 for file, sub in pairs(tree) do
66 local sub_path = (parent_path and (parent_path .. "/") or "") .. file
67 local ok, err -- luacheck: ignore 231
68
69 if type(sub) == "table" then
70 ok, err = do_recurse_rock_manifest_entry(sub, sub_path)
71 else
72 ok, err = action(sub_path)
73 end
74
75 if err then return nil, err end
76 end
77 return true
78 end
79 return do_recurse_rock_manifest_entry(entry)
80end
81
82local function store_package_data(result, rock_manifest, deploy_type)
83 if rock_manifest[deploy_type] then
84 repos.recurse_rock_manifest_entry(rock_manifest[deploy_type], function(file_path)
85 local _, item_name = manif.get_provided_item(deploy_type, file_path)
86 result[item_name] = file_path
87 return true
88 end)
89 end
90end
91
92--- Obtain a table of modules within an installed package.
93-- @param name string: The package name; for example "luasocket"
94-- @param version string: The exact version number including revision;
95-- for example "2.0.1-1".
96-- @return table: A table of modules where keys are module names
97-- and values are file paths of files providing modules
98-- relative to "lib" or "lua" rock manifest subtree.
99-- If no modules are found or if package name or version
100-- are invalid, an empty table is returned.
101function repos.package_modules(name, version)
102 assert(type(name) == "string" and not name:match("/"))
103 assert(type(version) == "string")
104
105 local result = {}
106 local rock_manifest = manif.load_rock_manifest(name, version)
107 if not rock_manifest then return result end
108 store_package_data(result, rock_manifest, "lib")
109 store_package_data(result, rock_manifest, "lua")
110 return result
111end
112
113--- Obtain a table of command-line scripts within an installed package.
114-- @param name string: The package name; for example "luasocket"
115-- @param version string: The exact version number including revision;
116-- for example "2.0.1-1".
117-- @return table: A table of commands where keys and values are command names
118-- as strings - file paths of files providing commands
119-- relative to "bin" rock manifest subtree.
120-- If no commands are found or if package name or version
121-- are invalid, an empty table is returned.
122function repos.package_commands(name, version)
123 assert(type(name) == "string" and not name:match("/"))
124 assert(type(version) == "string")
125
126 local result = {}
127 local rock_manifest = manif.load_rock_manifest(name, version)
128 if not rock_manifest then return result end
129 store_package_data(result, rock_manifest, "bin")
130 return result
131end
132
133
134--- Check if a rock contains binary executables.
135-- @param name string: name of an installed rock
136-- @param version string: version of an installed rock
137-- @return boolean: returns true if rock contains platform-specific
138-- binary executables, or false if it is a pure-Lua rock.
139function repos.has_binaries(name, version)
140 assert(type(name) == "string" and not name:match("/"))
141 assert(type(version) == "string")
142
143 local rock_manifest = manif.load_rock_manifest(name, version)
144 if rock_manifest and rock_manifest.bin then
145 for bin_name, md5 in pairs(rock_manifest.bin) do
146 -- TODO verify that it is the same file. If it isn't, find the actual command.
147 if fs.is_actual_binary(dir.path(cfg.deploy_bin_dir, bin_name)) then
148 return true
149 end
150 end
151 end
152 return false
153end
154
155function repos.run_hook(rockspec, hook_name)
156 assert(rockspec:type() == "rockspec")
157 assert(type(hook_name) == "string")
158
159 local hooks = rockspec.hooks
160 if not hooks then
161 return true
162 end
163
164 if cfg.hooks_enabled == false then
165 return nil, "This rockspec contains hooks, which are blocked by the 'hooks_enabled' setting in your LuaRocks configuration."
166 end
167
168 if not hooks.substituted_variables then
169 util.variable_substitutions(hooks, rockspec.variables)
170 hooks.substituted_variables = true
171 end
172 local hook = hooks[hook_name]
173 if hook then
174 util.printout(hook)
175 if not fs.execute(hook) then
176 return nil, "Failed running "..hook_name.." hook."
177 end
178 end
179 return true
180end
181
182function repos.should_wrap_bin_scripts(rockspec)
183 assert(rockspec:type() == "rockspec")
184
185 if cfg.wrap_bin_scripts ~= nil then
186 return cfg.wrap_bin_scripts
187 end
188 if rockspec.deploy and rockspec.deploy.wrap_bin_scripts == false then
189 return false
190 end
191 return true
192end
193
194local function find_suffixed(file, suffix)
195 local filenames = {file}
196 if suffix and suffix ~= "" then
197 table.insert(filenames, 1, file .. suffix)
198 end
199
200 for _, filename in ipairs(filenames) do
201 if fs.exists(filename) then
202 return filename
203 end
204 end
205
206 return nil, table.concat(filenames, ", ") .. " not found"
207end
208
209local function check_suffix(filename, suffix)
210 local suffixed_filename, err = find_suffixed(filename, suffix)
211 if not suffixed_filename then
212 return ""
213 end
214 return suffixed_filename:sub(#filename + 1)
215end
216
217-- Files can be deployed using versioned and non-versioned names.
218-- Several items with same type and name can exist if they are
219-- provided by different packages or versions. In any case
220-- item from the newest version of lexicographically smallest package
221-- is deployed using non-versioned name and others use versioned names.
222
223local function get_deploy_paths(name, version, deploy_type, file_path, repo)
224 assert(type(name) == "string")
225 assert(type(version) == "string")
226 assert(type(deploy_type) == "string")
227 assert(type(file_path) == "string")
228
229 repo = repo or cfg.root_dir
230 local deploy_dir = path["deploy_" .. deploy_type .. "_dir"](repo)
231 local non_versioned = dir.path(deploy_dir, file_path)
232 local versioned = path.versioned_name(non_versioned, deploy_dir, name, version)
233 return { nv = non_versioned, v = versioned }
234end
235
236local function check_spot_if_available(name, version, deploy_type, file_path)
237 local item_type, item_name = manif.get_provided_item(deploy_type, file_path)
238 local cur_name, cur_version = manif.get_current_provider(item_type, item_name)
239
240 -- older versions of LuaRocks (< 3) registered "foo.init" files as "foo"
241 -- (which caused problems, so that behavior was changed). But look for that
242 -- in the manifest anyway for backward compatibility.
243 if not cur_name and deploy_type == "lua" and item_name:match("%.init$") then
244 cur_name, cur_version = manif.get_current_provider(item_type, (item_name:gsub("%.init$", "")))
245 end
246
247 if (not cur_name)
248 or (name < cur_name)
249 or (name == cur_name and (version == cur_version
250 or vers.compare_versions(version, cur_version))) then
251 return "nv", cur_name, cur_version, item_name
252 else
253 -- Existing version has priority, deploy new version using versioned name.
254 return "v", cur_name, cur_version, item_name
255 end
256end
257
258local function backup_existing(should_backup, target)
259 if not should_backup then
260 fs.delete(target)
261 return
262 end
263 if fs.exists(target) then
264 local backup = target
265 repeat
266 backup = backup.."~"
267 until not fs.exists(backup) -- Slight race condition here, but shouldn't be a problem.
268
269 util.warning(target.." is not tracked by this installation of LuaRocks. Moving it to "..backup)
270 local move_ok, move_err = os.rename(target, backup)
271 if not move_ok then
272 return nil, move_err
273 end
274 return backup
275 end
276end
277
278local function prepare_op_install()
279 local mkdirs = {}
280 local rmdirs = {}
281
282 local function memoize_mkdir(d)
283 if mkdirs[d] then
284 return true
285 end
286 local ok, err = fs.make_dir(d)
287 if not ok then
288 return nil, err
289 end
290 mkdirs[d] = true
291 return true
292 end
293
294 local function op_install(op)
295 local ok, err = memoize_mkdir(dir.dir_name(op.dst))
296 if not ok then
297 return nil, err
298 end
299
300 local backup, err = backup_existing(op.backup, op.dst)
301 if err then
302 return nil, err
303 end
304 if backup then
305 op.backup_file = backup
306 end
307
308 ok, err = op.fn(op.src, op.dst, op.backup)
309 if not ok then
310 return nil, err
311 end
312
313 rmdirs[dir.dir_name(op.src)] = true
314 return true
315 end
316
317 local function done_op_install()
318 for d, _ in pairs(rmdirs) do
319 fs.remove_dir_tree_if_empty(d)
320 end
321 end
322
323 return op_install, done_op_install
324end
325
326local function rollback_install(op)
327 fs.delete(op.dst)
328 if op.backup_file then
329 os.rename(op.backup_file, op.dst)
330 end
331 fs.remove_dir_tree_if_empty(dir.dir_name(op.dst))
332 return true
333end
334
335local function op_rename(op)
336 if op.suffix then
337 local suffix = check_suffix(op.src, op.suffix)
338 op.src = op.src .. suffix
339 op.dst = op.dst .. suffix
340 end
341
342 if fs.exists(op.src) then
343 fs.make_dir(dir.dir_name(op.dst))
344 fs.delete(op.dst)
345 local ok, err = os.rename(op.src, op.dst)
346 fs.remove_dir_tree_if_empty(dir.dir_name(op.src))
347 return ok, err
348 else
349 return true
350 end
351end
352
353local function rollback_rename(op)
354 return op_rename({ src = op.dst, dst = op.src })
355end
356
357local function prepare_op_delete()
358 local deletes = {}
359 local rmdirs = {}
360
361 local function done_op_delete()
362 for _, f in ipairs(deletes) do
363 os.remove(f)
364 end
365
366 for d, _ in pairs(rmdirs) do
367 fs.remove_dir_tree_if_empty(d)
368 end
369 end
370
371 local function op_delete(op)
372 if op.suffix then
373 local suffix = check_suffix(op.name, op.suffix)
374 op.name = op.name .. suffix
375 end
376
377 table.insert(deletes, op.name)
378
379 rmdirs[dir.dir_name(op.name)] = true
380 end
381
382 return op_delete, done_op_delete
383end
384
385local function rollback_ops(ops, op_fn, n)
386 for i = 1, n do
387 op_fn(ops[i])
388 end
389end
390
391--- Double check that all files referenced in `rock_manifest` are installed in `repo`.
392function repos.check_everything_is_installed(name, version, rock_manifest, repo, accept_versioned)
393 local missing = {}
394 local suffix = cfg.wrapper_suffix or ""
395 for _, category in ipairs({"bin", "lua", "lib"}) do
396 if rock_manifest[category] then
397 repos.recurse_rock_manifest_entry(rock_manifest[category], function(file_path)
398 local paths = get_deploy_paths(name, version, category, file_path, repo)
399 if category == "bin" then
400 if (fs.exists(paths.nv) or fs.exists(paths.nv .. suffix))
401 or (accept_versioned and (fs.exists(paths.v) or fs.exists(paths.v .. suffix))) then
402 return
403 end
404 else
405 if fs.exists(paths.nv) or (accept_versioned and fs.exists(paths.v)) then
406 return
407 end
408 end
409 table.insert(missing, paths.nv)
410 end)
411 end
412 end
413 if #missing > 0 then
414 return nil, "failed deploying files. " ..
415 "The following files were not installed:\n" ..
416 table.concat(missing, "\n")
417 end
418 return true
419end
420
421--- Deploy a package from the rocks subdirectory.
422-- @param name string: name of package
423-- @param version string: exact package version in string format
424-- @param wrap_bin_scripts bool: whether commands written in Lua should be wrapped.
425-- @param deps_mode: string: Which trees to check dependencies for:
426-- "one" for the current default tree, "all" for all trees,
427-- "order" for all trees with priority >= the current default, "none" for no trees.
428function repos.deploy_local_files(name, version, wrap_bin_scripts, deps_mode)
429 assert(type(name) == "string" and not name:match("/"))
430 assert(type(version) == "string")
431 assert(type(wrap_bin_scripts) == "boolean")
432
433 local rock_manifest, load_err = manif.load_rock_manifest(name, version)
434 if not rock_manifest then return nil, load_err end
435
436 local repo = cfg.root_dir
437 local renames = {}
438 local installs = {}
439
440 local function install_binary(source, target)
441 if wrap_bin_scripts and fs.is_lua(source) then
442 return fs.wrap_script(source, target, deps_mode, name, version)
443 else
444 return fs.copy_binary(source, target)
445 end
446 end
447
448 local function move_lua(source, target)
449 return fs.move(source, target, "read")
450 end
451
452 local function move_lib(source, target)
453 return fs.move(source, target, "exec")
454 end
455
456 if rock_manifest.bin then
457 local source_dir = path.bin_dir(name, version)
458 repos.recurse_rock_manifest_entry(rock_manifest.bin, function(file_path)
459 local source = dir.path(source_dir, file_path)
460 local paths = get_deploy_paths(name, version, "bin", file_path, repo)
461 local mode, cur_name, cur_version = check_spot_if_available(name, version, "bin", file_path)
462
463 if mode == "nv" and cur_name then
464 local cur_paths = get_deploy_paths(cur_name, cur_version, "bin", file_path, repo)
465 table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v, suffix = cfg.wrapper_suffix })
466 end
467 local target = mode == "nv" and paths.nv or paths.v
468 local backup = name ~= cur_name or version ~= cur_version
469 if wrap_bin_scripts and fs.is_lua(source) then
470 target = target .. (cfg.wrapper_suffix or "")
471 end
472 table.insert(installs, { fn = install_binary, src = source, dst = target, backup = backup })
473 end)
474 end
475
476 if rock_manifest.lua then
477 local source_dir = path.lua_dir(name, version)
478 repos.recurse_rock_manifest_entry(rock_manifest.lua, function(file_path)
479 local source = dir.path(source_dir, file_path)
480 local paths = get_deploy_paths(name, version, "lua", file_path, repo)
481 local mode, cur_name, cur_version = check_spot_if_available(name, version, "lua", file_path)
482
483 if mode == "nv" and cur_name then
484 local cur_paths = get_deploy_paths(cur_name, cur_version, "lua", file_path, repo)
485 table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v })
486 cur_paths = get_deploy_paths(cur_name, cur_version, "lib", file_path:gsub("%.lua$", "." .. cfg.lib_extension), repo)
487 table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v })
488 end
489 local target = mode == "nv" and paths.nv or paths.v
490 local backup = name ~= cur_name or version ~= cur_version
491 table.insert(installs, { fn = move_lua, src = source, dst = target, backup = backup })
492 end)
493 end
494
495 if rock_manifest.lib then
496 local source_dir = path.lib_dir(name, version)
497 repos.recurse_rock_manifest_entry(rock_manifest.lib, function(file_path)
498 local source = dir.path(source_dir, file_path)
499 local paths = get_deploy_paths(name, version, "lib", file_path, repo)
500 local mode, cur_name, cur_version = check_spot_if_available(name, version, "lib", file_path)
501
502 if mode == "nv" and cur_name then
503 local cur_paths = get_deploy_paths(cur_name, cur_version, "lua", file_path:gsub("%.[^.]+$", ".lua"), repo)
504 table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v })
505 cur_paths = get_deploy_paths(cur_name, cur_version, "lib", file_path, repo)
506 table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v })
507 end
508 local target = mode == "nv" and paths.nv or paths.v
509 local backup = name ~= cur_name or version ~= cur_version
510 table.insert(installs, { fn = move_lib, src = source, dst = target, backup = backup })
511 end)
512 end
513
514 for i, op in ipairs(renames) do
515 local ok, err = op_rename(op)
516 if not ok then
517 rollback_ops(renames, rollback_rename, i - 1)
518 return nil, err
519 end
520 end
521 local op_install, done_op_install = prepare_op_install()
522 for i, op in ipairs(installs) do
523 local ok, err = op_install(op)
524 if not ok then
525 rollback_ops(installs, rollback_install, i - 1)
526 rollback_ops(renames, rollback_rename, #renames)
527 return nil, err
528 end
529 end
530 done_op_install()
531
532 local ok, err = repos.check_everything_is_installed(name, version, rock_manifest, repo, true)
533 if not ok then
534 return nil, err
535 end
536
537 return true
538end
539
540local function add_to_double_checks(double_checks, name, version)
541 double_checks[name] = double_checks[name] or {}
542 double_checks[name][version] = true
543end
544
545local function double_check_all(double_checks, repo)
546 local errs = {}
547 for next_name, versions in pairs(double_checks) do
548 for next_version in pairs(versions) do
549 local rock_manifest, load_err = manif.load_rock_manifest(next_name, next_version)
550 local ok, err = repos.check_everything_is_installed(next_name, next_version, rock_manifest, repo, true)
551 if not ok then
552 table.insert(errs, err)
553 end
554 end
555 end
556 if next(errs) then
557 return nil, table.concat(errs, "\n")
558 end
559 return true
560end
561
562--- Delete a package from the local repository.
563-- @param name string: name of package
564-- @param version string: exact package version in string format
565-- @param deps_mode: string: Which trees to check dependencies for:
566-- "one" for the current default tree, "all" for all trees,
567-- "order" for all trees with priority >= the current default, "none" for no trees.
568-- @param quick boolean: do not try to fix the versioned name
569-- of another version that provides the same module that
570-- was deleted. This is used during 'purge', as every module
571-- will be eventually deleted.
572function repos.delete_local_version(name, version, deps_mode, quick)
573 assert(type(name) == "string" and not name:match("/"))
574 assert(type(version) == "string")
575 assert(type(deps_mode) == "string")
576
577 local rock_manifest, load_err = manif.load_rock_manifest(name, version)
578 if not rock_manifest then
579 if not quick then
580 return nil, "rock_manifest file not found for "..name.." "..version.." - removed entry from the manifest", "remove"
581 end
582 return nil, load_err, "fail"
583 end
584
585 local repo = cfg.root_dir
586 local renames = {}
587 local deletes = {}
588
589 local double_checks = {}
590
591 if rock_manifest.bin then
592 repos.recurse_rock_manifest_entry(rock_manifest.bin, function(file_path)
593 local paths = get_deploy_paths(name, version, "bin", file_path, repo)
594 local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "bin", file_path)
595 if mode == "v" then
596 table.insert(deletes, { name = paths.v, suffix = cfg.wrapper_suffix })
597 else
598 table.insert(deletes, { name = paths.nv, suffix = cfg.wrapper_suffix })
599
600 local next_name, next_version = manif.get_next_provider("command", item_name)
601 if next_name then
602 add_to_double_checks(double_checks, next_name, next_version)
603 local next_paths = get_deploy_paths(next_name, next_version, "bin", file_path, repo)
604 table.insert(renames, { src = next_paths.v, dst = next_paths.nv, suffix = cfg.wrapper_suffix })
605 end
606 end
607 end)
608 end
609
610 if rock_manifest.lua then
611 repos.recurse_rock_manifest_entry(rock_manifest.lua, function(file_path)
612 local paths = get_deploy_paths(name, version, "lua", file_path, repo)
613 local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "lua", file_path)
614 if mode == "v" then
615 table.insert(deletes, { name = paths.v })
616 else
617 table.insert(deletes, { name = paths.nv })
618
619 local next_name, next_version = manif.get_next_provider("module", item_name)
620 if next_name then
621 add_to_double_checks(double_checks, next_name, next_version)
622 local next_lua_paths = get_deploy_paths(next_name, next_version, "lua", file_path, repo)
623 table.insert(renames, { src = next_lua_paths.v, dst = next_lua_paths.nv })
624 local next_lib_paths = get_deploy_paths(next_name, next_version, "lib", file_path:gsub("%.[^.]+$", ".lua"), repo)
625 table.insert(renames, { src = next_lib_paths.v, dst = next_lib_paths.nv })
626 end
627 end
628 end)
629 end
630
631 if rock_manifest.lib then
632 repos.recurse_rock_manifest_entry(rock_manifest.lib, function(file_path)
633 local paths = get_deploy_paths(name, version, "lib", file_path, repo)
634 local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "lib", file_path)
635 if mode == "v" then
636 table.insert(deletes, { name = paths.v })
637 else
638 table.insert(deletes, { name = paths.nv })
639
640 local next_name, next_version = manif.get_next_provider("module", item_name)
641 if next_name then
642 add_to_double_checks(double_checks, next_name, next_version)
643 local next_lua_paths = get_deploy_paths(next_name, next_version, "lua", file_path:gsub("%.[^.]+$", ".lua"), repo)
644 table.insert(renames, { src = next_lua_paths.v, dst = next_lua_paths.nv })
645 local next_lib_paths = get_deploy_paths(next_name, next_version, "lib", file_path, repo)
646 table.insert(renames, { src = next_lib_paths.v, dst = next_lib_paths.nv })
647 end
648 end
649 end)
650 end
651
652 local op_delete, done_op_delete = prepare_op_delete()
653 for _, op in ipairs(deletes) do
654 op_delete(op)
655 end
656 done_op_delete()
657
658 if not quick then
659 for _, op in ipairs(renames) do
660 op_rename(op)
661 end
662
663 local ok, err = double_check_all(double_checks, repo)
664 if not ok then
665 return nil, err, "fail"
666 end
667 end
668
669 fs.delete(path.install_dir(name, version))
670 if not get_installed_versions(name) then
671 fs.delete(dir.path(cfg.rocks_dir, name))
672 end
673
674 if quick then
675 return true, nil, "ok"
676 end
677
678 return true, nil, "remove"
679end
680
681return repos
diff --git a/src/luarocks/repos.lua b/src/luarocks/repos.lua
index 2e71b3f7..186bc1c1 100644
--- a/src/luarocks/repos.lua
+++ b/src/luarocks/repos.lua
@@ -1,6 +1,20 @@
1local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local os = _tl_compat and _tl_compat.os or os; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table
2
3local repos = {Op = {}, Paths = {}, }
4
5
6
7
8
9
10
11
12
13
14
15
16
1 17
2--- Functions for managing the repository on disk.
3local repos = {}
4 18
5local fs = require("luarocks.fs") 19local fs = require("luarocks.fs")
6local path = require("luarocks.path") 20local path = require("luarocks.path")
@@ -10,45 +24,55 @@ local dir = require("luarocks.dir")
10local manif = require("luarocks.manif") 24local manif = require("luarocks.manif")
11local vers = require("luarocks.core.vers") 25local vers = require("luarocks.core.vers")
12 26
13local unpack = unpack or table.unpack -- luacheck: ignore 211 27
14 28
15-- Tree of files installed by a package are stored 29
16-- in its rock manifest. Some of these files have to 30
17-- be deployed to locations where Lua can load them as 31
18-- modules or where they can be used as commands. 32
19-- These files are characterised by pair 33
20-- (deploy_type, file_path), where deploy_type is the first 34
21-- component of the file path and file_path is the rest of the 35
22-- path. Only files with deploy_type in {"lua", "lib", "bin"} 36
23-- are deployed somewhere. 37
24-- Each deployed file provides an "item". An item is 38
25-- characterised by pair (item_type, item_name). 39
26-- item_type is "command" for files with deploy_type 40
27-- "bin" and "module" for deploy_type in {"lua", "lib"}. 41
28-- item_name is same as file_path for commands 42
29-- and is produced using path.path_to_module(file_path) 43
30-- for modules. 44
31 45
32--- Get all installed versions of a package. 46
33-- @param name string: a package name. 47
34-- @return table or nil: An array of strings listing installed 48
35-- versions of a package, or nil if none is available. 49
50
51
52
53
54
55
56
57
58
59
60
36local function get_installed_versions(name) 61local function get_installed_versions(name)
37 assert(type(name) == "string" and not name:match("/")) 62 assert(not name:match("/"))
38 63
39 local dirs = fs.list_dir(path.versions_dir(name)) 64 local dirs = fs.list_dir(path.versions_dir(name))
40 return (dirs and #dirs > 0) and dirs or nil 65 return (dirs and #dirs > 0) and dirs or nil
41end 66end
42 67
43--- Check if a package exists in a local repository. 68
44-- Version numbers are compared as exact string comparison. 69
45-- @param name string: name of package 70
46-- @param version string: package version in string format 71
47-- @return boolean: true if a package is installed, 72
48-- false otherwise. 73
49function repos.is_installed(name, version) 74function repos.is_installed(name, version)
50 assert(type(name) == "string" and not name:match("/")) 75 assert(not name:match("/"))
51 assert(type(version) == "string")
52 76
53 return fs.is_dir(path.install_dir(name, version)) 77 return fs.is_dir(path.install_dir(name, version))
54end 78end
@@ -64,7 +88,7 @@ function repos.recurse_rock_manifest_entry(entry, action)
64 88
65 for file, sub in pairs(tree) do 89 for file, sub in pairs(tree) do
66 local sub_path = (parent_path and (parent_path .. "/") or "") .. file 90 local sub_path = (parent_path and (parent_path .. "/") or "") .. file
67 local ok, err -- luacheck: ignore 231 91 local ok, err
68 92
69 if type(sub) == "table" then 93 if type(sub) == "table" then
70 ok, err = do_recurse_rock_manifest_entry(sub, sub_path) 94 ok, err = do_recurse_rock_manifest_entry(sub, sub_path)
@@ -89,18 +113,17 @@ local function store_package_data(result, rock_manifest, deploy_type)
89 end 113 end
90end 114end
91 115
92--- Obtain a table of modules within an installed package. 116
93-- @param name string: The package name; for example "luasocket" 117
94-- @param version string: The exact version number including revision; 118
95-- for example "2.0.1-1". 119
96-- @return table: A table of modules where keys are module names 120
97-- and values are file paths of files providing modules 121
98-- relative to "lib" or "lua" rock manifest subtree. 122
99-- If no modules are found or if package name or version 123
100-- are invalid, an empty table is returned. 124
101function repos.package_modules(name, version) 125function repos.package_modules(name, version)
102 assert(type(name) == "string" and not name:match("/")) 126 assert(not name:match("/"))
103 assert(type(version) == "string")
104 127
105 local result = {} 128 local result = {}
106 local rock_manifest = manif.load_rock_manifest(name, version) 129 local rock_manifest = manif.load_rock_manifest(name, version)
@@ -110,18 +133,17 @@ function repos.package_modules(name, version)
110 return result 133 return result
111end 134end
112 135
113--- Obtain a table of command-line scripts within an installed package. 136
114-- @param name string: The package name; for example "luasocket" 137
115-- @param version string: The exact version number including revision; 138
116-- for example "2.0.1-1". 139
117-- @return table: A table of commands where keys and values are command names 140
118-- as strings - file paths of files providing commands 141
119-- relative to "bin" rock manifest subtree. 142
120-- If no commands are found or if package name or version 143
121-- are invalid, an empty table is returned. 144
122function repos.package_commands(name, version) 145function repos.package_commands(name, version)
123 assert(type(name) == "string" and not name:match("/")) 146 assert(not name:match("/"))
124 assert(type(version) == "string")
125 147
126 local result = {} 148 local result = {}
127 local rock_manifest = manif.load_rock_manifest(name, version) 149 local rock_manifest = manif.load_rock_manifest(name, version)
@@ -131,19 +153,22 @@ function repos.package_commands(name, version)
131end 153end
132 154
133 155
134--- Check if a rock contains binary executables. 156
135-- @param name string: name of an installed rock 157
136-- @param version string: version of an installed rock 158
137-- @return boolean: returns true if rock contains platform-specific 159
138-- binary executables, or false if it is a pure-Lua rock. 160
139function repos.has_binaries(name, version) 161function repos.has_binaries(name, version)
140 assert(type(name) == "string" and not name:match("/")) 162 assert(not name:match("/"))
141 assert(type(version) == "string") 163
164 local entries = manif.load_rock_manifest(name, version)
165 if not entries then
166 return false
167 end
168 local bin = entries["bin"]
169 if type(bin) == "table" then
170 for bin_name, md5 in pairs(bin) do
142 171
143 local rock_manifest = manif.load_rock_manifest(name, version)
144 if rock_manifest and rock_manifest.bin then
145 for bin_name, md5 in pairs(rock_manifest.bin) do
146 -- TODO verify that it is the same file. If it isn't, find the actual command.
147 if fs.is_actual_binary(dir.path(cfg.deploy_bin_dir, bin_name)) then 172 if fs.is_actual_binary(dir.path(cfg.deploy_bin_dir, bin_name)) then
148 return true 173 return true
149 end 174 end
@@ -153,8 +178,6 @@ function repos.has_binaries(name, version)
153end 178end
154 179
155function repos.run_hook(rockspec, hook_name) 180function repos.run_hook(rockspec, hook_name)
156 assert(rockspec:type() == "rockspec")
157 assert(type(hook_name) == "string")
158 181
159 local hooks = rockspec.hooks 182 local hooks = rockspec.hooks
160 if not hooks then 183 if not hooks then
@@ -169,18 +192,17 @@ function repos.run_hook(rockspec, hook_name)
169 util.variable_substitutions(hooks, rockspec.variables) 192 util.variable_substitutions(hooks, rockspec.variables)
170 hooks.substituted_variables = true 193 hooks.substituted_variables = true
171 end 194 end
172 local hook = hooks[hook_name] 195 local hook = (hooks)[hook_name]
173 if hook then 196 if hook then
174 util.printout(hook) 197 util.printout(hook)
175 if not fs.execute(hook) then 198 if not fs.execute(hook) then
176 return nil, "Failed running "..hook_name.." hook." 199 return nil, "Failed running " .. hook_name .. " hook."
177 end 200 end
178 end 201 end
179 return true 202 return true
180end 203end
181 204
182function repos.should_wrap_bin_scripts(rockspec) 205function repos.should_wrap_bin_scripts(rockspec)
183 assert(rockspec:type() == "rockspec")
184 206
185 if cfg.wrap_bin_scripts ~= nil then 207 if cfg.wrap_bin_scripts ~= nil then
186 return cfg.wrap_bin_scripts 208 return cfg.wrap_bin_scripts
@@ -192,7 +214,7 @@ function repos.should_wrap_bin_scripts(rockspec)
192end 214end
193 215
194local function find_suffixed(file, suffix) 216local function find_suffixed(file, suffix)
195 local filenames = {file} 217 local filenames = { file }
196 if suffix and suffix ~= "" then 218 if suffix and suffix ~= "" then
197 table.insert(filenames, 1, file .. suffix) 219 table.insert(filenames, 1, file .. suffix)
198 end 220 end
@@ -214,20 +236,16 @@ local function check_suffix(filename, suffix)
214 return suffixed_filename:sub(#filename + 1) 236 return suffixed_filename:sub(#filename + 1)
215end 237end
216 238
217-- Files can be deployed using versioned and non-versioned names. 239
218-- Several items with same type and name can exist if they are 240
219-- provided by different packages or versions. In any case 241
220-- item from the newest version of lexicographically smallest package 242
221-- is deployed using non-versioned name and others use versioned names. 243
222 244
223local function get_deploy_paths(name, version, deploy_type, file_path, repo) 245local function get_deploy_paths(name, version, deploy_type, file_path, repo)
224 assert(type(name) == "string")
225 assert(type(version) == "string")
226 assert(type(deploy_type) == "string")
227 assert(type(file_path) == "string")
228 246
229 repo = repo or cfg.root_dir 247 repo = repo or cfg.root_dir
230 local deploy_dir = path["deploy_" .. deploy_type .. "_dir"](repo) 248 local deploy_dir = (path)["deploy_" .. deploy_type .. "_dir"](repo)
231 local non_versioned = dir.path(deploy_dir, file_path) 249 local non_versioned = dir.path(deploy_dir, file_path)
232 local versioned = path.versioned_name(non_versioned, deploy_dir, name, version) 250 local versioned = path.versioned_name(non_versioned, deploy_dir, name, version)
233 return { nv = non_versioned, v = versioned } 251 return { nv = non_versioned, v = versioned }
@@ -237,20 +255,20 @@ local function check_spot_if_available(name, version, deploy_type, file_path)
237 local item_type, item_name = manif.get_provided_item(deploy_type, file_path) 255 local item_type, item_name = manif.get_provided_item(deploy_type, file_path)
238 local cur_name, cur_version = manif.get_current_provider(item_type, item_name) 256 local cur_name, cur_version = manif.get_current_provider(item_type, item_name)
239 257
240 -- older versions of LuaRocks (< 3) registered "foo.init" files as "foo" 258
241 -- (which caused problems, so that behavior was changed). But look for that 259
242 -- in the manifest anyway for backward compatibility. 260
243 if not cur_name and deploy_type == "lua" and item_name:match("%.init$") then 261 if not cur_name and deploy_type == "lua" and item_name:match("%.init$") then
244 cur_name, cur_version = manif.get_current_provider(item_type, (item_name:gsub("%.init$", ""))) 262 cur_name, cur_version = manif.get_current_provider(item_type, (item_name:gsub("%.init$", "")))
245 end 263 end
246 264
247 if (not cur_name) 265 if (not cur_name) or
248 or (name < cur_name) 266 (name < cur_name) or
249 or (name == cur_name and (version == cur_version 267 (name == cur_name and (version == cur_version or
250 or vers.compare_versions(version, cur_version))) then 268 vers.compare_versions(version, cur_version))) then
251 return "nv", cur_name, cur_version, item_name 269 return "nv", cur_name, cur_version, item_name
252 else 270 else
253 -- Existing version has priority, deploy new version using versioned name. 271
254 return "v", cur_name, cur_version, item_name 272 return "v", cur_name, cur_version, item_name
255 end 273 end
256end 274end
@@ -263,10 +281,10 @@ local function backup_existing(should_backup, target)
263 if fs.exists(target) then 281 if fs.exists(target) then
264 local backup = target 282 local backup = target
265 repeat 283 repeat
266 backup = backup.."~" 284 backup = backup .. "~"
267 until not fs.exists(backup) -- Slight race condition here, but shouldn't be a problem. 285 until not fs.exists(backup)
268 286
269 util.warning(target.." is not tracked by this installation of LuaRocks. Moving it to "..backup) 287 util.warning(target .. " is not tracked by this installation of LuaRocks. Moving it to " .. backup)
270 local move_ok, move_err = os.rename(target, backup) 288 local move_ok, move_err = os.rename(target, backup)
271 if not move_ok then 289 if not move_ok then
272 return nil, move_err 290 return nil, move_err
@@ -388,17 +406,17 @@ local function rollback_ops(ops, op_fn, n)
388 end 406 end
389end 407end
390 408
391--- Double check that all files referenced in `rock_manifest` are installed in `repo`. 409
392function repos.check_everything_is_installed(name, version, rock_manifest, repo, accept_versioned) 410function repos.check_everything_is_installed(name, version, rock_manifest, repo, accept_versioned)
393 local missing = {} 411 local missing = {}
394 local suffix = cfg.wrapper_suffix or "" 412 local suffix = cfg.wrapper_suffix or ""
395 for _, category in ipairs({"bin", "lua", "lib"}) do 413 for _, category in ipairs({ "bin", "lua", "lib" }) do
396 if rock_manifest[category] then 414 if rock_manifest[category] then
397 repos.recurse_rock_manifest_entry(rock_manifest[category], function(file_path) 415 repos.recurse_rock_manifest_entry(rock_manifest[category], function(file_path)
398 local paths = get_deploy_paths(name, version, category, file_path, repo) 416 local paths = get_deploy_paths(name, version, category, file_path, repo)
399 if category == "bin" then 417 if category == "bin" then
400 if (fs.exists(paths.nv) or fs.exists(paths.nv .. suffix)) 418 if (fs.exists(paths.nv) or fs.exists(paths.nv .. suffix)) or
401 or (accept_versioned and (fs.exists(paths.v) or fs.exists(paths.v .. suffix))) then 419 (accept_versioned and (fs.exists(paths.v) or fs.exists(paths.v .. suffix))) then
402 return 420 return
403 end 421 end
404 else 422 else
@@ -412,23 +430,21 @@ function repos.check_everything_is_installed(name, version, rock_manifest, repo,
412 end 430 end
413 if #missing > 0 then 431 if #missing > 0 then
414 return nil, "failed deploying files. " .. 432 return nil, "failed deploying files. " ..
415 "The following files were not installed:\n" .. 433 "The following files were not installed:\n" ..
416 table.concat(missing, "\n") 434 table.concat(missing, "\n")
417 end 435 end
418 return true 436 return true
419end 437end
420 438
421--- Deploy a package from the rocks subdirectory. 439
422-- @param name string: name of package 440
423-- @param version string: exact package version in string format 441
424-- @param wrap_bin_scripts bool: whether commands written in Lua should be wrapped. 442
425-- @param deps_mode: string: Which trees to check dependencies for: 443
426-- "one" for the current default tree, "all" for all trees, 444
427-- "order" for all trees with priority >= the current default, "none" for no trees. 445
428function repos.deploy_local_files(name, version, wrap_bin_scripts, deps_mode) 446function repos.deploy_local_files(name, version, wrap_bin_scripts, deps_mode)
429 assert(type(name) == "string" and not name:match("/")) 447 assert(not name:match("/"))
430 assert(type(version) == "string")
431 assert(type(wrap_bin_scripts) == "boolean")
432 448
433 local rock_manifest, load_err = manif.load_rock_manifest(name, version) 449 local rock_manifest, load_err = manif.load_rock_manifest(name, version)
434 if not rock_manifest then return nil, load_err end 450 if not rock_manifest then return nil, load_err end
@@ -553,31 +569,29 @@ local function double_check_all(double_checks, repo)
553 end 569 end
554 end 570 end
555 end 571 end
556 if next(errs) then 572 if next(errs) ~= nil then
557 return nil, table.concat(errs, "\n") 573 return nil, table.concat(errs, "\n")
558 end 574 end
559 return true 575 return true
560end 576end
561 577
562--- Delete a package from the local repository. 578
563-- @param name string: name of package 579
564-- @param version string: exact package version in string format 580
565-- @param deps_mode: string: Which trees to check dependencies for: 581
566-- "one" for the current default tree, "all" for all trees, 582
567-- "order" for all trees with priority >= the current default, "none" for no trees. 583
568-- @param quick boolean: do not try to fix the versioned name 584
569-- of another version that provides the same module that 585
570-- was deleted. This is used during 'purge', as every module 586
571-- will be eventually deleted. 587
572function repos.delete_local_version(name, version, deps_mode, quick) 588function repos.delete_local_version(name, version, deps_mode, quick)
573 assert(type(name) == "string" and not name:match("/")) 589 assert(not name:match("/"))
574 assert(type(version) == "string")
575 assert(type(deps_mode) == "string")
576 590
577 local rock_manifest, load_err = manif.load_rock_manifest(name, version) 591 local rock_manifest, load_err = manif.load_rock_manifest(name, version)
578 if not rock_manifest then 592 if not rock_manifest then
579 if not quick then 593 if not quick then
580 return nil, "rock_manifest file not found for "..name.." "..version.." - removed entry from the manifest", "remove" 594 return nil, "rock_manifest file not found for " .. name .. " " .. version .. " - removed entry from the manifest", "remove"
581 end 595 end
582 return nil, load_err, "fail" 596 return nil, load_err, "fail"
583 end 597 end
diff --git a/src/luarocks/repos.tl b/src/luarocks/repos.tl
index b5e1efe8..e98ffedb 100644
--- a/src/luarocks/repos.tl
+++ b/src/luarocks/repos.tl
@@ -341,7 +341,7 @@ local function prepare_op_install(): function(Op): (boolean, string), function()
341 return op_install, done_op_install 341 return op_install, done_op_install
342end 342end
343 343
344local function rollback_install(op: Op): boolean 344local function rollback_install(op: Op): boolean, string
345 fs.delete(op.dst) 345 fs.delete(op.dst)
346 if op.backup_file then 346 if op.backup_file then
347 os.rename(op.backup_file, op.dst) 347 os.rename(op.backup_file, op.dst)
@@ -553,13 +553,13 @@ function repos.deploy_local_files(name: string, version: string, wrap_bin_script
553 return true 553 return true
554end 554end
555 555
556local function add_to_double_checks(double_checks, name, version) 556local function add_to_double_checks(double_checks: {string: {string: boolean}}, name: string, version: string)
557 double_checks[name] = double_checks[name] or {} 557 double_checks[name] = double_checks[name] or {}
558 double_checks[name][version] = true 558 double_checks[name][version] = true
559end 559end
560 560
561local function double_check_all(double_checks, repo) 561local function double_check_all(double_checks: {string: {string: boolean}}, repo: string | Tree): boolean, string
562 local errs = {} 562 local errs: {string} = {}
563 for next_name, versions in pairs(double_checks) do 563 for next_name, versions in pairs(double_checks) do
564 for next_version in pairs(versions) do 564 for next_version in pairs(versions) do
565 local rock_manifest, load_err = manif.load_rock_manifest(next_name, next_version) 565 local rock_manifest, load_err = manif.load_rock_manifest(next_name, next_version)
@@ -569,7 +569,7 @@ local function double_check_all(double_checks, repo)
569 end 569 end
570 end 570 end
571 end 571 end
572 if next(errs) then 572 if next(errs) ~= nil then
573 return nil, table.concat(errs, "\n") 573 return nil, table.concat(errs, "\n")
574 end 574 end
575 return true 575 return true
@@ -585,10 +585,8 @@ end
585-- of another version that provides the same module that 585-- of another version that provides the same module that
586-- was deleted. This is used during 'purge', as every module 586-- was deleted. This is used during 'purge', as every module
587-- will be eventually deleted. 587-- will be eventually deleted.
588function repos.delete_local_version(name, version, deps_mode, quick) 588function repos.delete_local_version(name: string, version: string, deps_mode: string, quick: boolean): boolean, string, string
589 assert(type(name) == "string" and not name:match("/")) 589 assert(not name:match("/"))
590 assert(type(version) == "string")
591 assert(type(deps_mode) == "string")
592 590
593 local rock_manifest, load_err = manif.load_rock_manifest(name, version) 591 local rock_manifest, load_err = manif.load_rock_manifest(name, version)
594 if not rock_manifest then 592 if not rock_manifest then
@@ -605,7 +603,7 @@ function repos.delete_local_version(name, version, deps_mode, quick)
605 local double_checks = {} 603 local double_checks = {}
606 604
607 if rock_manifest.bin then 605 if rock_manifest.bin then
608 repos.recurse_rock_manifest_entry(rock_manifest.bin, function(file_path) 606 repos.recurse_rock_manifest_entry(rock_manifest.bin, function(file_path: string): boolean, string
609 local paths = get_deploy_paths(name, version, "bin", file_path, repo) 607 local paths = get_deploy_paths(name, version, "bin", file_path, repo)
610 local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "bin", file_path) 608 local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "bin", file_path)
611 if mode == "v" then 609 if mode == "v" then
@@ -624,7 +622,7 @@ function repos.delete_local_version(name, version, deps_mode, quick)
624 end 622 end
625 623
626 if rock_manifest.lua then 624 if rock_manifest.lua then
627 repos.recurse_rock_manifest_entry(rock_manifest.lua, function(file_path) 625 repos.recurse_rock_manifest_entry(rock_manifest.lua, function(file_path: string): boolean, string
628 local paths = get_deploy_paths(name, version, "lua", file_path, repo) 626 local paths = get_deploy_paths(name, version, "lua", file_path, repo)
629 local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "lua", file_path) 627 local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "lua", file_path)
630 if mode == "v" then 628 if mode == "v" then
@@ -645,7 +643,7 @@ function repos.delete_local_version(name, version, deps_mode, quick)
645 end 643 end
646 644
647 if rock_manifest.lib then 645 if rock_manifest.lib then
648 repos.recurse_rock_manifest_entry(rock_manifest.lib, function(file_path) 646 repos.recurse_rock_manifest_entry(rock_manifest.lib, function(file_path: string): boolean, string
649 local paths = get_deploy_paths(name, version, "lib", file_path, repo) 647 local paths = get_deploy_paths(name, version, "lib", file_path, repo)
650 local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "lib", file_path) 648 local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "lib", file_path)
651 if mode == "v" then 649 if mode == "v" then
@@ -694,4 +692,4 @@ function repos.delete_local_version(name, version, deps_mode, quick)
694 return true, nil, "remove" 692 return true, nil, "remove"
695end 693end
696 694
697return rep \ No newline at end of file 695return repos \ No newline at end of file