forked from 3scale/lua-rover
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgit.lua
More file actions
213 lines (182 loc) · 7.07 KB
/
git.lua
File metadata and controls
213 lines (182 loc) · 7.07 KB
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
--- Fetch back-end for retrieving sources from GIT.
local git = {}
local unpack = unpack or table.unpack
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local vers = require("luarocks.core.vers")
local util = require("luarocks.util")
local persist = require('luarocks.persist')
local type_check = require("luarocks.type_check")
local cached_git_version
--- Get git version.
-- @param git_cmd string: name of git command.
-- @return table: git version as returned by luarocks.core.vers.parse_version.
local function git_version(git_cmd)
if not cached_git_version then
local version_line = io.popen(fs.Q(git_cmd)..' --version'):read()
local version_string = version_line:match('%d-%.%d+%.?%d*')
cached_git_version = vers.parse_version(version_string)
end
return cached_git_version
end
--- Check if git satisfies version requirement.
-- @param git_cmd string: name of git command.
-- @param version string: required version.
-- @return boolean: true if git matches version or is newer, false otherwise.
local function git_is_at_least(git_cmd, version)
return git_version(git_cmd) >= vers.parse_version(version)
end
--- Git >= 1.7.10 can clone a branch **or tag**, < 1.7.10 by branch only. We
-- need to know this in order to build the appropriate command; if we can't
-- clone by tag then we'll have to issue a subsequent command to check out the
-- given tag.
-- @param git_cmd string: name of git command.
-- @return boolean: Whether Git can clone by tag.
local function git_can_clone_by_tag(git_cmd)
return git_is_at_least(git_cmd, "1.7.10")
end
--- Git >= 1.8.4 can fetch submodules shallowly, saving bandwidth and time for
-- submodules with large history.
-- @param git_cmd string: name of git command.
-- @return boolean: Whether Git can fetch submodules shallowly.
local function git_supports_shallow_submodules(git_cmd)
return git_is_at_least(git_cmd, "1.8.4")
end
--- Git >= 2.10 can fetch submodules shallowly according to .gitmodules configuration, allowing more granularity.
-- @param git_cmd string: name of git command.
-- @return boolean: Whether Git can fetch submodules shallowly according to .gitmodules.
local function git_supports_shallow_recommendations(git_cmd)
return git_is_at_least(git_cmd, "2.10.0")
end
local function git_identifier(git_cmd, ver)
if not (ver:match("^dev%-%d+$") or ver:match("^scm%-%d+$")) then
return nil
end
local pd = io.popen(fs.command_at(fs.current_dir(), fs.Q(git_cmd).." log --pretty=format:%ai_%h -n 1"))
if not pd then
return nil
end
local date_hash = pd:read("*l")
pd:close()
if not date_hash then
return nil
end
local date, time, tz, hash = date_hash:match("([^%s]+) ([^%s]+) ([^%s]+)_([^%s]+)")
date = date:gsub("%-", "")
time = time:gsub(":", "")
return date .. "." .. time .. "." .. hash
end
local function git_commit(git_cmd, path)
local command = {
fs.Q(git_cmd),
"--git-dir", fs.Q(dir.path(path, '.git')),
'--work-tree', fs.Q(path),
'rev-parse', '--verify', 'HEAD'
}
local fd = io.popen(table.concat(command, ' '))
local out = fd:read("*l")
local ok = fd:close()
if ok then
return out
else
return nil, 'could not get git commit hash'
end
end
local function update_rockspec(rockspec, changes)
local file = rockspec.local_abs_filename
local cached = persist.load_into_table(file)
util.deep_merge(cached, changes)
util.deep_merge(rockspec, changes)
return persist.save_from_table(file, cached, type_check.rockspec_order)
end
--- Download sources for building a rock, using git.
-- @param rockspec table: The rockspec table
-- @param extract boolean: Unused in this module (required for API purposes.)
-- @param dest_dir string or nil: If set, will extract to the given directory.
-- @return (string, string) or (nil, string): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message.
function git.get_sources(rockspec, extract, dest_dir, depth)
assert(type(dest_dir) == "string" or not dest_dir)
local git_cmd = rockspec.variables.GIT
local name_version = rockspec.name .. "-" .. rockspec.version
local module = dir.base_name(rockspec.source.url)
-- Strip off .git from base name if present
module = module:gsub("%.git$", "")
local ok, err_msg = fs.is_tool_available(git_cmd, "Git")
if not ok then
return nil, err_msg
end
local store_dir
if not dest_dir then
store_dir = fs.make_temp_dir(name_version)
if not store_dir then
return nil, "Failed creating temporary directory."
end
util.schedule_function(fs.delete, store_dir)
else
store_dir = dest_dir
end
store_dir = fs.absolute_name(store_dir)
local ok, err = fs.change_dir(store_dir)
if not ok then return nil, err end
local commit = string.match(rockspec.source.hash or '', '%w+')
if not depth and commit then depth = '--shallow-submodules' end -- setting it to empty string fails
local command = {fs.Q(git_cmd), "clone", depth or "--depth=1", rockspec.source.url, module}
local tag_or_branch = rockspec.source.tag or rockspec.source.branch
-- If the tag or branch is explicitly set to "master" in the rockspec, then
-- we can avoid passing it to Git since it's the default.
if tag_or_branch == "master" then tag_or_branch = nil end
if tag_or_branch then
if git_can_clone_by_tag(git_cmd) then
-- The argument to `--branch` can actually be a branch or a tag as of
-- Git 1.7.10.
table.insert(command, 3, "--branch=" .. tag_or_branch)
end
end
if not fs.execute(unpack(command)) then
return nil, "Failed cloning git repository."
end
ok, err = fs.change_dir(module)
if not ok then return nil, err end
if tag_or_branch and not git_can_clone_by_tag() then
if not fs.execute(fs.Q(git_cmd), "checkout", tag_or_branch) then
return nil, 'Failed to check out the "' .. tag_or_branch ..'" tag or branch.'
end
end
-- Fetching git submodules is supported only when rockspec format is >= 3.0.
if rockspec:format_is_at_least("3.0") then
command = {fs.Q(git_cmd), "submodule", "update", "--init", "--recursive"}
if git_supports_shallow_recommendations(git_cmd) then
table.insert(command, 5, "--recommend-shallow")
elseif git_supports_shallow_submodules(git_cmd) then
-- Fetch only the last commit of each submodule.
table.insert(command, 5, "--depth=1")
end
if not fs.execute(unpack(command)) then
return nil, 'Failed to fetch submodules.'
end
end
if not rockspec.source.tag then
rockspec.source.identifier = git_identifier(git_cmd, rockspec.version)
end
if commit then
if not fs.execute(fs.Q(git_cmd), 'reset', '--hard', commit) then
return nil, 'Failed to checkout revision ' .. commit
end
end
ok, err = update_rockspec(rockspec, {
source = {
hash = commit or git_commit(git_cmd, dir.path(store_dir, module))
}
})
if not ok then
return nil, err
end
fs.delete(dir.path(store_dir, module, ".git"))
fs.delete(dir.path(store_dir, module, ".gitignore"))
fs.pop_dir()
fs.pop_dir()
return module, store_dir
end
return git