189 lines
4.1 KiB
Lua
189 lines
4.1 KiB
Lua
|
local socket = require 'socket'
|
||
|
local urllib = require 'socket.url'
|
||
|
local lfs = require 'lfs'
|
||
|
|
||
|
local Repo = git.repo.Repo
|
||
|
local Pack = git.pack.Pack
|
||
|
local join_path = git.util.join_path
|
||
|
local parent_dir = git.util.parent_dir
|
||
|
local make_dir = git.util.make_dir
|
||
|
local correct_separators = git.util.correct_separators
|
||
|
|
||
|
local assert, error, getmetatable, io, os, pairs, print, require, string, tonumber =
|
||
|
assert, error, getmetatable, io, os, pairs, print, require, string, tonumber
|
||
|
|
||
|
local _VERSION, newproxy = _VERSION, newproxy
|
||
|
|
||
|
module(...)
|
||
|
|
||
|
local GIT_PORT = 9418
|
||
|
|
||
|
local function git_connect(host)
|
||
|
local sock = assert(socket.connect(host, GIT_PORT))
|
||
|
local gitsocket = {}
|
||
|
|
||
|
function gitsocket:send(data)
|
||
|
if not data then -- flush packet
|
||
|
sock:send('0000')
|
||
|
else
|
||
|
local len = #data + 4
|
||
|
len = string.format("%04x", len)
|
||
|
assert(sock:send(len .. data))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function gitsocket:receive()
|
||
|
local len = assert(sock:receive(4))
|
||
|
len = tonumber(len, 16)
|
||
|
if len == 0 then return end -- flush packet
|
||
|
local data = assert(sock:receive(len - 4))
|
||
|
return data
|
||
|
end
|
||
|
|
||
|
function gitsocket:close()
|
||
|
sock:close()
|
||
|
end
|
||
|
|
||
|
return gitsocket
|
||
|
end
|
||
|
|
||
|
local function addFinalizer(object, finalizer)
|
||
|
if _VERSION <= "Lua 5.1" then
|
||
|
local gc = newproxy(true)
|
||
|
getmetatable(gc).__gc = finalizer
|
||
|
object.__gc = gc
|
||
|
else
|
||
|
local mt = getmetatable(object)
|
||
|
if mt then mt.__gc = finalizer
|
||
|
else setmetatable(object, {__gc = finalizer})
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function git_fetch(host, path, repo, head, supress_progress)
|
||
|
local s = git_connect(host)
|
||
|
s:send('git-upload-pack '..path..'\0host='..host..'\0')
|
||
|
|
||
|
local refs, refsbyname = {}, {}
|
||
|
repeat
|
||
|
local ref = s:receive()
|
||
|
if ref then
|
||
|
local sha, name = ref:sub(1,40), ref:sub(42, -2)
|
||
|
refs[sha] = name
|
||
|
refsbyname[name] = sha
|
||
|
end
|
||
|
until not ref
|
||
|
|
||
|
local wantedSha
|
||
|
local headsha = head and refsbyname[head]
|
||
|
|
||
|
for sha, ref in pairs(refs) do
|
||
|
-- we implicitly want this ref
|
||
|
local wantObject = true
|
||
|
-- unless we ask for a specific head
|
||
|
if headsha then
|
||
|
if sha ~= headsha then
|
||
|
wantObject = false
|
||
|
else
|
||
|
wantedSha = sha
|
||
|
end
|
||
|
end
|
||
|
-- or we already have it
|
||
|
if repo and repo:has_object(sha) then
|
||
|
wantObject = false
|
||
|
end
|
||
|
if wantObject then
|
||
|
s:send('want '..sha..' multi_ack_detailed side-band-64k ofs-delta\n')
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if head and not wantedSha then
|
||
|
error("Server does not have "..head)
|
||
|
end
|
||
|
|
||
|
s:send('deepen 1')
|
||
|
s:send()
|
||
|
while s:receive() do end
|
||
|
s:send('done\n')
|
||
|
|
||
|
assert(s:receive() == "NAK\n")
|
||
|
|
||
|
local packname = os.tmpname() .. '.pack'
|
||
|
local packfile = assert(io.open(packname, 'wb'))
|
||
|
repeat
|
||
|
local got = s:receive()
|
||
|
if got then
|
||
|
-- get sideband channel, 1=pack data, 2=progress, 3=error
|
||
|
local cmd = string.byte(got:sub(1,1))
|
||
|
local data = got:sub(2)
|
||
|
if cmd == 1 then
|
||
|
packfile:write(data)
|
||
|
elseif cmd == 2 then
|
||
|
if not supress_progress then io.write(data) end
|
||
|
else
|
||
|
error(data)
|
||
|
end
|
||
|
end
|
||
|
until not got
|
||
|
|
||
|
packfile:close()
|
||
|
s:close()
|
||
|
|
||
|
local pack = Pack.open(packname)
|
||
|
if repo then
|
||
|
pack:unpack(repo)
|
||
|
repo.isShallow = true
|
||
|
if wantedSha then
|
||
|
local headfile = correct_separators(join_path(repo.dir, head))
|
||
|
assert(make_dir(parent_dir(headfile)))
|
||
|
local f = assert(io.open(headfile, 'wb'))
|
||
|
f:write(wantedSha)
|
||
|
f:close()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
addFinalizer(pack, function()
|
||
|
os.remove(packname)
|
||
|
end)
|
||
|
|
||
|
return pack, wantedSha
|
||
|
end
|
||
|
|
||
|
function fetch(url, repo, head, supress_progress)
|
||
|
if repo then assert(getmetatable(repo) == Repo, "arg #2 is not a repository") end
|
||
|
url = urllib.parse(url)
|
||
|
if url.scheme == 'git' then
|
||
|
local pack, sha = git_fetch(url.host, url.path, repo, head, supress_progress)
|
||
|
return pack, sha
|
||
|
else
|
||
|
error('unsupported scheme: '..url.scheme)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function remotes(url)
|
||
|
-- TODO: refactor common code
|
||
|
url = assert(urllib.parse(url))
|
||
|
|
||
|
if url.scheme ~= 'git' then
|
||
|
error('unsupported scheme: '..url.scheme)
|
||
|
end
|
||
|
|
||
|
local host, path = url.host, url.path
|
||
|
|
||
|
local s = git_connect(host)
|
||
|
s:send('git-upload-pack '..path..'\0host='..host..'\0')
|
||
|
|
||
|
local remote = {}
|
||
|
repeat
|
||
|
local ref = s:receive()
|
||
|
if ref then
|
||
|
local sha, name = ref:sub(1,40), ref:sub(42, -2)
|
||
|
remote[name] = sha
|
||
|
end
|
||
|
until not ref
|
||
|
|
||
|
s:close()
|
||
|
|
||
|
return remote
|
||
|
end
|