-- gall.ll
--
-- Git Abstraction Layer for Lua -- Low level interface
--
-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org>
--
--

---
-- Low level interface to Git
--
-- In addition to the documented functions below, the following Git subcommands
-- are directly exposed as functions which are essentially like...
--
--    function gall.ll.FUNC(...)
--        return gall.ll.rungit("FUNC", ...)
--    end
--
-- ...but with correct handling of the tabular input to @{rungit}
--
-- The current full-list of those functions is:
--
-- * cat_file exposes `git cat-file`
-- * symbolic_ref exposes `git symbolic-ref`
-- * show_ref exposes `git show-ref`
-- * hash_object exposes `git hash-object`
-- * ls_tree exposes `git ls-tree`
-- * init exposes `git init`
-- * merge_base exposes `git merge-base`
-- * rev_list exposes `git rev-list`
-- * config exposes `git config`
--
-- @module gall.ll


local sp = require "luxio.subprocess"
local util = require "gall.util"


local git2
if os.getenv "GALL_DISABLE_GIT2" then
   git2 = nil
else
   ok, git2 = pcall(require, "gall.ll.git2")
   if not ok then
      git2 = nil
   end
end

local assert = assert

local git_exe = "git"

---
-- Run the given git process.
--
-- The run parameters must contain:
--
-- * The `repo` (the value for `GIT_DIR`)
-- * At least 1 list entry for the command line
-- * Optionally an `env` table.
-- * Optionally a string `stdin` to pass into the process
-- * Optionally a `stdout` boolean which says whether to capture stdout
-- * Optionally a `stderr` boolean which says whether to capture stderr
--
-- Additionally, if `stdout` or `stderr` are functions instead of booleans then
-- they will be called to filter the stream after reading.
--
-- @function rungit
-- @tparam table t Run parameters
-- @treturn number Exit code
-- @treturn string Contents of stdout
-- @treturn string Contents of stderr
-- @raise Function will assert if given no repository or if the subprocess is killed uncleanly

local function _rungit(t)
   assert(t.repo, "No repository?")

   local proc_args = {
      env = {},
      git_exe, unpack(t)
   }

   for k, v in pairs(t.env or {}) do
      proc_args.env[k] = v
   end

   proc_args.env.GIT_DIR = t.repo

   if t.stdin then
      proc_args.stdin = t.stdin
   end

   if t.stdout then
      proc_args.stdout = sp.PIPE
   end

   if t.stderr then
      proc_args.stderr = sp.PIPE
   end

   local proc = sp.spawn_simple(proc_args)
   local stdout, stderr

   if t.stdout then
      stdout = proc.stdout:read("*a")
      proc.stdout:close()
      if type(t.stdout) == "function" then
	 stdout = t.stdout(stdout)
      end
   end

   if t.stderr then
      stderr = proc.stderr:read("*a")
      proc.stderr:close()
      if type(t.stderr) == "function" then
	 stderr = t.stderr(stderr)
      end
   end

   local how, why = proc:wait()

   assert(how == "exit", "Not cleanly exited")

   return why, stdout, stderr
end

---
-- Get / Set the git executable
--
-- If `e` is provided, then it is set as the path, otherwise the current
-- path is returned as-is.  By default, the Git executable is simply `git`.
--
-- @function get_set_git
-- @tparam ?string e The path to the Git executable
-- @treturn string The path to the Git executable

local function get_set_git(e)
   if e then
      git_exe = e
   end
   return git_exe
end

---
-- Remove a trailing newline if present.
--
-- @function chomp
-- @tparam string s The string to chomp
-- @treturn string `s` with a single trailing newline (if present) removed.

local function _chomp(s)
   local rest = s:match("^(.*)\n$")
   return rest or s
end

local mod_ret = {
   rungit = _rungit,
   get_set_git = get_set_git,
   chomp = _chomp,
   git2 = git2,
}

local simple_cmds = {
   "cat-file", "symbolic-ref", "show-ref",
   "hash-object", "ls-tree", "init", "merge-base", "rev-list", "config"
}

for _, s in pairs(simple_cmds) do
   local ss = s:gsub("%-", "_")
   local function ss_fn(_t)
      local t = util.deep_copy(_t)
      table.insert(t, 1, s)
      t.stdout = _chomp
      return _rungit(t)
   end
   mod_ret[ss] = ss_fn
end

return mod_ret
