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