Browse code
feat: split up giant files, introduce the "component" abstraction
Ed Langley authored on 08/05/2020 07:16:26
Showing 9 changed files
Showing 9 changed files
- cl-git.asd
- commit.lisp
- extract.lisp
- model.lisp
- package.lisp
- protocol.lisp
- repository.lisp
- tree.lisp
- util.lisp
... | ... |
@@ -1,25 +1,34 @@ |
1 | 1 |
;;; -*- Mode:Lisp; Syntax:ANSI-Common-Lisp; Package: ASDF-USER -*- |
2 | 2 |
(in-package :asdf-user) |
3 | 3 |
|
4 |
-(defsystem :cl-git |
|
5 |
- :description "" |
|
6 |
- :author "Ed L <edward@elangley.org>" |
|
7 |
- :license "MIT" |
|
8 |
- :pathname #-fw.dev nil #+fw.dev #p"PROJECTS:cl-git;" |
|
9 |
- :depends-on (:alexandria |
|
10 |
- :chipz |
|
11 |
- :cl-dot |
|
12 |
- :data-lens |
|
13 |
- :fwoar-lisputils |
|
14 |
- :fwoar-lisputils/bin-parser |
|
15 |
- :ironclad |
|
16 |
- :serapeum |
|
17 |
- :split-sequence |
|
18 |
- :uiop) |
|
19 |
- :components ((:file "package") |
|
20 |
- (:file "util" :depends-on ("package")) |
|
21 |
- (:file "model" :depends-on ("package")) |
|
22 |
- (:file "extract" :depends-on ("package")) |
|
23 |
- (:file "branch" :depends-on ("package" "extract")) |
|
24 |
- (:file "git" :depends-on ("package" "util" "model" "branch")) |
|
25 |
- (:file "porcelain" :depends-on ("package" "git")))) |
|
4 |
+(defsystem :cl-git |
|
5 |
+ :description "A pure-Lisp git implementation" |
|
6 |
+ :author "Ed L <edward@elangley.org>" |
|
7 |
+ :license "MIT" |
|
8 |
+ :pathname #-fw.dev nil #+fw.dev #p"PROJECTS:cl-git;" |
|
9 |
+ :depends-on (:alexandria |
|
10 |
+ :chipz |
|
11 |
+ :cl-dot |
|
12 |
+ :data-lens |
|
13 |
+ :fwoar-lisputils |
|
14 |
+ :fwoar-lisputils/bin-parser |
|
15 |
+ :ironclad |
|
16 |
+ :serapeum |
|
17 |
+ :split-sequence |
|
18 |
+ :uiop) |
|
19 |
+ :components ((:file "package") |
|
20 |
+ (:file "util" :depends-on ("package")) |
|
21 |
+ |
|
22 |
+ ;; data model |
|
23 |
+ (:file "model" :depends-on ("package")) |
|
24 |
+ (:file "protocol" :depends-on ("package" "model")) |
|
25 |
+ (:file "repository" :depends-on ("package" "model")) |
|
26 |
+ (:file "tree" :depends-on ("package" "model")) |
|
27 |
+ (:file "commit" :depends-on ("package" "model")) |
|
28 |
+ |
|
29 |
+ (:file "extract" :depends-on ("package" "commit" "tree")) |
|
30 |
+ (:file "branch" :depends-on ("package" "extract")) |
|
31 |
+ (:file "git" :depends-on ("package" "util" "model" "branch")) |
|
32 |
+ |
|
33 |
+ ;; stable programmer interface |
|
34 |
+ (:file "porcelain" :depends-on ("package" "git" "commit")))) |
26 | 35 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,39 @@ |
1 |
+(in-package :fwoar.cl-git) |
|
2 |
+ |
|
3 |
+(defclass git-commit () |
|
4 |
+ ((%metadata :initarg :metadata :reader metadata) |
|
5 |
+ (%data :initarg :data :reader data))) |
|
6 |
+ |
|
7 |
+(defun git-commit (metadata data) |
|
8 |
+ (fw.lu:new 'git-commit metadata data)) |
|
9 |
+ |
|
10 |
+(defun parse-commit (commit) |
|
11 |
+ (destructuring-bind (metadata message) |
|
12 |
+ (partition-subseq #(#\newline #\newline) |
|
13 |
+ commit #+(or)(babel:octets-to-string commit :encoding :latin1)) |
|
14 |
+ (values message |
|
15 |
+ (map 'vector (serapeum:op (partition #\space _)) |
|
16 |
+ (fwoar.string-utils:split #\newline metadata))))) |
|
17 |
+ |
|
18 |
+(defun make-commit (data) |
|
19 |
+ (multiple-value-bind (message metadata) |
|
20 |
+ (parse-commit data) |
|
21 |
+ (git-commit metadata message))) |
|
22 |
+ |
|
23 |
+(defmethod -extract-object-of-type ((type (eql :commit)) s repository &key) |
|
24 |
+ (make-commit (babel:octets-to-string s :encoding *git-encoding*))) |
|
25 |
+ |
|
26 |
+ |
|
27 |
+(defmethod component ((component (eql :tree)) (object git-commit)) |
|
28 |
+ (ensure-ref |
|
29 |
+ (cadr |
|
30 |
+ (fw.lu:v-assoc :tree (metadata object) |
|
31 |
+ :test 'string-equal)))) |
|
32 |
+(defmethod component ((component (eql :parents)) (object git-commit)) |
|
33 |
+ (coerce (remove-if-not (serapeum:op |
|
34 |
+ (string= "parent" _)) |
|
35 |
+ (metadata object) |
|
36 |
+ :key #'car) |
|
37 |
+ 'list)) |
|
38 |
+(defmethod component ((component (eql :message)) (object git-commit)) |
|
39 |
+ (data object)) |
... | ... |
@@ -42,69 +42,29 @@ |
42 | 42 |
(return-from find-object-in-pack-files |
43 | 43 |
(values pack mid)))))) |
44 | 44 |
|
45 |
-(defun behead (data) |
|
46 |
- (elt (partition 0 data) |
|
47 |
- 1)) |
|
48 |
- |
|
49 |
-(defun tree-entry (data) |
|
50 |
- (values-list (partition 0 data :with-offset 20))) |
|
51 |
- |
|
52 |
-(defun format-tree-entry (entry) |
|
53 |
- (destructuring-bind (info sha) (partition 0 entry) |
|
54 |
- (concatenate 'vector |
|
55 |
- (apply #'concatenate 'vector |
|
56 |
- (serapeum:intersperse (vector (char-code #\tab)) |
|
57 |
- (reverse |
|
58 |
- (partition (char-code #\space) |
|
59 |
- info)))) |
|
60 |
- (list (char-code #\tab)) |
|
61 |
- (babel:string-to-octets (elt (->sha-string sha) 0) :encoding *git-encoding*)))) |
|
62 |
- |
|
63 |
-(defun tree-entries (data &optional accum) |
|
64 |
- (if (<= (length data) 0) |
|
65 |
- (apply #'concatenate 'vector |
|
66 |
- (serapeum:intersperse (vector (char-code #\newline)) |
|
67 |
- (nreverse accum))) |
|
68 |
- (multiple-value-bind (next rest) (tree-entry data) |
|
69 |
- (tree-entries rest |
|
70 |
- (list* (format-tree-entry next) |
|
71 |
- accum))))) |
|
72 |
- |
|
73 |
-(defun extract-object-of-type (type s repository) |
|
74 |
- (with-simple-restart (continue "Skip object of type ~s" type) |
|
75 |
- (%extract-object-of-type type s repository))) |
|
76 |
- |
|
77 |
-(defgeneric %extract-object-of-type (type s repository) |
|
78 |
- (:method ((type integer) s repository) |
|
79 |
- (extract-object-of-type (object-type->sym type) |
|
80 |
- s |
|
81 |
- repository)) |
|
82 |
- |
|
83 |
- (:method ((type (eql :commit)) s repository) |
|
84 |
- s) |
|
85 |
- |
|
86 |
- (:method ((type (eql :blob)) s repository) |
|
87 |
- s) |
|
88 |
- |
|
89 |
- (:method ((type (eql :tag)) s repository) |
|
90 |
- s) |
|
91 |
- |
|
92 |
- (:method ((type (eql :tree)) s repository) |
|
93 |
- (tree-entries s))) |
|
94 |
- |
|
95 | 45 |
(defun read-object-from-pack (s repository) |
96 |
- (let* ((metadata (fwoar.bin-parser:extract-high s)) |
|
46 |
+ (let* ((pos (file-position s)) |
|
47 |
+ (metadata (fwoar.bin-parser:extract-high s)) |
|
97 | 48 |
(type (object-type->sym (get-object-type metadata))) |
98 | 49 |
(size (get-object-size metadata)) |
99 | 50 |
(decompressed (if (member type '(:ofs-delta :ref-delta)) |
100 |
- s |
|
51 |
+ (let ((buffer (make-array size :element-type '(unsigned-byte 8)))) |
|
52 |
+ (read-sequence buffer s) |
|
53 |
+ buffer) |
|
101 | 54 |
(chipz:decompress nil (chipz:make-dstate 'chipz:zlib) s))) |
102 |
- (object-data (extract-object-of-type type decompressed repository))) |
|
55 |
+ (object-data (extract-object-of-type type decompressed repository pos))) |
|
103 | 56 |
(list (cons :type (object-type->sym type)) |
104 | 57 |
(cons :decompressed-size size) |
105 | 58 |
(cons :object-data object-data) |
106 | 59 |
(cons :raw-data object-data)))) |
107 | 60 |
|
61 |
+(defun extract-object-of-type (type s repository pos) |
|
62 |
+ (with-simple-restart (continue "Skip object of type ~s" type) |
|
63 |
+ (-extract-object-of-type (object-type->sym type) |
|
64 |
+ s |
|
65 |
+ repository |
|
66 |
+ :offset-from pos))) |
|
67 |
+ |
|
108 | 68 |
(defun extract-object-from-pack (pack obj-number) |
109 | 69 |
(with-open-file (s (index-file pack) :element-type '(unsigned-byte 8)) |
110 | 70 |
(with-open-file (p (pack-file pack) :element-type '(unsigned-byte 8)) |
... | ... |
@@ -115,20 +75,6 @@ |
115 | 75 |
(file-position p object-offset-in-pack) |
116 | 76 |
(read-object-from-pack p (repository pack))))))) |
117 | 77 |
|
118 |
-(defun root-of (repo) |
|
119 |
- (typecase repo |
|
120 |
- (repository (root repo)) |
|
121 |
- ((or pathname string) (namestring |
|
122 |
- (truename repo))))) |
|
123 |
- |
|
124 |
-(defun object (repo id) |
|
125 |
- (let ((repo-root (root-of repo))) |
|
126 |
- (or (alexandria:when-let ((object-file (loose-object repo id))) |
|
127 |
- (make-instance 'loose-object :repo repo-root :hash id :file object-file)) |
|
128 |
- (multiple-value-bind (pack offset) (find-object-in-pack-files repo-root id) |
|
129 |
- (when pack |
|
130 |
- (make-instance 'packed-object :hash id :repo repo-root :offset offset :pack pack)))))) |
|
131 |
- |
|
132 | 78 |
(defun extract-loose-object (repo file) |
133 | 79 |
(with-open-file (s file :element-type '(unsigned-byte 8)) |
134 | 80 |
(alexandria:when-let ((result (chipz:decompress nil (chipz:make-dstate 'chipz:zlib) |
... | ... |
@@ -138,20 +84,14 @@ |
138 | 84 |
(extract-object-of-type (object-type->sym (babel:octets-to-string type)) |
139 | 85 |
(elt (partition 0 rest) |
140 | 86 |
1) |
141 |
- repo))))) |
|
142 |
- |
|
143 |
-(defgeneric extract-object-next (object) |
|
144 |
- (:method ((object loose-object)) |
|
145 |
- (extract-loose-object (object-repo object) |
|
146 |
- (loose-object-file object))) |
|
147 |
- (:method ((object packed-object)) |
|
87 |
+ repo |
|
88 |
+ 0))))) |
|
89 |
+ |
|
90 |
+(defgeneric extract-object (object) |
|
91 |
+ (:method ((object loose-ref)) |
|
92 |
+ (extract-loose-object (ref-repo object) |
|
93 |
+ (loose-ref-file object))) |
|
94 |
+ (:method ((object packed-ref)) |
|
148 | 95 |
(data-lens.lenses:view *object-data-lens* |
149 |
- (extract-object-from-pack (packed-object-pack object) |
|
150 |
- (packed-object-offset object))))) |
|
151 |
- |
|
152 |
-(defun extract-object (repo id) |
|
153 |
- (if (loose-object-p repo id) |
|
154 |
- (extract-loose-object repo (loose-object repo id)) |
|
155 |
- (data-lens.lenses:view *object-data-lens* |
|
156 |
- (multiple-value-call 'extract-object-from-pack |
|
157 |
- (find-object-in-pack-files (root repo) id))))) |
|
96 |
+ (extract-object-from-pack (packed-ref-pack object) |
|
97 |
+ (packed-ref-offset object))))) |
... | ... |
@@ -14,9 +14,6 @@ |
14 | 14 |
(defclass git-object () |
15 | 15 |
()) |
16 | 16 |
|
17 |
-(defclass commit (git-object) |
|
18 |
- ()) |
|
19 |
- |
|
20 | 17 |
(defgeneric object-type->sym (object-type) |
21 | 18 |
(:method ((o-t symbol)) |
22 | 19 |
o-t)) |
... | ... |
@@ -29,6 +26,7 @@ |
29 | 26 |
(4 :tag) |
30 | 27 |
(6 :ofs-delta) |
31 | 28 |
(7 :ref-delta))) |
29 |
+ |
|
32 | 30 |
(defmethod object-type->sym ((object-type string)) |
33 | 31 |
(string-case:string-case ((string-downcase object-type)) |
34 | 32 |
("commit" :commit) |
... | ... |
@@ -82,19 +80,19 @@ |
82 | 80 |
"Is ID an ID of a loose object?" |
83 | 81 |
(loose-object repository id)) |
84 | 82 |
|
85 |
-(defclass git-object () |
|
86 |
- ((%repo :initarg :repo :reader object-repo) |
|
87 |
- (%hash :initarg :hash :reader object-hash))) |
|
88 |
-(defclass loose-object (git-object) |
|
89 |
- ((%file :initarg :file :reader loose-object-file))) |
|
90 |
-(defclass packed-object (git-object) |
|
91 |
- ((%pack :initarg :pack :reader packed-object-pack) |
|
92 |
- (%offset :initarg :offset :reader packed-object-offset))) |
|
93 |
- |
|
94 |
-(defmethod print-object ((obj git-object) s) |
|
83 |
+(defclass git-ref () |
|
84 |
+ ((%repo :initarg :repo :reader ref-repo) |
|
85 |
+ (%hash :initarg :hash :reader ref-hash))) |
|
86 |
+(defclass loose-ref (git-ref) |
|
87 |
+ ((%file :initarg :file :reader loose-ref-file))) |
|
88 |
+(defclass packed-ref (git-ref) |
|
89 |
+ ((%pack :initarg :pack :reader packed-ref-pack) |
|
90 |
+ (%offset :initarg :offset :reader packed-ref-offset))) |
|
91 |
+ |
|
92 |
+(defmethod print-object ((obj git-ref) s) |
|
95 | 93 |
(print-unreadable-object (obj s :type t) |
96 | 94 |
(format s "~a of ~a" |
97 |
- (subseq (object-hash obj) 0 7) |
|
95 |
+ (subseq (ref-hash obj) 0 7) |
|
98 | 96 |
(serapeum:string-replace (namestring (user-homedir-pathname)) |
99 |
- (root-of (object-repo obj)) |
|
97 |
+ (root-of (ref-repo obj)) |
|
100 | 98 |
"~/")))) |
... | ... |
@@ -10,4 +10,5 @@ |
10 | 10 |
(defpackage :git |
11 | 11 |
(:use) |
12 | 12 |
(:export #:show #:branch #:branches #:commit-parents #:in-repository |
13 |
- #:current-repository #:show-repository #:git #:tree #:contents)) |
|
13 |
+ #:with-repository #:current-repository #:show-repository #:git |
|
14 |
+ #:tree #:contents #:component)) |
14 | 15 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,18 @@ |
1 |
+(in-package :fwoar.cl-git) |
|
2 |
+ |
|
3 |
+(defgeneric -extract-object-of-type (type s repository &key &allow-other-keys) |
|
4 |
+ (:method ((type (eql :blob)) s repository &key) |
|
5 |
+ s) |
|
6 |
+ |
|
7 |
+ (:method ((type (eql :tag)) s repository &key) |
|
8 |
+ s)) |
|
9 |
+ |
|
10 |
+(defgeneric component (component object) |
|
11 |
+ (:argument-precedence-order object component) |
|
12 |
+ (:method (component (object git-ref)) |
|
13 |
+ (component component (extract-object object))) |
|
14 |
+ (:method ((component sequence) object) |
|
15 |
+ (reduce (lambda (cur next) |
|
16 |
+ (component next cur)) |
|
17 |
+ component |
|
18 |
+ :initial-value object))) |
0 | 19 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,22 @@ |
1 |
+(in-package :fwoar.cl-git) |
|
2 |
+ |
|
3 |
+(defun root-of (repo) |
|
4 |
+ (typecase repo |
|
5 |
+ (repository (root repo)) |
|
6 |
+ ((or pathname string) (namestring |
|
7 |
+ (truename repo))))) |
|
8 |
+ |
|
9 |
+(defun ref (repo id) |
|
10 |
+ (let ((repo-root (root-of repo))) |
|
11 |
+ (or (alexandria:when-let ((object-file (loose-object repo id))) |
|
12 |
+ (make-instance 'loose-ref |
|
13 |
+ :repo repo-root |
|
14 |
+ :hash id |
|
15 |
+ :file object-file)) |
|
16 |
+ (multiple-value-bind (pack offset) (find-object-in-pack-files repo-root id) |
|
17 |
+ (when pack |
|
18 |
+ (make-instance 'packed-ref |
|
19 |
+ :hash id |
|
20 |
+ :repo repo-root |
|
21 |
+ :offset offset |
|
22 |
+ :pack pack)))))) |
0 | 23 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,66 @@ |
1 |
+(in-package :fwoar.cl-git) |
|
2 |
+ |
|
3 |
+(defclass git-tree (git-object) |
|
4 |
+ ((%entries :initarg :entries :reader entries))) |
|
5 |
+ |
|
6 |
+(defun git-tree (entries) |
|
7 |
+ (fw.lu:new 'git-tree entries)) |
|
8 |
+ |
|
9 |
+(defclass tree-entry () |
|
10 |
+ ((%mode :initarg :mode :reader te-mode) |
|
11 |
+ (%name :initarg :name :reader te-name) |
|
12 |
+ (%hash :initarg :hash :reader te-hash))) |
|
13 |
+ |
|
14 |
+(defun tree-entry (name mode hash) |
|
15 |
+ (fw.lu:new 'tree-entry name mode hash)) |
|
16 |
+ |
|
17 |
+(defmethod print-object ((o tree-entry) s) |
|
18 |
+ (if *print-readably* |
|
19 |
+ (format s "#.(~s ~s ~s ~s)" |
|
20 |
+ 'tree-entry |
|
21 |
+ (te-name o) |
|
22 |
+ (te-mode o) |
|
23 |
+ (te-hash o)) |
|
24 |
+ (print-unreadable-object (o s :type t :identity t) |
|
25 |
+ (format s "(~a: ~a)" |
|
26 |
+ (te-name o) |
|
27 |
+ (subseq (te-hash o) 0 7))))) |
|
28 |
+ |
|
29 |
+(defun parse-tree-entry (data) |
|
30 |
+ (values-list (partition 0 data :with-offset 20))) |
|
31 |
+ |
|
32 |
+(defun format-tree-entry (entry) |
|
33 |
+ (destructuring-bind (info sha) (partition 0 entry) |
|
34 |
+ (destructuring-bind (mode name) (partition #\space |
|
35 |
+ (babel:octets-to-string info :encoding *git-encoding*)) |
|
36 |
+ (tree-entry name mode (elt (->sha-string sha) 0))))) |
|
37 |
+ |
|
38 |
+(defun tree-entries (data &optional accum) |
|
39 |
+ (if (<= (length data) 0) |
|
40 |
+ (nreverse accum) |
|
41 |
+ (multiple-value-bind (next rest) (parse-tree-entry data) |
|
42 |
+ (tree-entries rest |
|
43 |
+ (list* (format-tree-entry next) |
|
44 |
+ accum))))) |
|
45 |
+ |
|
46 |
+(defmethod -extract-object-of-type ((type (eql :tree)) s repository &key) |
|
47 |
+ (git-tree (tree-entries s))) |
|
48 |
+ |
|
49 |
+(defmethod component ((component (eql :entries)) (object git-tree)) |
|
50 |
+ (entries object)) |
|
51 |
+(defmethod component ((component string) (object git-tree)) |
|
52 |
+ (remove component (entries object) |
|
53 |
+ :test-not #'equal |
|
54 |
+ :key 'te-name)) |
|
55 |
+(defmethod component ((component pathname) (object git-tree)) |
|
56 |
+ (remove-if-not (lambda (it) |
|
57 |
+ (pathname-match-p it component)) |
|
58 |
+ (entries object) |
|
59 |
+ :key 'te-name)) |
|
60 |
+ |
|
61 |
+(defmethod component ((component (eql :name)) (object tree-entry)) |
|
62 |
+ (te-name object)) |
|
63 |
+(defmethod component ((component (eql :mode)) (object tree-entry)) |
|
64 |
+ (te-mode object)) |
|
65 |
+(defmethod component ((component (eql :hash)) (object tree-entry)) |
|
66 |
+ (te-hash object)) |
... | ... |
@@ -72,10 +72,6 @@ |
72 | 72 |
(list type |
73 | 73 |
(parse-integer length)))))) |
74 | 74 |
|
75 |
-(defun parse-commit (commit) |
|
76 |
- (destructuring-bind (metadata message) |
|
77 |
- (partition-subseq #(#\newline #\newline) |
|
78 |
- commit #+(or)(babel:octets-to-string commit :encoding :latin1)) |
|
79 |
- (values message |
|
80 |
- (map 'vector (serapeum:op (partition #\space _)) |
|
81 |
- (fwoar.string-utils:split #\newline metadata))))) |
|
75 |
+(defun behead (data) |
|
76 |
+ (elt (partition 0 data) |
|
77 |
+ 1)) |