-- gall.tree -- -- Git Abstraction Layer for Lua -- Tree object interface -- -- Copyright 2012 Daniel Silverstone -- -- 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, err = repo:rawgather("diff-tree", "-r", "-M", "-C", ((objs[self] or {}).sha) or "???", ((objs[other] or {}).sha) or "???") if ok ~= 0 then return nil, (streediff or "") .. "\n" .. (err or "") 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 if ll.git2 then function treeindex(tree, field) if tree_method[field] then return tree_method[field] end if not parsed[tree] then local repo = repos[tree] local oid = ll.git2.OID.hex(objs[tree].sha) local treeobj = ll.git2.Tree.lookup(repo.git2.repo, oid) for i = 0, treeobj:entrycount() - 1 do local entry = treeobj:entry_byindex(i) local perm = string.format('0x%08X', entry:filemode()) local sha = tostring(entry:id()) local name = entry:name() local obj = repos[tree]:get(sha) local type = obj.type local t = { permissions = perm, name = name, type = type, obj = obj, } rawset(tree, name, t) end parsed[tree] = true end return rawget(tree, field) end end local function treetostring(tree) return "" 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 and obj 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" }