214 |
Author: Edward Langley
215 |
Created: 2023-10-31 Tue 03:20
216 |
Validate
217 |
218 |
219 |
--------------------------------------------------------------------------------
/pack.lisp:
--------------------------------------------------------------------------------
1 | (in-package :fwoar.cl-git.pack)
2 |
3 | (defclass pack ()
4 | ((%pack :initarg :pack :reader pack-file)
5 | (%index :initarg :index :reader index-file)
6 | (%repository :initarg :repository :reader fwoar.cl-git:repository)))
7 | (defun pack (index pack repository)
8 | (fw.lu:new 'pack index pack repository))
9 |
10 | (defclass packed-ref (fwoar.cl-git.ref:ref)
11 | ((%pack :initarg :pack :reader packed-ref-pack)
12 | (%offset :initarg :offset :reader packed-ref-offset)))
13 |
14 | (defmacro with-pack-streams ((idx-sym pack-sym) pack &body body)
15 | (alexandria:once-only (pack)
16 | `(with-open-file (,idx-sym (index-file ,pack) :element-type 'fwoar.cl-git.types:octet)
17 | (with-open-file (,pack-sym (pack-file ,pack) :element-type 'fwoar.cl-git.types:octet)
18 | ,@body))))
19 |
20 | (defgeneric idx-toc (pack)
21 | (:method ((pack pack))
22 | (with-pack-streams (idx-stream _) pack
23 | (let* ((object-count (progn (file-position idx-stream 1028)
24 | (let ((buf (make-array 4)))
25 | (read-sequence buf idx-stream)
26 | (fwoar.bin-parser:be->int buf))))
27 | (signature 0)
28 | (version 4)
29 | (fanout 8)
30 | (shas (+ fanout
31 | #.(* 4 256)))
32 | (packed-crcs (+ shas
33 | (* 20 object-count)))
34 | (4-byte-offsets (+ packed-crcs
35 | (* 4 object-count)))
36 | (8-byte-offsets-pro (+ 4-byte-offsets
37 | (* object-count 4)))
38 | (pack-sha (- (file-length idx-stream)
39 | 40))
40 | (8-byte-offsets (when (/= 8-byte-offsets-pro pack-sha)
41 | 8-byte-offsets-pro))
42 | (idx-sha (- (file-length idx-stream)
43 | 20)))
44 | (values (fwoar.cl-git.utils:sym->plist signature
45 | version
46 | fanout
47 | shas
48 | packed-crcs
49 | 4-byte-offsets
50 | 8-byte-offsets
51 | pack-sha
52 | idx-sha)
53 | object-count)))))
54 |
55 | (defun edges-in-fanout (toc s sha)
56 | (let* ((fanout-offset (getf toc :fanout)))
57 | (file-position s (+ fanout-offset (* 4 (1- (elt sha 0)))))
58 | (destructuring-bind ((_ . cur) (__ . next))
59 | (fwoar.bin-parser:extract '((cur 4 fwoar.bin-parser:be->int)
60 | (next 4 fwoar.bin-parser:be->int))
61 | s)
62 | (declare (ignore _ __))
63 | (values cur next))))
64 |
65 | (defun extract-object-at-pos (pack pos ref)
66 | (with-open-file (p (fwoar.cl-git.pack:pack-file pack) :element-type '(unsigned-byte 8))
67 | (file-position p pos)
68 | (read-object-from-pack p
69 | (fwoar.cl-git:repository pack)
70 | ref)))
71 |
72 | (defun extract-object-from-pack (pack obj-number ref)
73 | (let ((object-offset-in-pack (read-4-byte-offset pack obj-number)))
74 | (extract-object-at-pos pack
75 | object-offset-in-pack
76 | ref)))
77 |
78 | (defun find-object-in-pack-files (repo id)
79 | (dolist (pack-file (fwoar.cl-git::pack-files repo))
80 | (multiple-value-bind (pack mid sha) (find-sha-in-pack pack-file id)
81 | (when pack
82 | (return-from find-object-in-pack-files
83 | (values pack mid sha))))))
84 |
85 | (defun find-sha-between-terms (toc s start end sha)
86 | (unless (>= start end)
87 | (let* ((sha-offset (getf toc :shas))
88 | (mid (floor (+ start end)
89 | 2)))
90 | (file-position s (+ sha-offset (* 20 mid)))
91 | (let ((sha-at-mid (fwoar.cl-git.utils:read-bytes
92 | 20 'fwoar.bin-parser:byte-array-to-hex-string s)))
93 | (cond ((serapeum:string-prefix-p sha sha-at-mid)
94 | (values mid sha-at-mid))
95 | ((string< sha sha-at-mid)
96 | (find-sha-between-terms toc s start mid sha))
97 | ((string> sha sha-at-mid)
98 | (find-sha-between-terms toc s (1+ mid) end sha))
99 | (t (values mid sha-at-mid)))))))
100 |
101 | (defun find-sha-in-pack (pack-file id)
102 | (with-open-file (s (fwoar.cl-git.pack:index-file pack-file)
103 | :element-type '(unsigned-byte 8))
104 | (let ((binary-sha (ironclad:hex-string-to-byte-array id))
105 | (toc (fwoar.cl-git.pack:idx-toc pack-file)))
106 | (multiple-value-bind (_ end) (edges-in-fanout toc s binary-sha)
107 | (declare (ignore _))
108 | (multiple-value-bind (midpoint sha)
109 | (find-sha-between-terms toc s 0 end id)
110 | (and midpoint
111 | (values pack-file
112 | midpoint
113 | sha)))))))
114 |
115 | (defun get-object-from-pack (s)
116 | (let* ((metadata (fwoar.bin-parser:extract-high s))
117 | (type (fwoar.cl-git::object-type->sym (fwoar.cl-git::get-object-type metadata)))
118 | (size (fwoar.cl-git::get-object-size metadata)))
119 | (case type
120 | (:ref-delta (error ":ref-delta not implemented yet"))
121 | (:ofs-delta (get-ofs-delta-offset-streaming s)))
122 | (let ((decompressed (chipz:decompress nil (chipz:make-dstate 'chipz:zlib) s)))
123 | (values (concatenate
124 | '(vector fwoar.cl-git.types:octet)
125 | (ecase type
126 | (:commit #.(babel:string-to-octets "commit" :encoding :ascii))
127 | (:blob #.(babel:string-to-octets "blob" :encoding :ascii))
128 | (:tree #.(babel:string-to-octets "tree" :encoding :ascii)))
129 | #(32)
130 | (babel:string-to-octets (prin1-to-string size ):encoding :ascii)
131 | #(0)
132 | decompressed)
133 | size
134 | (length decompressed)))))
135 |
136 | (defun get-ofs-delta-offset-streaming (buf)
137 | (let* ((idx 0))
138 | (flet ((advance ()
139 | (read-byte buf)))
140 | (loop
141 | for c = (advance)
142 | for ofs = (logand c 127) then (+ (ash (1+ ofs)
143 | 7)
144 | (logand c 127))
145 | while (> (logand c 128) 0)
146 | finally
147 | (return (values (- ofs) idx))))))
148 |
149 | (defun pack-offset-for-object (index-file obj-number)
150 | (let ((offset-offset (getf index-file
151 | :4-byte-offsets)))
152 | (+ offset-offset
153 | (* 4 obj-number))))
154 |
155 | (defun packed-ref (repo id)
156 | (multiple-value-bind (pack offset sha) (find-object-in-pack-files repo id)
157 | (when pack
158 | (make-instance 'packed-ref
159 | :hash sha
160 | :repo repo
161 | :offset offset
162 | :pack pack))))
163 |
164 | (defun raw-object-for-ref (packed-ref)
165 | (let ((pack (packed-ref-pack packed-ref)))
166 | (fwoar.cl-git.pack:with-pack-streams (i p) pack
167 | (file-position p (read-4-byte-offset pack
168 | (packed-ref-offset packed-ref)))
169 | (get-object-from-pack p))))
170 |
171 | (defun read-4-byte-offset (pack obj-number)
172 | (fwoar.cl-git.pack:with-pack-streams (s _) pack
173 | (file-position s
174 | (pack-offset-for-object (fwoar.cl-git.pack:idx-toc pack)
175 | obj-number))
176 | (fwoar.cl-git.utils:read-bytes 4 'fwoar.bin-parser:be->int s)))
177 |
178 | (defun read-object-from-pack (s repository ref)
179 | (let* ((pos (file-position s))
180 | (metadata (fwoar.bin-parser:extract-high s))
181 | (type (fwoar.cl-git::object-type->sym (fwoar.cl-git::get-object-type metadata)))
182 | (size (fwoar.cl-git::get-object-size metadata))
183 | (delta-base (case type
184 | (:ref-delta (error ":ref-delta not implemented yet"))
185 | (:ofs-delta (fwoar.cl-git.delta::get-ofs-delta-offset-streaming s))))
186 | (decompressed (chipz:decompress nil (chipz:make-dstate 'chipz:zlib) s))
187 | (object-data (fwoar.cl-git::extract-object-of-type type decompressed repository pos (pathname s) ref delta-base)))
188 | (list (cons :type (fwoar.cl-git::object-type->sym type))
189 | (cons :decompressed-size size)
190 | (cons :object-data object-data)
191 | (cons :raw-data decompressed))))
192 |
193 | (defun seek-to-object-in-pack (pack idx-stream pack-stream obj-number)
194 | (let* ((toc (idx-toc pack))
195 | (offset-offset (getf toc :4-byte-offsets)))
196 | (file-position idx-stream (+ offset-offset (* 4 obj-number)))
197 | (let ((object-offset-in-pack (fwoar.cl-git.utils:read-bytes
198 | 4 'fwoar.bin-parser:be->int idx-stream)))
199 | (values (file-position pack-stream object-offset-in-pack)
200 | object-offset-in-pack))))
201 |
202 | (defparameter *want-delta* nil)
203 | (defmethod fwoar.cl-git::extract-object ((object packed-ref))
204 | (let ((maybe-delta (data-lens.lenses:view fwoar.cl-git::*object-data-lens*
205 | (extract-object-from-pack
206 | (fwoar.cl-git.pack::packed-ref-pack object)
207 | (fwoar.cl-git.pack::packed-ref-offset object)
208 | object))))
209 | (if *want-delta*
210 | maybe-delta
211 | (fwoar.cl-git.delta:resolve-delta object
212 | maybe-delta))))
213 |
--------------------------------------------------------------------------------
/tests/git-objects.lisp:
--------------------------------------------------------------------------------
1 | (defpackage :fwoar.cl-git.git-objects
2 | (:use :cl :fwoar.cl-git.protocol)
3 | (:export ))
4 | (in-package :fwoar.cl-git.git-objects)
5 |
6 | (defparameter *fake-repo* :fwoar.cl-git.git-objects)
7 | (fiveam:def-suite :fwoar.cl-git.git-objects
8 | :description "testing branch resolution"
9 | :in :fwoar.cl-git)
10 | (fiveam:in-suite :fwoar.cl-git.git-objects)
11 |
12 | (defclass fake-ref (fwoar.cl-git.ref:ref)
13 | ())
14 | (defun fake-ref (repo hash)
15 | (fwoar.lisputils:new 'fake-ref repo hash))
16 |
17 | (defmethod fwoar.cl-git:ref ((repo (eql *fake-repo*)) hash)
18 | (fake-ref repo hash))
19 |
20 |
21 | (fiveam:def-test basic-commit ()
22 | (let ((fwoar.cl-git:*git-repository* *fake-repo*)
23 | (object (fwoar.cl-git::extract-loose-object
24 | nil
25 | (asdf:system-relative-pathname
26 | :co.fwoar.cl-git
27 | "tests/sample-git-objects/hello-world-commit.git-obj")
28 | (make-instance 'fake-ref :hash "the-hash"))))
29 | (5am:is (typep object 'fwoar.cl-git:git-commit))
30 | (5am:is (equal "hello, git!
31 | "
32 | (component :message object)))
33 | (5am:is (equal ()
34 | (component :parents object)))
35 | (5am:is (equal "L Edgley