diff options
author | Daniel Silverstone <dsilvers@digital-scurf.org> | 2012-09-08 14:06:28 +0100 |
---|---|---|
committer | Daniel Silverstone <dsilvers@digital-scurf.org> | 2012-09-08 14:06:28 +0100 |
commit | d1c919e95956c4c883aa052dbdbacbe1b6129387 (patch) | |
tree | 86c6cb34c8baa91ac20f0dd4ee8e257c96b6923e | |
parent | 2678f0d052da02483e828da9db43f9a2d6734de6 (diff) | |
download | gall-d1c919e95956c4c883aa052dbdbacbe1b6129387.tar.gz |
GALL: Initial import of Gitano library gitano.git as gall
-rw-r--r-- | lib/gall.lua | 18 | ||||
-rw-r--r-- | lib/gall/commit.lua | 165 | ||||
-rw-r--r-- | lib/gall/ll.lua | 98 | ||||
-rw-r--r-- | lib/gall/object.lua | 78 | ||||
-rw-r--r-- | lib/gall/repository.lua | 279 | ||||
-rw-r--r-- | lib/gall/tag.lua | 97 | ||||
-rw-r--r-- | lib/gall/tree.lua | 209 | ||||
-rw-r--r-- | lib/gall/util.lua | 29 | ||||
-rw-r--r-- | test/test-gall.lua | 17 |
9 files changed, 990 insertions, 0 deletions
diff --git a/lib/gall.lua b/lib/gall.lua index 4a2dbb8..a2f613a 100644 --- a/lib/gall.lua +++ b/lib/gall.lua @@ -10,8 +10,26 @@ local ABI_VERSION = 0 local VERSION = "Gall v1." .. tostring(API_VERSION) +local util = require "gall.util" +local ll = require "gall.ll" + +local repository = require "gall.repository" +local tag = require "gall.tag" +local commit = require "gall.commit" +local tree = require "gall.tree" +local object = require "gall.object" + return { _API_VERSION = API_VERSION, _ABI_VERSION = ABI_VERSION, VERSION = VERSION, + + repository = repository, + tag = tag, + commit = commit, + tree = tree, + object = object, + + ll = ll, + util = util, } diff --git a/lib/gall/commit.lua b/lib/gall/commit.lua new file mode 100644 index 0000000..92afff8 --- /dev/null +++ b/lib/gall/commit.lua @@ -0,0 +1,165 @@ +-- gall.commit +-- +-- Git Abstraction Layer for Lua -- Commit object interface +-- +-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org> +-- +-- + +local ll = require "gall.ll" + +local objs = setmetatable({}, {__mode="k"}) +local repos = setmetatable({}, {__mode="k"}) +local parsed = setmetatable({}, {__mode="k"}) + +local _new + +local function parse_person(pers) + local real, email, when, tz = pers:match("^(.-) <([^>]+)> ([0-9]+) ([+-][0-9]+)$") + return { + realname = real, + email = email, + unixtime = when, + timezone = tz + } +end + +local function parse_parents(repo, ps) + local ret = {} + for _, psha in ipairs(ps) do + ret[_] = repo:get(psha) + end + return ret +end + +local function commitindex(commit, field) + if not parsed[commit] then + local raw = objs[commit].raw + local headers, body, signature = {}, "" + local state = "headers" + + for l in raw:gmatch("([^\n]*)\n") do + if state == "headers" then + local first, second = l:match("^([^ ]+) (.+)$") + if first then + if headers[first] then + headers[first][#headers[first]+1] = second + else + headers[first] = { second } + end + else + state = "message" + end + elseif state == "message" then + if l == PGP_SIG_START then + signature = l .. "\n" + state = "signature" + else + body = body .. l .. "\n" + end + else + signature = signature .. l .. "\n" + end + end + + -- there's always one tree + rawset(commit, "tree", repos[commit]:get(headers.tree[1])) + -- Always one author + rawset(commit, "author", parse_person(headers.author[1])) + -- Always one committer + rawset(commit, "committer", parse_person(headers.committer[1])) + -- Zero or more parents + headers.parent = headers.parent or {} + rawset(commit, "parents", parse_parents(repos[commit], headers.parent)) + -- A message + rawset(commit, "message", body) + -- And an optional signature + rawset(commit, "signature", signature) + -- Promote the SHA + rawset(commit, "sha", objs[commit].sha) + + -- Signal we are parsed + parsed[commit] = true + end + + return rawget(commit, field) +end + +local function committostring(commit) + return "<GitCommit(" .. tostring(objs[commit].sha) .. ") in " .. tostring(repos[commit]) .. ">" +end + +local commitmeta = { + __index = commitindex, + __tostring = committostring +} + +function _new(repo, obj) + local ret = setmetatable({}, commitmeta) + objs[ret] = obj + repos[ret] = repo + return ret +end + +function _create(repo, data) + if not data.tree then + return nil, "No tree?" + end + if not data.author then + return nil, "No author?" + end + if not data.committer then + return nil, "No committer?" + end + if not data.author.realname then + return nil, "No author name?" + end + if not data.author.email then + return nil, "No author email?" + end + if not data.committer.realname then + return nil, "No committer name?" + end + if not data.committer.email then + return nil, "No committer email?" + end + if not data.message then + return nil, "No message?" + end + if not data.parents then + data.parents = {} + end + + -- Construct the commandline and environment + local env = { + GIT_AUTHOR_NAME = data.author.realname, + GIT_AUTHOR_EMAIL = data.author.email, + GIT_COMMITTER_NAME = data.committer.realname, + GIT_COMMITTER_EMAIL = data.committer.email, + } + local cmd = { + "commit-tree", data.tree.sha + } + for i, v in ipairs(data.parents) do + cmd[#cmd+1] = "-p" + cmd[#cmd+1] = v.sha + end + + if not data.message:match("\n$") then + data.message = data.message .. "\n" + end + + local why, sha = repo:_run_with_input_and_env(env, data.message, + ll.chomp, unpack(cmd)) + + if why ~= 0 then + return nil, "commit-tree returned " .. tostring(why) + end + + return repo:get(sha) +end + +return { + create = _create, + new = _new +} diff --git a/lib/gall/ll.lua b/lib/gall/ll.lua new file mode 100644 index 0000000..7f20683 --- /dev/null +++ b/lib/gall/ll.lua @@ -0,0 +1,98 @@ +-- gall.ll +-- +-- Git Abstraction Layer for Lua -- Low level interface +-- +-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org> +-- +-- + +local sp = require "luxio.subprocess" +local util = require "gall.util" + +local assert = assert + +local git_exe = "git" + +local function _rungit(t) + assert(t.repo, "No repository?") + + local proc_args = { + env = {}, + git_exe, unpack(t) + } + + for k, v in pairs(t.env or {}) do + proc_args.env[k] = v + end + + proc_args.env.GIT_DIR = t.repo + + if t.stdin then + proc_args.stdin = t.stdin + end + + if t.stdout then + proc_args.stdout = sp.PIPE + end + + if t.stderr then + proc_args.stderr = sp.PIPE + end + + local proc = sp.spawn_simple(proc_args) + local stdout, stderr + + if t.stdout then + stdout = proc.stdout:read("*a") + proc.stdout:close() + if type(t.stdout) == "function" then + stdout = t.stdout(stdout) + end + end + + if t.stderr then + stderr = proc.stderr:read("*a") + proc.stderr:close() + if type(t.stderr) == "function" then + stderr = t.stderr(stderr) + end + end + + local how, why = proc:wait() + + assert(how == "exit", "Not cleanly exited") + + return why, stdout, stderr +end + +local function _setgit(e) + git_exe = e +end + +local function _chomp(s) + local rest = s:match("^(.*)\n$") + return rest or s +end + +local mod_ret = { + rungit = _rungit, + setgit = _setgit, + chomp = _chomp +} + +local simple_cmds = { + "cat-file", "symbolic-ref", "show-ref", + "hash-object", "ls-tree", "init", "merge-base", "rev-list", "config" +} + +for _, s in pairs(simple_cmds) do + local ss = s:gsub("%-", "_") + mod_ret[ss] = function(_t) + local t = util.deep_copy(_t) + table.insert(t, 1, s) + t.stdout = _chomp + return _rungit(t) + end +end + +return mod_ret diff --git a/lib/gall/object.lua b/lib/gall/object.lua new file mode 100644 index 0000000..3b904c5 --- /dev/null +++ b/lib/gall/object.lua @@ -0,0 +1,78 @@ +-- gall.object +-- +-- Git Abstraction Layer for Lua -- Generic object interface +-- +-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org> +-- +-- + +local ll = require "gall.ll" +local commit = require "gall.commit" +local tree = require "gall.tree" +local tag = require "gall.tag" + +local repos = setmetatable({}, {__mode="k"}) + +local function _objectindex(obj, field) + local ok, ret + if field == "type" then + ok, ret = repos[obj]:gather("cat-file", "-t", obj.sha) + elseif field == "size" then + ok, ret = repos[obj]:gather("cat-file", "-s", obj.sha) + elseif field == "raw" then + ok, ret = repos[obj]:rawgather("cat-file", (obj.type == "tag" and "tag" or "-p"), obj.sha) + elseif field == "content" then + if obj.type == "blob" then + ok, ret = 0, obj.raw + elseif obj.type == "commit" then + ok, ret = 0, commit.new(repos[obj], obj) + elseif obj.type == "tree" then + ok, ret = 0, tree.new(repos[obj], obj) + elseif obj.type == "tag" then + ok, ret = 0, tag.new(repos[obj], obj) + else + error("Unknown type <" .. obj.type .. "> for content parse") + end + else + error("Unknown field <" .. tostring(field) .. ">") + end + + assert(ok == 0, "Unable to retrieve " .. field) + + if field == "size" then ret = tonumber(ret) end + + rawset(obj, field, ret) + + return ret +end + +function _objecttostring(obj) + return "<GitObject(" .. tostring(obj.sha)..") in " .. tostring(repos[obj]) .. ">" +end + +local objectmeta = { + __index = _objectindex, + __tostring = _objecttostring +} + +local function _new(repo, sha) + local ret = setmetatable({sha=sha}, objectmeta) + repos[ret] = repo + return ret +end + +local function _create(repo, type, content) + local why, sha = + repo:_run_with_input(content, ll.chomp, + "hash-object", "-t", type, "-w", "--stdin") + if why ~= 0 then + return nil, "hash-object returned " .. tostring(why) + end + + return _new(repo, sha) +end + +return { + new = _new, + create = _create, +} diff --git a/lib/gall/repository.lua b/lib/gall/repository.lua new file mode 100644 index 0000000..af1ded4 --- /dev/null +++ b/lib/gall/repository.lua @@ -0,0 +1,279 @@ +-- gall.repository +-- +-- Git Abstraction Layer for Lua -- Repository interface +-- +-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org> +-- +-- + +local ll = require "gall.ll" +local object = require "gall.object" + +local chomp = ll.chomp + +local repomethod = {} + +local pattern = { + fullsha = string.rep("[0-9a-f]", 40), + shortsha = string.rep("[0-9a-f]", 7), + ref = "refs/.+" +} + +for k, v in pairs(pattern) do + pattern[k] = ("^%s$"):format(v) +end + +local function _repotostring(repo) + return "<GitRepository(" .. repo.path .. ")>" +end + +function repomethod:_run_with_input_and_env(env, input, want_output, ...) + local t = {...} + t.repo = self.path + if want_output then + t.stdout = want_output + end + if input then + t.stdin = input + end + -- We never want to see the stderr dumped to the client, so we eat it. + t.stderr = true + if env then + t.env = env + end + return ll.rungit(t) +end + +function repomethod:_run_with_input(input, want_output, ...) + return self:_run_with_input_and_env(nil, input, want_output, ...) +end + +function repomethod:_run(want_output, ...) + return self:_run_with_input(nil, want_output, ...) +end + +function repomethod:gather(...) + return self:_run(chomp, ...) +end + +function repomethod:rawgather(...) + return self:_run(true, ...) +end + +function repomethod:force_empty_tree() + self:_run(true, "hash-object", "-t", "tree", "-w", "/dev/null") +end + +function repomethod:hash_object(type, content, inject) + local args = { + "hash-object", "-t", type, "--stdin", + } + if inject then + args[#args+1] = "-w" + end + local ok, sha = self:_run_with_input(content, chomp, unpack(args)) + return (ok == 0) and sha or nil +end + +function repomethod:get_ref(ref) + local ok, sha = self:_run(chomp, "show-ref", "--verify", "-s", ref) + return (ok == 0) and sha or nil +end + +function repomethod:update_ref(ref, new_ref, reason, old_ref) + if new_ref and not old_ref then + old_ref = string.rep("0", 40) + end + if not reason then + reason = "Gall internal operations" + end + + local cmd = { "update-ref", "-m", reason } + + if not new_ref then + cmd[#cmd+1] = "-d" + end + + cmd[#cmd+1] = ref + + if new_ref then + cmd[#cmd+1] = new_ref + end + if old_ref then + cmd[#cmd+1] = old_ref + end + + local why = self:_run(false, unpack(cmd)) + if why ~= 0 then + return nil, "update-ref returned " .. tostring(why) + end + return true +end + +function repomethod:update_server_info() + local why = self:_run(false, "update-server-info") + if why ~= 0 then + return nil, "update-server-info returned " .. tostring(why) + end + return true +end + +function repomethod:all_refs() + local ok, refs = self:_run(chomp, "show-ref") + if ok ~= 0 then return nil, refs end + local reft = {} + for sha, ref in refs:gmatch("([0-9a-f]+) (refs/[^\n]+)") do + reft[ref] = sha + end + return reft +end + +function repomethod:normalise(sha) + -- Purpose is to take a 'shaish' object and normalise it + if sha:match(pattern.fullsha) then + return sha + elseif sha:match(pattern.ref) then + local ref, err = self:get_ref(sha) + return ref, err + else + local ok, out = self:_run_with_input(sha, chomp, "cat-file", "--batch-check") + if not ok then + error(out) + end + sha = out:match("^("..string.rep("[0-9a-f]", 40)..")") + return sha + end + return nil, "Unable to normalise " .. tostring(sha) +end + +function repomethod:get(_sha) + local sha, err = self:normalise(_sha) + if not sha then + return nil, err + end + local ret = self.objmemo[sha] + if not ret then + ret = object.new(self, sha) + self.objmemo[sha] = ret + end + return ret +end + +function repomethod:merge_base(sha_1, sha_2, get_all) + local args = { sha_1, sha_2 } + if get_all then + args = { "-a", sha_1, sha_2 } + end + args.repo = self.path + local ok, out = ll.merge_base(args) + if not ok then + return nil, out + end + local ret = {} + for sha in out:gmatch("([a-f0-9]+)") do + ret[#ret+1] = sha + end + if #ret == 0 then + return true + end + return unpack(ret) +end + +function repomethod:rev_list(oldhead, newhead, firstonly) + local args = { newhead, "^" .. oldhead } + if firstonly then + table.insert(args, 1, "--first-parent") + end + args.repo = self.path + local ok, out = ll.rev_list(args) + if not ok then + return nil, out + end + local ret = {} + for sha in out:gmatch("([a-f0-9]+)") do + ret[#ret+1] = sha + end + if #ret == 0 then + return true + end + return ret +end + +function repomethod:symbolic_ref(name, toref) + if not toref then + return ll.symbolic_ref { "-q", name, + stderr=true, repo=self.path } + else + return ll.symbolic_ref { "-q", name, toref, + stderr=true, repo=self.path } + end + return false, "argh" +end + +function repomethod:config(confname, value) + -- Trivial interface to key/value in config + if not value then + return self:gather("config", confname) + else + self:gather("config", "--replace-all", confname, value) + end +end + +local repomt = { + __index = repomethod, + __tostring = _repotostring +} + +local function _new(path) + -- return a new git repository object + -- with the git_dir set for the provided path + -- and, if we had to add /.git then the GIT_WORK_DIR set + -- appropriately too + + local retrepo = {objmemo=setmetatable({}, {__mode="v"})} + + local repopath = path + local workpath = nil + + local ok, symref = ll.symbolic_ref { "-q", "HEAD", stderr=true, repo=repopath } + if ok ~= 0 then + repopath = path .. "/.git" + workpath = path + ok, symref = ll.symbolic_ref { "-q", "HEAD", stderr=true, repo=repopath } + end + + if ok ~= 0 then + return nil, "Unable to find Git repository at " .. path + end + + retrepo.path = repopath + retrepo.work = workpath + retrepo.HEAD = symref + + return setmetatable(retrepo, repomt) +end + +local function _create(path, full) + -- Cause a bare repository to be created (or a non-bare if full is true) + local args = { + stderr = true, + repo = path, + "-q" + } + if not full then + args[#args+1] = "--bare" + end + ok, msg = ll.init(args) + + if ok ~= 0 then + return nil, "Unable to create Git repository at " .. path + end + + -- Otherwise, return the shiny new repo + return _new(path) +end + +return { + create = _create, + new = _new +} diff --git a/lib/gall/tag.lua b/lib/gall/tag.lua new file mode 100644 index 0000000..707af31 --- /dev/null +++ b/lib/gall/tag.lua @@ -0,0 +1,97 @@ +-- gall.tag +-- +-- Git Abstraction Layer for Lua -- Tag object interface +-- +-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org> +-- +-- + +local ll = require "gall.ll" + +local objs = setmetatable({}, {__mode="k"}) +local repos = setmetatable({}, {__mode="k"}) +local parsed = setmetatable({}, {__mode="k"}) + +local _new + +local function parse_person(pers) + local real, email, when, tz = pers:match("^(.-) <([^>]+)> ([0-9]+) ([+-][0-9]+)$") + return { + realname = real, + email = email, + unixtime = when, + timezone = tz + } +end + +local function tagindex(tag, field) + if not parsed[tag] then + local raw = objs[tag].raw + local headers, body, signature = {}, "" + local state = "headers" + + for l in raw:gmatch("([^\n]*)\n") do + if state == "headers" then + local first, second = l:match("^([^ ]+) (.+)$") + if first then + if headers[first] then + headers[first][#headers[first]+1] = second + else + headers[first] = { second } + end + else + state = "message" + end + elseif state == "message" then + if l == PGP_SIG_START then + signature = l .. "\n" + state = "signature" + else + body = body .. l .. "\n" + end + else + signature = signature .. l .. "\n" + end + end + + -- there's always one object + rawset(tag, "object", repos[tag]:get(headers.object[1])) + -- Always one type + rawset(tag, "type", headers.type[1]) + -- Always one tag name + rawset(tag, "tag", headers.tag[1]) + -- Always one tagger + rawset(tag, "tagger", parse_person(headers.tag[1])) + -- A message + rawset(tag, "message", body) + -- And an optional signature + rawset(tag, "signature", signature) + -- Promote the SHA + rawset(tag, "sha", objs[tag].sha) + + -- Signal we are parsed + parsed[tag] = true + end + + return rawget(tag, field) +end + +local function tagtostring(tag) + return "<GitTag(" .. tostring(objs[tag].sha) .. ") in " .. tostring(repos[tag]) .. ">" +end + +local tagmeta = { + __index = tagindex, + __tostring = tagtostring +} + +function _new(repo, obj) + local ret = setmetatable({}, tagmeta) + objs[ret] = obj + repos[ret] = repo + return ret +end + +return { + new = _new +} diff --git a/lib/gall/tree.lua b/lib/gall/tree.lua new file mode 100644 index 0000000..ed7ba4b --- /dev/null +++ b/lib/gall/tree.lua @@ -0,0 +1,209 @@ +-- gall.tree +-- +-- Git Abstraction Layer for Lua -- Tree object interface +-- +-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org> +-- +-- + +local ll = require "gall.ll" + +local PARSED = {} + +local objs = setmetatable({}, {__mode="k"}) +local repos = setmetatable({}, {__mode="k"}) +local parsed = setmetatable({}, {__mode="k"}) + +local tree_method = {} + +local shapattern = ("[a-f0-9]"):rep(40) +local shacapture = "(" .. shapattern .. ")" +local modepattern = ("[0-7]"):rep(6) +local modecapture = "(" .. modepattern .. ")" +-- startmode, endmode, startsha, endsha, action, optional score, filename(s) +local treediffline = table.concat { + ":", modecapture, " ", modecapture, " ", shacapture, " ", shacapture, " ", + "(.)", "([0-9]*)", "\t", "([^\n]+)", "\n" +} + +local mode_to_kind_map = { + ["000000"] = "missing", + ["160000"] = "submodule", + ["100644"] = "blob", + ["100755"] = "xblob", + ["120000"] = "symlink", +} + +local function unescape_filename(fn) + local zfn = fn:gsub("\\\\", "\0") + zfn = zfn:gsub("\\n", "\n") + zfn = zfn:gsub("\\t", "\t") + return (zfn:gsub("%z", "\\")) +end + +function tree_method:diff_to(other) + -- Generate a diff from self to other + local repo = repos[self] + local ok, streediff = repo:rawgather("diff-tree", "-r", "-M", "-C", + objs[self].sha, objs[other].sha) + if ok ~= 0 then + return nil, streediff + end + local treediff = {} + for startmode, endmode, startsha, endsha, action, score, filenames in + streediff:gmatch(treediffline) do + local diffentry = { + startmode = startmode, + endmode = endmode, + startkind = mode_to_kind_map[startmode] or "UNKNOWN", + endkind = mode_to_kind_map[endmode] or "UNKNOWN", + startsha = startsha, + endsha = endsha, + action = action, + score = (score ~= "") and score or nil, + } + if action == "C" or action == "R" then + local src, dst = filenames:match("([^\t]+)\t(.+)") + diffentry.src_name = unescape_filename(src) + diffentry.filename = unescape_filename(dst) + diffentry.dst_name = diffentry.filename + else + diffentry.filename = unescape_filename(filenames) + end + treediff[#treediff+1] = diffentry + treediff[diffentry.filename] = diffentry + end + return treediff +end + +local function treeindex(tree, field) + if tree_method[field] then + return tree_method[field] + end + + if not parsed[tree] then + local raw = objs[tree].raw + for l in raw:gmatch("([^\n]+)\n") do + local perm, type, sha, name = l:match("^([0-9]+) ([^ ]+) ([0-9a-f]+)\t(.+)$") + local t = { + permissions = perm, + name = name, + type = type, + obj = repos[tree]:get(sha) + } + rawset(tree, name, t) + end + parsed[tree] = true + end + + return rawget(tree, field) +end + +local function treetostring(tree) + return "<GitTree(" .. tostring(objs[tree].sha) .. ") in " .. tostring(repos[tree]) .. ">" +end + +local treemeta = { + __index = treeindex, + __tostring = treetostring, +} + +local function _new(repo, obj) + local ret = setmetatable({}, treemeta) + objs[ret] = obj + repos[ret] = repo + return ret +end + +local function _realise(t) + if not parsed[t] then + local ignored = t[parsed] + end + return t +end + +local function _flatten(t) + local ret = {} + local function _inner_flatten(pfx, tt) + _realise(tt) + for k, v in pairs(tt) do + local leaf = pfx .. k + if v.type == "tree" then + _inner_flatten(leaf .. "/", v.obj.content) + else + ret[leaf] = v + end + end + end + _inner_flatten("", t) + return ret +end + +local function _create(repo, flat_tree) + local function __store(tree, element, content) + local prefix, suffix = element:match("^([^/]+)/(.+)$") + if not prefix then + tree[element] = content + else + tree[prefix] = tree[prefix] or { [""] = true } + __store(tree[prefix], suffix, content) + end + end + local t = {[""] = true} + for k, v in pairs(flat_tree) do + __store(t, k, v) + end + + -- t is now a 'tree' so we need to turn any tables into trees recursively + + local function __treeify(t) + -- Step one, ensure any trees inside t are treeified + for k, v in pairs(t) do + if k ~= "" and rawget(v, "") then + local _v, reason = __treeify(v) + if not _v then + return nil, reason + end + t[k] = _v + end + end + -- Next, construct a mktree input + local tree_ent = "" + local mode = { + tree = "040000", + blob = "100644", + } + for k, v in pairs(t) do + if k ~= "" then + local ok, obj = pcall(function() return v.obj end) + if ok then + v = obj + end + if not mode[v.type] then + return nil, "Unknown type " .. v.type + end + tree_ent = tree_ent .. + ("%s %s %s\t%s\0"):format(mode[v.type], v.type, v.sha, k) + end + end + + local why, sha = repo:_run_with_input(tree_ent, ll.chomp, + "mktree", "-z") + if why ~= 0 then + return nil, "mktree returned " .. tostring(why) + end + return repo:get(sha) + end + + return __treeify(t) +end + +return { + realise = _realise, + flatten = _flatten, + new = _new, + create = _create, + + -- Magic SHA1 for empty tree + empty_sha = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" +} diff --git a/lib/gall/util.lua b/lib/gall/util.lua new file mode 100644 index 0000000..d6c5297 --- /dev/null +++ b/lib/gall/util.lua @@ -0,0 +1,29 @@ +-- gall.ll +-- +-- Git Abstraction Layer for Lua -- Utility functions +-- +-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org> +-- +-- + +local function deep_copy(t, memo) + if not memo then memo = {} end + if memo[t] then return memo[t] end + local ret = {} + local kk, vv + for k, v in pairs(t) do + kk, vv = k, v + if type(k) == "table" then + kk = _deep_copy(k) + end + if type(v) == "table" then + vv = _deep_copy(v) + end + ret[kk] = vv + end + return ret +end + +return { + deep_copy = deep_copy, +} diff --git a/test/test-gall.lua b/test/test-gall.lua index 2cd691a..3f0558d 100644 --- a/test/test-gall.lua +++ b/test/test-gall.lua @@ -11,8 +11,18 @@ local luacov = require 'luacov' +local ents = {} +ents.util = require 'gall.util' +ents.ll = require 'gall.ll' +ents.object = require 'gall.object' +ents.tree = require 'gall.tree' +ents.commit = require 'gall.commit' +ents.tag = require 'gall.tag' +ents.repository = require 'gall.repository' + local gall = require 'gall' + local testnames = {} local real_assert = assert @@ -34,6 +44,13 @@ function suite.test_gall_not_empty() assert(next(gall), "Gall module is empty") end +for _, ent in ipairs { + "repository", "util", "ll", "tag", "commit", "tree", "object" } do + suite["test_gall_"..ent.."_is_"..ent] = function() + assert(gall[ent] == ents[ent], "gall."..ent.." module is not gall."..ent.." entry") + end +end + local count_ok = 0 for _, testname in ipairs(testnames) do -- print("Run: " .. testname) |