Last active
August 18, 2022 05:38
-
-
Save SquidDev/e0f82765bfdefd48b0b15a5c06c0603b to your computer and use it in GitHub Desktop.
A tiny git clone library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local preload = type(package) == "table" and type(package.preload) == "table" and package.preload or {} | |
local require = require | |
if type(require) ~= "function" then | |
local loading = {} | |
local loaded = {} | |
require = function(name) | |
local result = loaded[name] | |
if result ~= nil then | |
if result == loading then | |
error("loop or previous error loading module '" .. name .. "'", 2) | |
end | |
return result | |
end | |
loaded[name] = loading | |
local contents = preload[name] | |
if contents then | |
result = contents(name) | |
else | |
error("cannot load '" .. name .. "'", 2) | |
end | |
if result == nil then result = true end | |
loaded[name] = result | |
return result | |
end | |
end | |
preload["objects"] = function(...) | |
local inflate_zlib = require "deflate".inflate_zlib | |
local sha = require "metis.crypto.sha1" | |
local band, bor, lshift, rshift = bit32.band, bit32.bor, bit32.lshift, bit32.rshift | |
local byte, format, sub = string.byte, string.format, string.sub | |
local types = { [0] = "none", "commit", "tree", "blob", "tag", nil, "ofs_delta", "ref_delta", "any", "max" } | |
--- Get the type of a specific object | |
-- @tparam Object x The object to get the type of | |
-- @treturn string The object's type. | |
local function get_type(x) return types[x.ty] or "?" end | |
local event = ("luagit-%08x"):format(math.random(0, 2^24)) | |
local function check_in() | |
os.queueEvent(event) | |
os.pullEvent(event) | |
end | |
local sha_format = ("%02x"):rep(20) | |
local function reader(str) | |
local expected_checksum = format(sha_format, byte(str, -20, -1)) | |
local actual_checksum = sha(str:sub(1, -21)); | |
if expected_checksum ~= actual_checksum then | |
error(("checksum mismatch: expected %s, got %s"):format(expected_checksum, actual_checksum)) | |
end | |
str = str:sub(1, -20) | |
local pos = 1 | |
local function consume_read(len) | |
if len <= 0 then error("len < 0", 2) end | |
if pos > #str then error("end of stream") end | |
local cur_pos = pos | |
pos = pos + len | |
local res = sub(str, cur_pos, pos - 1) | |
if #res ~= len then error("expected " .. len .. " bytes, got" .. #res) end | |
return res | |
end | |
local function read8() | |
if pos > #str then error("end of stream") end | |
local cur_pos = pos | |
pos = pos + 1 | |
return byte(str, cur_pos) | |
end | |
return { | |
offset = function() return pos - 1 end, | |
read8 = read8, | |
read16 = function() return (read8() * (2^8)) + read8() end, | |
read32 = function() return (read8() * (2^24)) + (read8() * (2^16)) + (read8() * (2^8)) + read8() end, | |
read = consume_read, | |
close = function() | |
if pos ~= #str then error(("%d of %d bytes remaining"):format(#str - pos + 1, #str)) end | |
end, | |
} | |
end | |
--- Consume a string from the given input buffer | |
-- | |
-- @tparam Reader handle The handle to read from | |
-- @tparam number size The number of decompressed bytes to read | |
-- @treturn string The decompressed data | |
local function get_data(handle, size) | |
local tbl, n = {}, 1 | |
inflate_zlib { | |
input = handle.read8, | |
output = function(x) tbl[n], n = string.char(x), n + 1 end | |
} | |
local res = table.concat(tbl) | |
if #res ~= size then error(("expected %d decompressed bytes, got %d"):format(size, #res)) end | |
return res | |
end | |
--- Decode a binary delta file, applying it to the original | |
-- | |
-- The format is described in more detail in [the Git documentation][git_pack] | |
-- | |
-- [git_pack]: https://git-scm.com/docs/pack-format#_deltified_representation | |
-- | |
-- @tparam string original The original string | |
-- @tparam string delta The binary delta | |
-- @treturn string The patched string | |
local function apply_delta(original, delta) | |
local delta_offset = 1 | |
local function read_size() | |
local c = byte(delta, delta_offset) | |
delta_offset = delta_offset + 1 | |
local size = band(c, 0x7f) | |
local shift = 7 | |
while band(c, 0x80) ~= 0 do | |
c, delta_offset = byte(delta, delta_offset), delta_offset + 1 | |
size, shift = size + lshift(band(c, 0x7f), shift), shift + 7 | |
end | |
return size | |
end | |
local original_length = read_size() | |
local patched_length = read_size() | |
if original_length ~= #original then | |
error(("expected original of size %d, got size %d"):format(original_length, #original)) | |
end | |
local parts, n = {}, 1 | |
while delta_offset <= #delta do | |
local b = byte(delta, delta_offset) | |
delta_offset = delta_offset + 1 | |
if band(b, 0x80) ~= 0 then | |
-- Copy from the original file. Each bit represents which optional length/offset | |
-- bits are used. | |
local offset, length = 0, 0 | |
if band(b, 0x01) ~= 0 then | |
offset, delta_offset = bor(offset, byte(delta, delta_offset)), delta_offset + 1 | |
end | |
if band(b, 0x02) ~= 0 then | |
offset, delta_offset = bor(offset, lshift(byte(delta, delta_offset), 8)), delta_offset + 1 | |
end | |
if band(b, 0x04) ~= 0 then | |
offset, delta_offset = bor(offset, lshift(byte(delta, delta_offset), 16)), delta_offset + 1 | |
end | |
if band(b, 0x08) ~= 0 then | |
offset, delta_offset = bor(offset, lshift(byte(delta, delta_offset), 24)), delta_offset + 1 | |
end | |
if band(b, 0x10) ~= 0 then | |
length, delta_offset = bor(length, byte(delta, delta_offset)), delta_offset + 1 | |
end | |
if band(b, 0x20) ~= 0 then | |
length, delta_offset = bor(length, lshift(byte(delta, delta_offset), 8)), delta_offset + 1 | |
end | |
if band(b, 0x40) ~= 0 then | |
length, delta_offset = bor(length, lshift(byte(delta, delta_offset), 16)), delta_offset + 1 | |
end | |
if length == 0 then length = 0x10000 end | |
parts[n], n = sub(original, offset + 1, offset + length), n + 1 | |
elseif b > 0 then | |
-- Copy from the delta. The opcode encodes the length | |
parts[n], n = sub(delta, delta_offset, delta_offset + b - 1), n + 1 | |
delta_offset = delta_offset + b | |
else | |
error(("unknown opcode '%02x'"):format(b)) | |
end | |
end | |
local patched = table.concat(parts) | |
if patched_length ~= #patched then | |
error(("expected patched of size %d, got size %d"):format(patched_length, #patched)) | |
end | |
return patched | |
end | |
--- Unpack a single object, populating the output table | |
-- | |
-- @tparam Reader handle The handle to read from | |
-- @tparam { [string] = Object } out The populated data | |
local function unpack_object(handle, out) | |
local c = handle.read8() | |
local ty = band(rshift(c, 4), 7) | |
local size = band(c, 15) | |
local shift = 4 | |
while band(c, 0x80) ~= 0 do | |
c = handle.read8() | |
size = size + lshift(band(c, 0x7f), shift) | |
shift = shift + 7 | |
end | |
local data | |
if ty >= 1 and ty <= 4 then | |
-- commit/tree/blob/tag | |
data = get_data(handle, size) | |
elseif ty == 6 then | |
-- ofs_delta | |
data = get_data(handle, size) | |
error("ofs_delta not yet implemented") | |
elseif ty == 7 then | |
-- ref_delta | |
local base_hash = sha_format:format(handle.read(20):byte(1, 20)) | |
local delta = get_data(handle, size) | |
local original = out[base_hash] | |
if not original then error(("cannot find object %d to apply diff"):format(base_hash)) return end | |
ty = original.ty | |
data = apply_delta(original.data, delta) | |
else | |
error(("unknown object of type '%d'"):format(ty)) | |
end | |
-- We've got to do these separately. Format doesn't like null bytes | |
local whole = ("%s %d\0"):format(types[ty], #data) .. data | |
local sha = sha(whole) | |
out[sha] = { ty = ty, data = data, sha = sha } | |
end | |
local function unpack(handle, progress) | |
local header = handle.read(4) | |
if header ~= "PACK" then error("expected PACK, got " .. header, 0) end | |
local version = handle.read32() | |
local entries = handle.read32() | |
local out = {} | |
for i = 1, entries do | |
if progress then progress(i, entries) end | |
check_in() | |
unpack_object(handle, out) | |
end | |
return out | |
end | |
local function build_tree(objects, object, prefix, out) | |
if not prefix then prefix = "" end | |
if not out then out = {} end | |
local idx = 1 | |
while idx <= #object do | |
-- dddddd NAME\0<SHA> | |
local _, endidx, mode, name = object:find("^(%x+) ([^%z]+)%z", idx) | |
if not endidx then break end | |
name = prefix .. name | |
local sha = object:sub(endidx + 1, endidx + 20):gsub(".", function(x) return ("%02x"):format(string.byte(x)) end) | |
local entry = objects[sha] | |
if not entry then error(("cannot find %s %s (%s)"):format(mode, name, sha)) end | |
if entry.ty == 3 then | |
out[name] = entry.data | |
elseif entry.ty == 2 then | |
build_tree(objects, entry.data, name .. "/", out) | |
else | |
error("unknown type for " .. name .. " (" .. sha .. "): " .. get_type(entry)) | |
end | |
idx = endidx + 21 | |
end | |
return out | |
end | |
local function build_commit(objects, sha) | |
local commit = objects[sha] | |
if not commit then error("cannot find commit " .. sha) end | |
if commit.ty ~= 1 then error("Expected commit, got " .. types[commit.ty]) end | |
local tree_sha = commit.data:match("tree (%x+)\n") | |
if not tree_sha then error("Cannot find tree from commit") end | |
local tree = objects[tree_sha] | |
if not tree then error("cannot find tree " .. tree_sha) end | |
if tree.ty ~= 2 then error("Expected tree, got " .. tree[tree.ty]) end | |
return build_tree(objects, tree.data) | |
end | |
return { | |
reader = reader, | |
unpack = unpack, | |
build_tree = build_tree, | |
build_commit = build_commit, | |
type = get_type, | |
} | |
end | |
preload["network"] = function(...) | |
local function pkt_line(msg) | |
return ("%04x%s\n"):format(5 + #msg, msg) | |
end | |
local function pkt_linef(fmt, ...) | |
return pkt_line(fmt:format(...)) | |
end | |
local flush_line = "0000" | |
local function read_pkt_line(handle) | |
local data = handle.read(4) | |
if data == nil or data == "" then return nil end | |
local len = tonumber(data, 16) | |
if len == nil then | |
error(("read_pkt_line: cannot convert %q to a number"):format(data)) | |
elseif len == 0 then | |
return false, data | |
else | |
return handle.read(len - 4), data | |
end | |
end | |
local function fetch(url, lines, content_type) | |
if type(lines) == "table" then lines = table.concat(lines) end | |
local ok, err = http.request(url, lines, { | |
['User-Agent'] = 'CCGit/1.0', | |
['Content-Type'] = content_type, | |
}, true) | |
if ok then | |
while true do | |
local event, event_url, param1, param2 = os.pullEvent() | |
if event == "http_success" and event_url == url then | |
return true, param1 | |
elseif event == "http_failure" and event_url == url then | |
printError("Cannot fetch " .. url .. ": " .. param1) | |
return false, param2 | |
end | |
end | |
else | |
printError("Cannot fetch " .. url .. ": " .. err) | |
return false, nil | |
end | |
end | |
local function force_fetch(...) | |
local ok, handle, err_handle = fetch(...) | |
if not ok then | |
if err_handle then | |
print(err_handle.getStatusCode()) | |
print(textutils.serialize(err_handle.getResponseHeaders())) | |
print(err_handle.readAll()) | |
end | |
error("Cannot fetch", 0) | |
end | |
return handle | |
end | |
local function receive(handle) | |
local out = {} | |
while true do | |
local line = read_pkt_line(handle) | |
if line == nil then break end | |
out[#out + 1] = line | |
end | |
handle.close() | |
return out | |
end | |
return { | |
read_pkt_line = read_pkt_line, | |
force_fetch = force_fetch, | |
receive = receive, | |
pkt_linef = pkt_linef, | |
flush_line = flush_line, | |
} | |
end | |
preload["deflate"] = function(...) | |
--[[ | |
(c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT). | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
THE SOFTWARE. | |
(end license) | |
--]] | |
local assert, error, ipairs, pairs, tostring, type, setmetatable, io, math | |
= assert, error, ipairs, pairs, tostring, type, setmetatable, io, math | |
local table_sort, math_max, string_char = table.sort, math.max, string.char | |
local band, lshift, rshift = bit32.band, bit32.lshift, bit32.rshift | |
local function make_outstate(outbs) | |
local outstate = {} | |
outstate.outbs = outbs | |
outstate.len = 0 | |
outstate.window = {} | |
outstate.window_pos = 1 | |
return outstate | |
end | |
local function output(outstate, byte) | |
local window_pos = outstate.window_pos | |
outstate.outbs(byte) | |
outstate.len = outstate.len + 1 | |
outstate.window[window_pos] = byte | |
outstate.window_pos = window_pos % 32768 + 1 -- 32K | |
end | |
local function noeof(val) | |
return assert(val, 'unexpected end of file') | |
end | |
local function memoize(f) | |
return setmetatable({}, { | |
__index = function(self, k) | |
local v = f(k) | |
self[k] = v | |
return v | |
end | |
}) | |
end | |
-- small optimization (lookup table for powers of 2) | |
local pow2 = memoize(function(n) return 2^n end) | |
local function bitstream_from_bytestream(bys) | |
local buf_byte = 0 | |
local buf_nbit = 0 | |
local o = { type = "bitstream" } | |
function o:nbits_left_in_byte() | |
return buf_nbit | |
end | |
function o:read(nbits) | |
nbits = nbits or 1 | |
while buf_nbit < nbits do | |
local byte = bys() | |
if not byte then return end -- note: more calls also return nil | |
buf_byte = buf_byte + lshift(byte, buf_nbit) | |
buf_nbit = buf_nbit + 8 | |
end | |
local bits | |
if nbits == 0 then | |
bits = 0 | |
elseif nbits == 32 then | |
bits = buf_byte | |
buf_byte = 0 | |
else | |
bits = band(buf_byte, rshift(0xffffffff, 32 - nbits)) | |
buf_byte = rshift(buf_byte, nbits) | |
end | |
buf_nbit = buf_nbit - nbits | |
return bits | |
end | |
return o | |
end | |
local function get_bitstream(o) | |
if type(o) == "table" and o.type == "bitstream" then | |
return o | |
elseif io.type(o) == 'file' then | |
return bitstream_from_bytestream(function() local sb = o:read(1) if sb then return sb:byte() end end) | |
elseif type(o) == "function" then | |
return bitstream_from_bytestream(o) | |
else | |
error 'unrecognized type' | |
end | |
end | |
local function get_obytestream(o) | |
local bs | |
if io.type(o) == 'file' then | |
bs = function(sbyte) o:write(string_char(sbyte)) end | |
elseif type(o) == 'function' then | |
bs = o | |
else | |
error('unrecognized type: ' .. tostring(o)) | |
end | |
return bs | |
end | |
local function HuffmanTable(init, is_full) | |
local t = {} | |
if is_full then | |
for val,nbits in pairs(init) do | |
if nbits ~= 0 then | |
t[#t+1] = {val=val, nbits=nbits} | |
end | |
end | |
else | |
for i=1,#init-2,2 do | |
local firstval, nbits, nextval = init[i], init[i+1], init[i+2] | |
if nbits ~= 0 then | |
for val=firstval,nextval-1 do | |
t[#t+1] = {val=val, nbits=nbits} | |
end | |
end | |
end | |
end | |
table_sort(t, function(a,b) | |
return a.nbits == b.nbits and a.val < b.val or a.nbits < b.nbits | |
end) | |
-- assign codes | |
local code = 1 -- leading 1 marker | |
local nbits = 0 | |
for _, s in ipairs(t) do | |
if s.nbits ~= nbits then | |
code = code * pow2[s.nbits - nbits] | |
nbits = s.nbits | |
end | |
s.code = code | |
code = code + 1 | |
end | |
local minbits = math.huge | |
local look = {} | |
for _, s in ipairs(t) do | |
minbits = math.min(minbits, s.nbits) | |
look[s.code] = s.val | |
end | |
local msb = function(bits, nbits) | |
local res = 0 | |
for _ = 1, nbits do | |
res = lshift(res, 1) + band(bits, 1) | |
bits = rshift(bits, 1) | |
end | |
return res | |
end | |
local tfirstcode = memoize( | |
function(bits) return pow2[minbits] + msb(bits, minbits) end) | |
function t:read(bs) | |
local code = 1 -- leading 1 marker | |
local nbits = 0 | |
while 1 do | |
if nbits == 0 then -- small optimization (optional) | |
code = tfirstcode[noeof(bs:read(minbits))] | |
nbits = nbits + minbits | |
else | |
local b = noeof(bs:read()) | |
nbits = nbits + 1 | |
code = code * 2 + b -- MSB first | |
end | |
local val = look[code] | |
if val then | |
return val | |
end | |
end | |
end | |
return t | |
end | |
local function parse_zlib_header(bs) | |
local cm = bs:read(4) -- Compression Method | |
local cinfo = bs:read(4) -- Compression info | |
local fcheck = bs:read(5) -- FLaGs: FCHECK (check bits for CMF and FLG) | |
local fdict = bs:read(1) -- FLaGs: FDICT (present dictionary) | |
local flevel = bs:read(2) -- FLaGs: FLEVEL (compression level) | |
local cmf = cinfo * 16 + cm -- CMF (Compresion Method and flags) | |
local flg = fcheck + fdict * 32 + flevel * 64 -- FLaGs | |
if cm ~= 8 then -- not "deflate" | |
error("unrecognized zlib compression method: " .. cm) | |
end | |
if cinfo > 7 then | |
error("invalid zlib window size: cinfo=" .. cinfo) | |
end | |
local window_size = 2^(cinfo + 8) | |
if (cmf*256 + flg) % 31 ~= 0 then | |
error("invalid zlib header (bad fcheck sum)") | |
end | |
if fdict == 1 then | |
error("FIX:TODO - FDICT not currently implemented") | |
local dictid_ = bs:read(32) | |
end | |
return window_size | |
end | |
local function parse_huffmantables(bs) | |
local hlit = bs:read(5) -- # of literal/length codes - 257 | |
local hdist = bs:read(5) -- # of distance codes - 1 | |
local hclen = noeof(bs:read(4)) -- # of code length codes - 4 | |
local ncodelen_codes = hclen + 4 | |
local codelen_init = {} | |
local codelen_vals = { | |
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15} | |
for i=1,ncodelen_codes do | |
local nbits = bs:read(3) | |
local val = codelen_vals[i] | |
codelen_init[val] = nbits | |
end | |
local codelentable = HuffmanTable(codelen_init, true) | |
local function decode(ncodes) | |
local init = {} | |
local nbits | |
local val = 0 | |
while val < ncodes do | |
local codelen = codelentable:read(bs) | |
--FIX:check nil? | |
local nrepeat | |
if codelen <= 15 then | |
nrepeat = 1 | |
nbits = codelen | |
elseif codelen == 16 then | |
nrepeat = 3 + noeof(bs:read(2)) | |
-- nbits unchanged | |
elseif codelen == 17 then | |
nrepeat = 3 + noeof(bs:read(3)) | |
nbits = 0 | |
elseif codelen == 18 then | |
nrepeat = 11 + noeof(bs:read(7)) | |
nbits = 0 | |
else | |
error 'ASSERT' | |
end | |
for _ = 1, nrepeat do | |
init[val] = nbits | |
val = val + 1 | |
end | |
end | |
local huffmantable = HuffmanTable(init, true) | |
return huffmantable | |
end | |
local nlit_codes = hlit + 257 | |
local ndist_codes = hdist + 1 | |
local littable = decode(nlit_codes) | |
local disttable = decode(ndist_codes) | |
return littable, disttable | |
end | |
local tdecode_len_base | |
local tdecode_len_nextrabits | |
local tdecode_dist_base | |
local tdecode_dist_nextrabits | |
local function parse_compressed_item(bs, outstate, littable, disttable) | |
local val = littable:read(bs) | |
if val < 256 then -- literal | |
output(outstate, val) | |
elseif val == 256 then -- end of block | |
return true | |
else | |
if not tdecode_len_base then | |
local t = {[257]=3} | |
local skip = 1 | |
for i=258,285,4 do | |
for j=i,i+3 do t[j] = t[j-1] + skip end | |
if i ~= 258 then skip = skip * 2 end | |
end | |
t[285] = 258 | |
tdecode_len_base = t | |
end | |
if not tdecode_len_nextrabits then | |
local t = {} | |
for i=257,285 do | |
local j = math_max(i - 261, 0) | |
t[i] = rshift(j, 2) | |
end | |
t[285] = 0 | |
tdecode_len_nextrabits = t | |
end | |
local len_base = tdecode_len_base[val] | |
local nextrabits = tdecode_len_nextrabits[val] | |
local extrabits = bs:read(nextrabits) | |
local len = len_base + extrabits | |
if not tdecode_dist_base then | |
local t = {[0]=1} | |
local skip = 1 | |
for i=1,29,2 do | |
for j=i,i+1 do t[j] = t[j-1] + skip end | |
if i ~= 1 then skip = skip * 2 end | |
end | |
tdecode_dist_base = t | |
end | |
if not tdecode_dist_nextrabits then | |
local t = {} | |
for i=0,29 do | |
local j = math_max(i - 2, 0) | |
t[i] = rshift(j, 1) | |
end | |
tdecode_dist_nextrabits = t | |
end | |
local dist_val = disttable:read(bs) | |
local dist_base = tdecode_dist_base[dist_val] | |
local dist_nextrabits = tdecode_dist_nextrabits[dist_val] | |
local dist_extrabits = bs:read(dist_nextrabits) | |
local dist = dist_base + dist_extrabits | |
for _ = 1,len do | |
local pos = (outstate.window_pos - 1 - dist) % 32768 + 1 -- 32K | |
output(outstate, assert(outstate.window[pos], 'invalid distance')) | |
end | |
end | |
return false | |
end | |
local function parse_block(bs, outstate) | |
local bfinal = bs:read(1) | |
local btype = bs:read(2) | |
local BTYPE_NO_COMPRESSION = 0 | |
local BTYPE_FIXED_HUFFMAN = 1 | |
local BTYPE_DYNAMIC_HUFFMAN = 2 | |
local _BTYPE_RESERVED = 3 | |
if btype == BTYPE_NO_COMPRESSION then | |
bs:read(bs:nbits_left_in_byte()) | |
local len = bs:read(16) | |
local _nlen = noeof(bs:read(16)) | |
for i=1,len do | |
local by = noeof(bs:read(8)) | |
output(outstate, by) | |
end | |
elseif btype == BTYPE_FIXED_HUFFMAN or btype == BTYPE_DYNAMIC_HUFFMAN then | |
local littable, disttable | |
if btype == BTYPE_DYNAMIC_HUFFMAN then | |
littable, disttable = parse_huffmantables(bs) | |
else | |
littable = HuffmanTable {0,8, 144,9, 256,7, 280,8, 288,nil} | |
disttable = HuffmanTable {0,5, 32,nil} | |
end | |
repeat | |
local is_done = parse_compressed_item( | |
bs, outstate, littable, disttable) | |
until is_done | |
else | |
error('unrecognized compression type '..btype) | |
end | |
return bfinal ~= 0 | |
end | |
local function inflate(t) | |
local bs = get_bitstream(t.input) | |
local outbs = get_obytestream(t.output) | |
local outstate = make_outstate(outbs) | |
repeat | |
local is_final = parse_block(bs, outstate) | |
until is_final | |
end | |
local function adler32(byte, crc) | |
local s1 = crc % 65536 | |
local s2 = (crc - s1) / 65536 | |
s1 = (s1 + byte) % 65521 | |
s2 = (s2 + s1) % 65521 | |
return s2*65536 + s1 | |
end -- 65521 is the largest prime smaller than 2^16 | |
local function inflate_zlib(t) | |
local bs = get_bitstream(t.input) | |
local outbs = get_obytestream(t.output) | |
local disable_crc = t.disable_crc | |
if disable_crc == nil then disable_crc = false end | |
local _window_size = parse_zlib_header(bs) | |
local data_adler32 = 1 | |
inflate { | |
input=bs, | |
output = disable_crc and outbs or function(byte) | |
data_adler32 = adler32(byte, data_adler32) | |
outbs(byte) | |
end, | |
len = t.len, | |
} | |
bs:read(bs:nbits_left_in_byte()) | |
local b3 = bs:read(8) | |
local b2 = bs:read(8) | |
local b1 = bs:read(8) | |
local b0 = bs:read(8) | |
local expected_adler32 = ((b3*256 + b2)*256 + b1)*256 + b0 | |
if not disable_crc then | |
if data_adler32 ~= expected_adler32 then | |
error('invalid compressed data--crc error') | |
end | |
end | |
end | |
return { | |
inflate = inflate, | |
inflate_zlib = inflate_zlib, | |
} | |
end | |
preload["clone"] = function(...) | |
--- Git clone in Lua, from the bottom up | |
-- | |
-- http://stefan.saasen.me/articles/git-clone-in-haskell-from-the-bottom-up/#the_clone_process | |
-- https://github.com/creationix/lua-git | |
do -- metis loader | |
local modules = { | |
["metis.argparse"] = "src/metis/argparse.lua", | |
["metis.crypto.sha1"] = "src/metis/crypto/sha1.lua", | |
["metis.timer"] = "src/metis/timer.lua", | |
} | |
package.loaders[#package.loaders + 1] = function(name) | |
local path = modules[name] | |
if not path then return nil, "not a metis module" end | |
local local_path = "/.cache/metis/ae11085f261e5b506654162c80d21954c0d54e5e/" .. path | |
if not fs.exists(local_path) then | |
local url = "https://raw.githubusercontent.com/SquidDev-CC/metis/ae11085f261e5b506654162c80d21954c0d54e5e/" .. path | |
local request, err = http.get(url) | |
if not request then return nil, "Cannot download " .. url .. ": " .. err end | |
local out = fs.open(local_path, "w") | |
out.write(request.readAll()) | |
out.close() | |
request.close() | |
end | |
local fn, err = loadfile(local_path, nil, _ENV) | |
if fn then return fn, local_path else return nil, err end | |
end | |
end | |
local network = require "network" | |
local objects = require "objects" | |
local url, name = ... | |
if not url or url == "-h" or url == "--help" then error("clone.lua URL [name]", 0) end | |
if url:sub(-1) == "/" then url = url:sub(1, -2) end | |
name = name or fs.getName(url):gsub("%.git$", "") | |
local destination = shell.resolve(name) | |
if fs.exists(destination) then | |
error(("%q already exists"):format(name), 0) | |
end | |
local function report(msg) | |
local last = "" | |
for line in msg:gmatch("[^\n]+") do last = line end | |
term.setCursorPos(1, select(2, term.getCursorPos())) | |
term.clearLine() | |
term.write(last) | |
end | |
local head | |
do -- Request a list of all refs | |
report("Cloning from " .. url) | |
local handle = network.force_fetch(url .. "/info/refs?service=git-upload-pack") | |
local res = network.receive(handle) | |
local sha_ptrn = ("%x"):rep(40) | |
local caps = {} | |
local refs = {} | |
for i = 1, #res do | |
local line = res[i] | |
if line ~= false and line:sub(1, 1) ~= "#" then | |
local sha, name = line:match("(" .. sha_ptrn .. ") ([^%z\n]+)") | |
if sha and name then | |
refs[name] = sha | |
local capData = line:match("%z([^\n]+)\n") | |
if capData then | |
for cap in (capData .. " "):gmatch("%S+") do | |
local eq = cap:find("=") | |
if eq then | |
caps[cap:sub(1, eq - 1)] = cap:sub(eq + 1) | |
else | |
caps[cap] = true | |
end | |
end | |
end | |
else | |
printError("Unexpected line: " .. line) | |
end | |
end | |
end | |
head = refs['HEAD'] or refs['refs/heads/master'] or error("Cannot find master", 0) | |
if not caps['shallow'] then error("Server does not support shallow fetching", 0) end | |
-- TODO: Handle both. We don't even need the side-band really? | |
if not caps['side-band-64k'] then error("Server does not support side band", 0) end | |
end | |
do -- Now actually perform the clone | |
local handle = network.force_fetch(url .. "/git-upload-pack", { | |
network.pkt_linef("want %s side-band-64k shallow", head), | |
network.pkt_linef("deepen 1"), | |
network.flush_line, | |
network.pkt_linef("done"), | |
}, "application/x-git-upload-pack-request") | |
local pack, head = {}, nil | |
while true do | |
local line = network.read_pkt_line(handle) | |
if line == nil then break end | |
if line == false or line == "NAK\n" then | |
-- Skip | |
elseif line:byte(1) == 1 then | |
table.insert(pack, line:sub(2)) | |
elseif line:byte(1) == 2 or line:byte(1) == 3 then | |
report(line:sub(2):gsub("\r", "\n")) | |
elseif line:find("^shallow ") then | |
head = line:sub(#("shallow ") + 1) | |
else | |
printError("Unknown line: " .. tostring(line)) | |
end | |
end | |
handle.close() | |
local stream = objects.reader(table.concat(pack)) | |
local objs = objects.unpack(stream, function(x, n) | |
report(("Extracting %d/%d (%.2f%%)"):format(x, n, x/n*100)) | |
end) | |
stream.close() | |
if not head then error("Cannot find HEAD commit", 0) end | |
for k, v in pairs(objects.build_commit(objs, head)) do | |
local out = fs.open(fs.combine(destination, fs.combine(k, "")), "wb") | |
out.write(v) | |
out.close() | |
end | |
end | |
report(("Cloned to %q"):format(name)) | |
print() | |
end | |
return preload["clone"](...) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local e=type(package)=="table"and type(package.preload)=="table"and package.preload or{}local t=require if type(t)~= | |
"function"then local a={}local o={}t=function(i)local n=o[i]if n~=nil then if n==a then error( | |
"loop or previous error loading module '"..i.."'",2)end return n end o[i]=a local s=e[i]if s then n=s(i)else error( | |
"cannot load '"..i.."'",2)end if n==nil then n=true end o[i]=n return n end end e["objects"]=function(...)local a=t | |
"deflate".inflate_zlib local o=t"metis.crypto.sha1"local i,n,s,h=bit32.band,bit32.bor,bit32.lshift,bit32.rshift local r, | |
d,l=string.byte,string.format,string.sub local u={[0]="none","commit","tree","blob","tag",nil,"ofs_delta","ref_delta", | |
"any","max"}local function c(j)return u[j.ty]or"?"end local m=("luagit-%08x"):format(math.random(0,2^24))local function | |
f()os.queueEvent(m)os.pullEvent(m)end local w=("%02x"):rep(20)local function y(j)local x=d(w,r(j,-20,-1))local z=o(j:sub( | |
1,-21))if x~=z then error(("checksum mismatch: expected %s, got %s"):format(x,z))end j=j:sub(1,-20)local _=1 local | |
function E(A)if A<=0 then error("len < 0",2)end if _>#j then error("end of stream")end local O=_ _=_+A local I=l(j,O,_-1 | |
)if#I~=A then error("expected "..A.." bytes, got"..#I)end return I end local function T()if _>#j then error( | |
"end of stream")end local A=_ _=_+1 return r(j,A)end return{offset=function()return _-1 end,read8=T,read16=function() | |
return(T()*(2^8))+T()end,read32=function()return(T()*(2^24))+(T()*(2^16))+(T()*(2^8))+T()end,read=E,close=function()if _ | |
~=#j then error(("%d of %d bytes remaining"):format(#j-_+1,#j))end end}end local function p(j,x)local z,_={},1 a{input=j | |
.read8,output=function(T)z[_],_=string.char(T),_+1 end}local E=table.concat(z)if#E~=x then error(( | |
"expected %d decompressed bytes, got %d"):format(x,#E))end return E end local function v(j,x)local z=1 local function _( | |
)local N=r(x,z)z=z+1 local S=i(N,0x7f)local H=7 while i(N,0x80)~=0 do N,z=r(x,z),z+1 S,H=S+s(i(N,0x7f),H),H+7 end return | |
S end local E=_()local T=_()if E~=#j then error(("expected original of size %d, got size %d"):format(E,#j))end local A,O | |
={},1 while z<=#x do local N=r(x,z)z=z+1 if i(N,0x80)~=0 then local S,H=0,0 if i(N,0x01)~=0 then S,z=n(S,r(x,z)),z+1 end | |
if i(N,0x02)~=0 then S,z=n(S,s(r(x,z),8)),z+1 end if i(N,0x04)~=0 then S,z=n(S,s(r(x,z),16)),z+1 end if i(N,0x08)~=0 | |
then S,z=n(S,s(r(x,z),24)),z+1 end if i(N,0x10)~=0 then H,z=n(H,r(x,z)),z+1 end if i(N,0x20)~=0 then H,z=n(H,s(r(x,z),8) | |
),z+1 end if i(N,0x40)~=0 then H,z=n(H,s(r(x,z),16)),z+1 end if H==0 then H=0x10000 end A[O],O=l(j,S+1,S+H),O+1 elseif N | |
>0 then A[O],O=l(x,z,z+N-1),O+1 z=z+N else error(("unknown opcode '%02x'"):format(N))end end local I=table.concat(A)if T | |
~=#I then error(("expected patched of size %d, got size %d"):format(T,#I))end return I end local function b(j,x)local z= | |
j.read8()local _=i(h(z,4),7)local E=i(z,15)local T=4 while i(z,0x80)~=0 do z=j.read8()E=E+s(i(z,0x7f),T)T=T+7 end local | |
A if _>=1 and _<=4 then A=p(j,E)elseif _==6 then A=p(j,E)error("ofs_delta not yet implemented")elseif _==7 then local I= | |
w:format(j.read(20):byte(1,20))local N=p(j,E)local S=x[I]if not S then error(("cannot find object %d to apply diff"): | |
format(I))return end _=S.ty A=v(S.data,N)else error(("unknown object of type '%d'"):format(_))end local O=("%s %d\0"): | |
format(u[_],#A)..A local o=o(O)x[o]={ty=_,data=A,sha=o}end local function g(j,x)local z=j.read(4)if z~="PACK"then error( | |
"expected PACK, got "..z,0)end local _=j.read32()local E=j.read32()local T={}for A=1,E do if x then x(A,E)end f()b(j,T) | |
end return T end local function k(j,x,z,_)if not z then z=""end if not _ then _={}end local E=1 while E<=#x do local T,A | |
,O,I=x:find("^(%x+) ([^%z]+)%z",E)if not A then break end I=z..I local o=x:sub(A+1,A+20):gsub(".",function(S)return( | |
"%02x"):format(string.byte(S))end)local N=j[o]if not N then error(("cannot find %s %s (%s)"):format(O,I,o))end if N.ty== | |
3 then _[I]=N.data elseif N.ty==2 then k(j,N.data,I.."/",_)else error("unknown type for "..I.." ("..o.."): "..c(N))end E | |
=A+21 end return _ end local function q(j,o)local x=j[o]if not x then error("cannot find commit "..o)end if x.ty~=1 then | |
error("Expected commit, got "..u[x.ty])end local z=x.data:match("tree (%x+)\n")if not z then error( | |
"Cannot find tree from commit")end local _=j[z]if not _ then error("cannot find tree "..z)end if _.ty~=2 then error( | |
"Expected tree, got ".._[_.ty])end return k(j,_.data)end return{reader=y,unpack=g,build_tree=k,build_commit=q,type=c}end | |
e["network"]=function(...)local function a(d)return("%04x%s\n"):format(5+#d,d)end local function o(d,...)return a(d: | |
format(...))end local i="0000"local function n(d)local l=d.read(4)if l==nil or l==""then return nil end local u=tonumber( | |
l,16)if u==nil then error(("read_pkt_line: cannot convert %q to a number"):format(l))elseif u==0 then return false,l | |
else return d.read(u-4),l end end local function s(d,l,u)if type(l)=="table"then l=table.concat(l)end local c,m=http. | |
request(d,l,{['User-Agent']='CCGit/1.0',['Content-Type']=u},true)if c then while true do local f,w,y,p=os.pullEvent()if | |
f=="http_success"and w==d then return true,y elseif f=="http_failure"and w==d then printError("Cannot fetch "..d..": ".. | |
y)return false,p end end else printError("Cannot fetch "..d..": "..m)return false,nil end end local function h(...) | |
local d,l,u=s(...)if not d then if u then print(u.getStatusCode())print(textutils.serialize(u.getResponseHeaders())) | |
print(u.readAll())end error("Cannot fetch",0)end return l end local function r(d)local l={}while true do local u=n(d)if | |
u==nil then break end l[#l+1]=u end d.close()return l end return{read_pkt_line=n,force_fetch=h,receive=r,pkt_linef=o, | |
flush_line=i}end e["deflate"]=function(...)local a,o,i,n,s,h,r,d,l=assert,error,ipairs,pairs,tostring,type,setmetatable, | |
io,math local u,c,m=table.sort,l.max,string.char local f,w,y=bit32.band,bit32.lshift,bit32.rshift local function p(L) | |
local U={}U.outbs=L U.len=0 U.window={}U.window_pos=1 return U end local function v(L,U)local C=L.window_pos L.outbs(U)L | |
.len=L.len+1 L.window[C]=U L.window_pos=C%32768+1 end local function b(L)return a(L,'unexpected end of file')end local | |
function g(L)return r({},{__index=function(U,C)local M=L(C)U[C]=M return M end})end local k=g(function(L)return 2^L end) | |
local function q(L)local U=0 local C=0 local M={type="bitstream"}function M:nbits_left_in_byte()return C end function M: | |
read(F)F=F or 1 while C<F do local Y=L()if not Y then return end U=U+w(Y,C)C=C+8 end local W if F==0 then W=0 elseif F== | |
32 then W=U U=0 else W=f(U,y(0xffffffff,32-F))U=y(U,F)end C=C-F return W end return M end local function j(L)if h(L)== | |
"table"and L.type=="bitstream"then return L elseif d.type(L)=='file'then return q(function()local U=L:read(1)if U then | |
return U:byte()end end)elseif h(L)=="function"then return q(L)else o'unrecognized type'end end local function x(L)local | |
U if d.type(L)=='file'then U=function(C)L:write(m(C))end elseif h(L)=='function'then U=L else o('unrecognized type: '..s( | |
L))end return U end local function z(L,U)local C={}if U then for B,G in n(L)do if G~=0 then C[#C+1]={val=B,nbits=G}end | |
end else for B=1,#L-2,2 do local G,K,Q=L[B],L[B+1],L[B+2]if K~=0 then for J=G,Q-1 do C[#C+1]={val=J,nbits=K}end end end | |
end u(C,function(B,G)return B.nbits==G.nbits and B.val<G.val or B.nbits<G.nbits end)local M=1 local F=0 for B,G in i(C) | |
do if G.nbits~=F then M=M*k[G.nbits-F]F=G.nbits end G.code=M M=M+1 end local W=l.huge local Y={}for B,G in i(C)do W=l. | |
min(W,G.nbits)Y[G.code]=G.val end local P=function(B,F)local G=0 for K=1,F do G=w(G,1)+f(B,1)B=y(B,1)end return G end | |
local V=g(function(B)return k[W]+P(B,W)end)function C:read(B)local M=1 local F=0 while 1 do if F==0 then M=V[b(B:read(W) | |
)]F=F+W else local K=b(B:read())F=F+1 M=M*2+K end local G=Y[M]if G then return G end end end return C end local | |
function _(L)local U=L:read(4)local C=L:read(4)local M=L:read(5)local F=L:read(1)local W=L:read(2)local Y=C*16+U local P | |
=M+F*32+W*64 if U~=8 then o("unrecognized zlib compression method: "..U)end if C>7 then o( | |
"invalid zlib window size: cinfo="..C)end local V=2^(C+8)if(Y*256+P)%31~=0 then o("invalid zlib header (bad fcheck sum)" | |
)end if F==1 then o("FIX:TODO - FDICT not currently implemented")local B=L:read(32)end return V end local function E(L) | |
local U=L:read(5)local C=L:read(5)local M=b(L:read(4))local F=M+4 local W={}local Y={16,17,18,0,8,7,9,6,10,5,11,4,12,3, | |
13,2,14,1,15}for J=1,F do local X=L:read(3)local Z=Y[J]W[Z]=X end local P=z(W,true)local function V(J)local X={}local Z | |
local ee=0 while ee<J do local ea=P:read(L)local eo if ea<=15 then eo=1 Z=ea elseif ea==16 then eo=3+b(L:read(2))elseif | |
ea==17 then eo=3+b(L:read(3))Z=0 elseif ea==18 then eo=11+b(L:read(7))Z=0 else o'ASSERT'end for ei=1,eo do X[ee]=Z ee=ee | |
+1 end end local et=z(X,true)return et end local B=U+257 local G=C+1 local K=V(B)local Q=V(G)return K,Q end local T | |
local A local O local I local function N(L,U,C,M)local F=C:read(L)if F<256 then v(U,F)elseif F==256 then return true | |
else if not T then local X={[257]=3}local Z=1 for ee=258,285,4 do for et=ee,ee+3 do X[et]=X[et-1]+Z end if ee~=258 then | |
Z=Z*2 end end X[285]=258 T=X end if not A then local X={}for Z=257,285 do local ee=c(Z-261,0)X[Z]=y(ee,2)end X[285]=0 A= | |
X end local W=T[F]local Y=A[F]local P=L:read(Y)local V=W+P if not O then local X={[0]=1}local Z=1 for ee=1,29,2 do for | |
et=ee,ee+1 do X[et]=X[et-1]+Z end if ee~=1 then Z=Z*2 end end O=X end if not I then local X={}for Z=0,29 do local ee=c(Z | |
-2,0)X[Z]=y(ee,1)end I=X end local B=M:read(L)local G=O[B]local K=I[B]local Q=L:read(K)local J=G+Q for X=1,V do local Z=( | |
U.window_pos-1-J)%32768+1 v(U,a(U.window[Z],'invalid distance'))end end return false end local function S(L,U)local C=L: | |
read(1)local M=L:read(2)local F=0 local W=1 local Y=2 local P=3 if M==F then L:read(L:nbits_left_in_byte())local V=L: | |
read(16)local B=b(L:read(16))for G=1,V do local K=b(L:read(8))v(U,K)end elseif M==W or M==Y then local V,B if M==Y then | |
V,B=E(L)else V=z{0,8,144,9,256,7,280,8,288,nil}B=z{0,5,32,nil}end repeat local G=N(L,U,V,B)until G else o( | |
'unrecognized compression type '..M)end return C~=0 end local function H(L)local U=j(L.input)local C=x(L.output)local M= | |
p(C)repeat local F=S(U,M)until F end local function R(L,U)local C=U%65536 local M=(U-C)/65536 C=(C+L)%65521 M=(M+C)% | |
65521 return M*65536+C end local function D(L)local U=j(L.input)local C=x(L.output)local M=L.disable_crc if M==nil then | |
M=false end local F=_(U)local W=1 H{input=U,output=M and C or function(K)W=R(K,W)C(K)end,len=L.len}U:read(U: | |
nbits_left_in_byte())local Y=U:read(8)local P=U:read(8)local V=U:read(8)local B=U:read(8)local G=((Y*256+P)*256+V)*256+B | |
if not M then if W~=G then o('invalid compressed data--crc error')end end end return{inflate=H,inflate_zlib=D}end e[ | |
"clone"]=function(...)do local d={["metis.argparse"]="src/metis/argparse.lua",["metis.crypto.sha1"]= | |
"src/metis/crypto/sha1.lua",["metis.timer"]="src/metis/timer.lua"}package.loaders[#package.loaders+1]=function(l)local u | |
=d[l]if not u then return nil,"not a metis module"end local c="/.cache/metis/ae11085f261e5b506654162c80d21954c0d54e5e/" | |
..u if not fs.exists(c)then local w="https://raw.githubusercontent.com/SquidDev-CC/metis/ae11085f261e5b506654162c80d21954c0d54e5e/" | |
..u local y,p=http.get(w)if not y then return nil,"Cannot download "..w..": "..p end local v=fs.open(c,"w")v.write(y. | |
readAll())v.close()y.close()end local m,f=loadfile(c,nil,_ENV)if m then return m,c else return nil,f end end end local a | |
=t"network"local o=t"objects"local i,n=...if not i or i=="-h"or i=="--help"then error("clone.lua URL [name]",0)end if i: | |
sub(-1)=="/"then i=i:sub(1,-2)end n=n or fs.getName(i):gsub("%.git$","")local s=shell.resolve(n)if fs.exists(s)then | |
error(("%q already exists"):format(n),0)end local function h(d)local l=""for u in d:gmatch("[^\n]+")do l=u end term. | |
setCursorPos(1,select(2,term.getCursorPos()))term.clearLine()term.write(l)end local r do h("Cloning from "..i)local d=a. | |
force_fetch(i.."/info/refs?service=git-upload-pack")local l=a.receive(d)local u=("%x"):rep(40)local c={}local m={}for f= | |
1,#l do local w=l[f]if w~=false and w:sub(1,1)~="#"then local y,n=w:match("("..u..") ([^%z\n]+)")if y and n then m[n]=y | |
local p=w:match("%z([^\n]+)\n")if p then for v in(p.." "):gmatch("%S+")do local b=v:find("=")if b then c[v:sub(1,b-1)]=v | |
:sub(b+1)else c[v]=true end end end else printError("Unexpected line: "..w)end end end r=m['HEAD']or m[ | |
'refs/heads/master']or error("Cannot find master",0)if not c['shallow']then error( | |
"Server does not support shallow fetching",0)end if not c['side-band-64k']then error("Server does not support side band" | |
,0)end end do local d=a.force_fetch(i.."/git-upload-pack",{a.pkt_linef("want %s side-band-64k shallow",r),a.pkt_linef( | |
"deepen 1"),a.flush_line,a.pkt_linef("done")},"application/x-git-upload-pack-request")local l,r={},nil while true do | |
local m=a.read_pkt_line(d)if m==nil then break end if m==false or m=="NAK\n"then elseif m:byte(1)==1 then table.insert(l | |
,m:sub(2))elseif m:byte(1)==2 or m:byte(1)==3 then h(m:sub(2):gsub("\r","\n"))elseif m:find("^shallow ")then r=m:sub(#( | |
"shallow ")+1)else printError("Unknown line: "..tostring(m))end end d.close()local u=o.reader(table.concat(l))local c=o. | |
unpack(u,function(m,f)h(("Extracting %d/%d (%.2f%%)"):format(m,f,m/f*100))end)u.close()if not r then error( | |
"Cannot find HEAD commit",0)end for m,f in pairs(o.build_commit(c,r))do local w=fs.open(fs.combine(s,fs.combine(m,"")), | |
"wb")w.write(f)w.close()end end h(("Cloned to %q"):format(n))print()end return e["clone"](...) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment