summaryrefslogtreecommitdiff
path: root/lib/gall/tree.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gall/tree.lua')
-rw-r--r--lib/gall/tree.lua209
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"
+}