git.fiddlerwoaroof.com
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
... ...
@@ -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))