diff options
author | Daniel Silverstone <dsilvers@digital-scurf.org> | 2013-05-27 17:21:07 +0100 |
---|---|---|
committer | Daniel Silverstone <dsilvers@digital-scurf.org> | 2013-05-27 17:21:07 +0100 |
commit | 9f1d54962fbd4dee155ccd099d34b4f064e20237 (patch) | |
tree | ecbcd19268d7f7dfae692fdc3653f932b84afaca | |
parent | a3fbdec53f490f83dbaacf5f62c7d4b9a05896e5 (diff) | |
parent | 186495107f9978e204230bb9a3f3d0c612a3542b (diff) | |
download | gitano-9f1d54962fbd4dee155ccd099d34b4f064e20237.tar.gz |
Merge remote-tracking branch 'richardmaw/add-copy-command-rebase3'
Reviewed-By: Daniel Silverstone <dsilvers@digital-scurf.org>
-rw-r--r-- | lib/gitano/command.lua | 4 | ||||
-rw-r--r-- | lib/gitano/copycommand.lua | 108 | ||||
-rw-r--r-- | lib/gitano/repository.lua | 69 | ||||
-rw-r--r-- | lib/gitano/util.lua | 143 |
4 files changed, 319 insertions, 5 deletions
diff --git a/lib/gitano/command.lua b/lib/gitano/command.lua index 80fa554..e55fe33 100644 --- a/lib/gitano/command.lua +++ b/lib/gitano/command.lua @@ -766,7 +766,7 @@ local function builtin_rename_prep(config, repo, cmdline, context) local ctx, action, reason -- Check 0, is the current repo nascent if repo.is_nascent then - return "deny", "Cannot rename a repository which does not exit" + return "deny", "Cannot rename a repository which does not exist" end -- Check 1, read current repo ctx = util.deep_copy(context) @@ -970,6 +970,8 @@ local admincmds = require 'gitano.admincommand' admincmds.register(register_cmd) local repocmds = require 'gitano.repocommand' repocmds.register(register_cmd) +local copycmds = require 'gitano.copycommand' +copycmds.register(register_cmd) return { register = register_cmd, diff --git a/lib/gitano/copycommand.lua b/lib/gitano/copycommand.lua new file mode 100644 index 0000000..6de825a --- /dev/null +++ b/lib/gitano/copycommand.lua @@ -0,0 +1,108 @@ +-- gitano.copycommand +-- +-- Gitano repository copy commands +-- +-- Copyright 2013 Richard Maw <richard.maw@gmail.com> + +local repository = require "gitano.repository" +local log = require "gitano.log" +local util = require "gitano.util" + +local builtin_copy_short = "Copy a repository to a new path" + +local builtin_copy_helptext = [[ +usage: copy <oldrepo> <newrepo> + +Copy a git repository locally. This is quicker than fetching the +old repository, creating the new one, then pushing to the new one. +]] + +local function builtin_copy_validate(config, srcrepo, cmdline) + if #cmdline ~= 3 then + log.error("usage: copy <oldrepo> <newrepo>") + return false + end + -- Check if source repository is nascent + if srcrepo.is_nascent then + return "deny", "Cannot copy a repository which does not exist" + end + -- Create the target repository object + local tgtrepo, msg = repository.find(config, cmdline[3]) + if not tgtrepo then + log.critical("Unable to locate repository.") + log.critical(" * " .. (tostring(msg))) + log.fatal("Cannot continue") + return false + end + if not tgtrepo.is_nascent then + log.error("Repository", tgtrepo.name, "already exists") + return false + end + cmdline.tgtrepo = tgtrepo + return true +end + +local function builtin_copy_prep(config, srcrepo, cmdline, context) + local ctx, action, reason + -- Check 1, source repository can be read + ctx = util.deep_copy(context) + ctx.operation = "read" + action, reason = srcrepo:run_lace(ctx) + if action ~= "allow" then + return action, reason + end + -- Check 2, target repository can be created + ctx = util.deep_copy(context) + ctx.operation = "createrepo" + action, reason = cmdline.tgtrepo:run_lace(ctx) + if action ~= "allow" then + return action, reason + end + -- Able to read and create, thus can copy + return "allow", "Passed all checks, can copy" +end + +local function builtin_copy_run(config, repo, cmdline, env) + local ok, msg = repo:copy_to(cmdline.tgtrepo) + if not ok then + log.error(msg) + return "exit", 1 + end + log.state("Copied", cmdline[2], "to", cmdline[3]) + + local tgtrepo, msg = repository.find(config, cmdline[3]) + if not tgtrepo then + log.critical("Unable to locate repository.") + log.critical(" * " .. (tostring(msg))) + log.fatal("Cannot continue") + return false + end + + local owner = env["GITANO_USER"] + log.chat("Setting repository owner to", owner) + ok, msg = tgtrepo:set_owner(owner) + if not ok then + log.error(msg) + return "exit", 1 + end + log.chat("Running checks to ensure hooks etc are configured") + ok, msg = tgtrepo:run_checks() + if not ok then + log.error(msg) + return "exit", 1 + end + log.state("Repository", tgtrepo.name, + "copied ok. Remember to configure rules etc.") + + return "exit", 0 +end + +local function register_commands(reg) + assert(reg("copy", builtin_copy_short, builtin_copy_helptext, + builtin_copy_validate, builtin_copy_prep, builtin_copy_run, + true, false, false)) +end + +return { + register = register_commands +} diff --git a/lib/gitano/repository.lua b/lib/gitano/repository.lua index 371fb51..796e162 100644 --- a/lib/gitano/repository.lua +++ b/lib/gitano/repository.lua @@ -519,6 +519,67 @@ function repo_method:rename_to(somename) return true end +function repo_method:copy_to(target) + local ok, err + + if not target.is_nascent then + return false, "Target repository is not Nascent" + end + + local newpath = target:fs_path() + -- copy to a different path so it does not appear until finished + local temp_path = newpath .. ".in_progress" + + if not util.mkdir_p(util.dirname(temp_path)) then + return false, "Cannot prepare path leading to repository." + end + + -- attempt to create the target directory, so we can detect + -- a copy is already in progress and return without removing + -- the target directory + ok, err = luxio.mkdir(temp_path, sio.tomode'0755') + if ok ~= 0 then + log.error("Failed to copy repository", self:fs_path(), + "to", newpath .. ":", "Copy already in progress") + return false, "Copy already in progress" + end + + local from = self:fs_path() + local function filter(parent, name, info) + return parent == from and name == "objects" + or util.copy_dir_filter_base(parent, name, info) + end + -- copy non-objects parts of the git repository + ok, err = util.copy_dir(from, temp_path, nil, filter) + if not ok then + log.error("Failed to copy repository", from, "to", temp_path, err) + util.rm_rf(temp_path) + return false, "Failed to copy repository" + end + -- Hardlink the objects tree + local cbs = util.deep_copy(util.copy_dir_copy_callbacks) + cbs[luxio.DT_REG] = util.hardlink_file + ok, err = util.copy_dir(util.path_join(from, 'objects'), + util.path_join(temp_path, 'objects'), + cbs) + if not ok then + log.error("Failed to hardlink objects of", from, "to", temp_path, err) + util.rm_rf(temp_path) + return ok, "Failed to copy repository" + end + + -- rename into place + ok, err = luxio.rename(temp_path, newpath) + if ok ~= 0 then + log.error("Failed to rename repository", temp_path, "to", + newpath, luxio.strerror(err)) + util.rm_rf(temp_path) + return false, "Failed to copy repository" + end + + return true +end + function repo_method:update_modified_date(shas) -- Update the info/web/last-modified local dirpath = self:fs_path() .. "/info/web" @@ -750,8 +811,8 @@ local function foreach_repository(conf, callback, filterfn) if e == 0 then if i.d_name:find("%.git$") then -- Might be a repo, save for later - all_repos[#all_repos+1] = (prefix .. "/" .. - i.d_name):gsub("^/", "") + all_repos[#all_repos+1] = (util.path_join(prefix, i.d_name) + ):gsub("^/", "") else if i.d_name:find("^[^%.]") then recurse[#recurse+1] = i.d_name @@ -762,8 +823,8 @@ local function foreach_repository(conf, callback, filterfn) dirp = nil -- Allow GC of DIR handle -- Now try and recurse if possible, for i = 1, #recurse do - local ok, msg = scan_dir(dirname .. "/" .. recurse[i], - prefix .. "/" .. recurse[i]) + local ok, msg = scan_dir(util.path_join(dirname, recurse[i]), + util.path_join(prefix, recurse[i])) if not ok then return ok, msg end diff --git a/lib/gitano/util.lua b/lib/gitano/util.lua index c8834d3..51c5bc2 100644 --- a/lib/gitano/util.lua +++ b/lib/gitano/util.lua @@ -8,6 +8,7 @@ local luxio = require 'luxio' local sio = require 'luxio.simple' +local log = require 'gitano.log' local tconcat = table.concat @@ -107,6 +108,10 @@ local function path_components(path) return ret end +local function path_join(...) + return tconcat({...}, "/") +end + local function dirname(path) local t = path_components(path) t[#t] = nil @@ -182,6 +187,136 @@ local function rm_rf(path) return (ret == 0), luxio.strerror(err) end +local function _write_all(file, data) + local towrite = #data + local written = 0 + while written < towrite do + local write_count, emsg = file:write(data, written) + if not write_count then + return false, written, emsg + end + written = written + write_count + end + return true, written +end + +local function copy_file(from, to, buffer_size) + -- Default buffer size is 4M, but can be changed + buffer_size = buffer_size or 4 * 1024 * 1024 + local fromfile, emsg = sio.open(from, "r") + if not fromfile then + return false, emsg + end + local tofile, emsg = sio.open(to, "wce") + if not tofile then + return false, emsg + end + local write_count + repeat + local ok + local bytes, emsg = fromfile:read(buffer_size) + if not bytes then + fromfile:close() + tofile:close() + return false, emsg + end + ok, write_count, emsg = _write_all(tofile, bytes) + if not ok then + fromfile:close() + tofile:close() + return false, emsg + end + until write_count == 0 + return true +end + +-- Adapter function, so hardlink follows the same return convention +-- as the copy_file function +local function hardlink_file(from, to) + local ret, err = luxio.link(from, to) + return ret == 0, luxio.strerror(err) +end + +-- TODO: optionally re-base absolute paths when target is moved +-- outside its base directory +local function copy_symlink(from, to) + local link_target, ret, err + ret, link_target = luxio.readlink(from) + if ret == -1 then + return false, luxio.strerror(link_target) + end + ret, err = luxio.symlink(link_target, to) + if ret ~= 0 then + return false, luxio.strerror(err) + end + return true +end + +local function copy_pathname_filter(exclude_set) + return function(parent_path, filename, fileinfo) + return exclude_set[filename] + end +end +local _exclude_builtin = { ["."] = true, [".."] = true } +local copy_dir_filter_base = copy_pathname_filter(_exclude_builtin) + +local copy_dir_copy_callbacks +-- filter_cb is a function, which takes (parent_path, filename, fileinfo) +-- and returns true if the component should not be copied +-- parent_path is required, since filter_cb is passed on to subdirectories +-- copy_cbs is an optional table of callbacks +local function copy_dir(from, to, copy_cbs, filter_cb) + filter_cb = filter_cb or copy_dir_filter_base + copy_cbs = copy_cbs or copy_dir_copy_callbacks + local ret, err, dirp + ret, err = mkdir_p(to) + if not ret then + return ret, err + end + dirp, err, ret = sio.opendir(from) + if not dirp then + return ret, err + end + for filename, fileinfo in dirp:iterate() do + local copycb = copy_cbs[fileinfo.d_type] + local filefrom = path_join(from, filename) + local fileto = path_join(to, filename) + if filter_cb(from, filename, fileinfo) then + log.ddebug("Skipping file", filename) + elseif fileinfo.d_type == luxio.DT_REG then + log.ddebug("Copying file", filefrom, "to", fileto) + ret, err = copycb(filefrom, fileto) + if not ret then + log.critical("Copy file", filefrom, "to", fileto, "failed:", err) + return false, err + end + elseif fileinfo.d_type == luxio.DT_LNK then + log.ddebug("Copying symlink", filefrom, "to", fileto) + ret, err = copycb(filefrom, fileto) + if not ret then + log.critical("Copy symlink", filefrom, "to", fileto, "failed:", err) + return false, err + end + elseif fileinfo.d_type == luxio.DT_DIR then + log.ddebug("Copying dir", filefrom, "to", fileto) + ret, err = copycb(filefrom, fileto, copy_cbs, filter_cb) + if not ret then + log.critical("Copy dir", filefrom, "to", fileto, "failed:", err) + return ret, err + end + else + return false, ("Unsupported file type %d"):format(fileinfo.d_type) + end + end + return true +end + +copy_dir_copy_callbacks = { + [luxio.DT_DIR] = copy_dir, + [luxio.DT_REG] = copy_file, + [luxio.DT_LNK] = copy_symlink, +} + local function html_escape(s) return (s:gsub("&", "&"): gsub("<", "<"): @@ -321,11 +456,19 @@ return { patesc = patesc, path_components = path_components, + path_join = path_join, dirname = dirname, basename = basename, + copy_symlink = copy_symlink, + hardlink_file = hardlink_file, + copy_file = copy_file, mkdir_p = mkdir_p, rm_rf = rm_rf, + copy_pathname_filter = copy_pathname_filter, + copy_dir_filter_base = copy_dir_filter_base, + copy_dir_copy_callbacks = copy_dir_copy_callbacks, + copy_dir = copy_dir, html_escape = html_escape, |