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