summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2013-05-27 17:21:07 +0100
committerDaniel Silverstone <dsilvers@digital-scurf.org>2013-05-27 17:21:07 +0100
commit9f1d54962fbd4dee155ccd099d34b4f064e20237 (patch)
treeecbcd19268d7f7dfae692fdc3653f932b84afaca
parenta3fbdec53f490f83dbaacf5f62c7d4b9a05896e5 (diff)
parent186495107f9978e204230bb9a3f3d0c612a3542b (diff)
downloadgitano-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.lua4
-rw-r--r--lib/gitano/copycommand.lua108
-rw-r--r--lib/gitano/repository.lua69
-rw-r--r--lib/gitano/util.lua143
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("&", "&amp;"):
gsub("<", "&lt;"):
@@ -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,