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