-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgit.lua
253 lines (216 loc) · 6.08 KB
/
git.lua
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
local fn = vim.fn
local uv = vim.loop
local tinsert = table.insert
local Task = require('neopm.task')
---@type NeopmState
local state = require('neopm.state')
local Git = {}
local GIT_ENV = {
GIT_TERMINAL_PROMPT = 0,
GIT_CONFIG_NOSYSTEM = 1,
GIT_CONFIG_GLOBAL = '/dev/null',
}
-- TODO: capture stderr and display errors
do
local git_version
--- Get git version, synchronous
---@return number[]
function Git.version()
if git_version then return git_version end
local ok, res = pcall(fn.systemlist, { state.git_command, '--version' })
if not ok or not res or not res[1] then return end
res = res[1]:match('^git version ([%d%.]+)$')
if not res then return end
res = vim.split(res, '.', { plain = true })
if not res then return end
for i, num in ipairs(res) do
res[i] = tonumber(num)
if not res[i] then return end
end
git_version = res
return res
end
end
---@param line string
---@return string line
local function get_last_line(line)
line = line:match('^(.-)[\r\n]*$') or line
line = line:match('[\r\n]([^\r\n]-)$') or line
line = line:match('^%s*(.-)%s*$')
if line and line ~= '' then
return line
end
end
--- Run git command, asynchronous
---@param args string[]
---@param cwd string
---@param on_update? fun(data: string)
---@param capture_stdout? boolean
---@return number status
---@return NeopmTaskExecResults results
function Git.run(args, cwd, on_update, capture_stdout)
local opts = {
cwd = cwd or state.install_dir,
env = GIT_ENV,
}
if on_update then
function opts.on_stderr(data)
local line = get_last_line(data)
if line then on_update(line) end
end
end
if capture_stdout then
opts.capture_stdout = true
end
return Task.current():exec(state.git_command, args, opts)
end
--- git clone, asynchronous
---@param plug NeopmPlug
---@param on_progress fun(data: string)
---@return number status
function Git.clone(plug, on_progress)
-- TODO: should ".git" suffix be stripped for output directory?
local uri = plug.uri:find(':') and plug.uri or
string.format('https://git::@github.com/%s.git', plug.uri)
local args = {
'clone',
uri,
plug.path,
'--origin', 'origin',
'--depth', '1',
'--no-single-branch',
}
if on_progress then
tinsert(args, '--progress')
end
local status = Git.run(args, nil, on_progress)
return status
end
--- git fetch, asynchronous
---@param plug NeopmPlug
---@param on_progress fun(data: string)
---@return number status
function Git.fetch(plug, on_progress)
-- TODO: revert previous patch if necessary
local args = {'fetch'}
if on_progress then
tinsert(args, '--progress')
end
-- if Task.current():stat(plug.path..'/.git/shallow') then
-- tinsert(args, '--depth')
-- tinsert(args, '99999999')
-- end
local status = Git.run(args, plug.path, on_progress)
return status
end
--- git checkout, asynchronous
---@param plug NeopmPlug
---@param branch string
---@return number status
function Git.checkout(plug, branch)
local status = Git.run({'checkout', '-q', branch}, plug.path)
return status
end
--- git merge, asynchronous
---@param plug NeopmPlug
---@param branch string
---@return number status
function Git.merge(plug, branch)
local status = Git.run({'merge', '--ff-only', branch}, plug.path)
return status
end
--- git patch, asynchronous
---@param plug NeopmPlug
---@return string? err
function Git.patch(plug)
-- TODO: revert previous patch if necessary
if not plug.patch then return end
local task = Task.current()
if Git.run({'apply', plug.patch}, plug.path) ~= 0 then
return 'patch: FAIL (non-zero exit code)'
end
if not task:copyfile(plug.patch, plug.path..'/.git/plug.diff') then
return 'patch: FAIL (failed to copy patch file)'
end
end
--- git patch -R, asynchronous
---@param plug NeopmPlug
---@return string? err
function Git.patch_revert(plug)
local task = Task.current()
local patch = plug.path..'/.git/plug.diff'
if not task:stat(patch) then return end
if Git.run({'apply', '-R', patch}, plug.path) ~= 0 then
return 'patch revert: FAIL (non-zero exit code)'
end
if not task:unlink(patch) then
return 'patch revert: FAIL (failed to remove old patch file)'
end
end
--- git log, asynchronous
---@param plug NeopmPlug
function Git.log(plug, rev)
local status, results = Git.run({
'log',
'--graph',
'--color=never',
'--no-show-signature',
'--pretty=format:%x01%h%x01%d%x01%s%x01%cr',
rev,
}, plug.path, nil, true)
if status == 0 then
local stdout = {}
for _, line in ipairs(results.stdout) do
if line:match('%S') then
tinsert(stdout, vim.split(line, '\1', { plain = true }))
end
end
return stdout
end
end
--- Get revision, asynchronous
---@param dir string
---@return string? rev
function Git.revision(dir)
local task = Task.current()
dir = dir..'/.git/'
local lines = task:readfile(dir..'HEAD', 1)
if not lines or not lines[1] then return end
local ref = lines[1]:match('^ref: (.*)$')
if not ref or ref == '' then return end
lines = task:readfile(dir..ref, 1)
if lines and lines[1] then return lines[1] end
lines = task:readfile(dir..'packed-refs')
if not lines then return end
local last = -#ref
for _, line in ipairs(lines) do
if line:sub(last) == ref then
local match = line:match('^([0-9a-f]+)%s')
if match then return match end
end
end
end
--- Get origin branch, asynchronous
---@param dir string
---@return string? branch
function Git.origin_branch(dir)
local task = Task.current()
local lines = task:readfile(dir..'/.git/refs/remotes/origin/HEAD', 1)
if lines and lines[1] then
return lines[1]:match('^ref: refs/remotes/origin/(.+)$')
end
local status, results = task:exec('git', {
'symbolic-ref', '--short', 'HEAD',
}, { capture_stdout = true })
if status == 0 and results.stdout[1] then
return results.stdout[1]:match('%S[^\n]*')
end
end
--- Check if patch exists, synchronous (unused)
---@param plug NeopmPlug
---@return boolean
function Git.is_patched(plug)
local patch = plug.path..'/.git/plug.diff'
return uv.fs_stat(patch) ~= nil
end
return Git