summaryrefslogtreecommitdiff
path: root/lib/gall/ll.lua
blob: 98b6858c835e0d590f9d427f1152f4b4942cc1e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
-- 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