char/sorcery
static-files based git repo viewer
git clone https://git.t4t.associates/char/sorcery
42f80d8
main
1import type { Commit , GitObject , Identity , ObjectType , Tag , TreeEntry } from "./types.ts" ; 2 3export async function inflate ( data :Uint8Array ) :Promise < Uint8Array > { 4const stream = new Blob ([ data as BlobPart ]). stream (). pipeThrough ( new DecompressionStream ( "deflate" )); 5return new Uint8Array ( await new Response ( stream ). arrayBuffer ()); 6} 7 8const utf8 = new TextDecoder (); 9 10export function toHex ( bytes :Uint8Array ) :string { 11return Array . from ( bytes , b => b . toString ( 16 ). padStart ( 2 , "0" )). join ( "" ); 12} 13 14export function oidBytes ( oid :string ) :Uint8Array { 15if ( ! /^(?:[0-9a-f]{40}|[0-9a-f]{64})$ / . test ( oid )) throw new Error ( `invalid object id ${ oid } ` ); 16return new Uint8Array ( oid . match ( / .. / g ) ! . map ( byte => parseInt ( byte , 16 ))); 17} 18 19/** `<type> <size>\0<content>`, as stored zlib-compressed in `objects/xx/` */ 20export function parseLoose ( raw :Uint8Array ) :GitObject { 21const nul = raw . indexOf ( 0 ); 22if ( nul === - 1 ) throw new Error ( "malformed loose object" ); 23const [ type , size ] = utf8 . decode ( raw . subarray ( 0 , nul )). split ( " " ); 24const data = raw . subarray ( nul + 1 ); 25if ( data . length !== Number ( size )) throw new Error ( "loose object size mismatch" ); 26return { type :type as ObjectType , data}; 27} 28 29function parseIdentity ( line :string ) :Identity { 30const lt = line . indexOf ( " <" ); 31const gt = line . indexOf ( "> " , lt ); 32const [ time , tz ] = line . slice ( gt + 2 ). split ( " " ); 33return { 34name :line . slice ( 0 , lt ), 35line . slice ( lt + 2 , gt ), 36time :Number ( time ), 37tz :tz ?? "+0000" , 38}; 39} 40 41export function parseCommit ( oid :string , data :Uint8Array ) :Commit { 42const text = utf8 . decode ( data ); 43const blank = text . indexOf ( "\n\n" ); 44const head = blank === - 1 ?text :text . slice ( 0 , blank ); 45const message = blank === - 1 ?"" :text . slice ( blank + 2 ); 46 47const commit :Commit = { 48 oid, 49tree :"" , 50parents :[], 51author :{ name :"" , "" , time :0 , tz :"+0000" }, 52committer :{ name :"" , "" , time :0 , tz :"+0000" }, 53 message, 54}; 55// continuation lines (multi-line headers like gpgsig) start with a space 56for ( const line of head . split ( "\n" ). filter ( l => ! l . startsWith ( " " ))) { 57const sp = line . indexOf ( " " ); 58const key = line . slice ( 0 , sp ); 59const value = line . slice ( sp + 1 ); 60if ( key === "tree" ) commit . tree = value ; 61else if ( key === "parent" ) commit . parents . push ( value ); 62else if ( key === "author" ) commit . author = parseIdentity ( value ); 63else if ( key === "committer" ) commit . committer = parseIdentity ( value ); 64else if ( key === "change-id" ) commit . changeId = value ; 65} 66return commit ; 67} 68 69/** entries of `<octal mode> <name>\0<raw oid>` */ 70export function parseTree ( data :Uint8Array , hashBytes :number ) :TreeEntry [] { 71if ( hashBytes !== 20 && hashBytes !== 32 ) throw new Error ( "unsupported object format" ); 72const entries :TreeEntry [] = []; 73let pos = 0 ; 74while ( pos < data . length ) { 75const sp = data . indexOf ( 0x20 , pos ); 76if ( sp === - 1 ) throw new Error ( "malformed tree object" ); 77const nul = data . indexOf ( 0 , sp ); 78if ( nul === - 1 || nul + 1 + hashBytes > data . length ) throw new Error ( "malformed tree object" ); 79entries . push ({ 80mode :parseInt ( utf8 . decode ( data . subarray ( pos , sp )), 8 ), 81name :utf8 . decode ( data . subarray ( sp + 1 , nul )), 82oid :toHex ( data . subarray ( nul + 1 , nul + 1 + hashBytes )), 83}); 84pos = nul + 1 + hashBytes ; 85} 86return entries ; 87} 88 89export function parseTag ( data :Uint8Array ) :Tag { 90const text = utf8 . decode ( data ); 91const blank = text . indexOf ( "\n\n" ); 92const tag :Tag = { object :"" , targetType :"commit" , name :"" , message :text . slice ( blank + 2 ) }; 93for ( const line of text . slice ( 0 , blank ). split ( "\n" )) { 94const sp = line . indexOf ( " " ); 95const [ key , value ] = [ line . slice ( 0 , sp ), line . slice ( sp + 1 )]; 96if ( key === "object" ) tag . object = value ; 97else if ( key === "type" ) tag . targetType = value as ObjectType ; 98else if ( key === "tag" ) tag . name = value ; 99} 100return tag ; 101}