diff options
Diffstat (limited to 'lib/gall/tree.lua')
-rw-r--r-- | lib/gall/tree.lua | 209 |
1 files changed, 209 insertions, 0 deletions
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" +} |