Coverage report: /home/ellis/comp/core/lib/vc/proto.lisp
Kind | Covered | All | % |
expression | 39 | 203 | 19.2 |
branch | 4 | 16 | 25.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; lib/vc/proto.lisp --- VC Protocol
9
(defvar *default-vc-kind* :hg)
11
(defvar *repo-roots* nil)
12
(defvar *repo-registry* (make-hash-table :test 'equal))
13
(defvar *repo-auto-register* t
14
"When non-nil, register all VC-REPO objects when they are created.")
17
(define-condition vc-error (std-error) ())
20
(defun namestring-or (obj)
25
(defun rel-pathname (path)
26
(pathname (string-left-trim '(#\/) path)))
28
(defun glob-path-match (glob)
30
(member (subseq p start end) (directory (rel-pathname glob)) :test 'equal)))
33
(defgeneric vc-init (self)
34
(:documentation "Initialize a vc-repo - calls either 'git init' or 'hg init'"))
36
(defgeneric vc-run (self cmd &rest args)
37
(:documentation "Run a vc CMD with ARGS."))
39
(defgeneric vc-clone (self remote &key &allow-other-keys)
40
(:documentation "Clone repo REMOTE into spec SELF."))
42
(defgeneric vc-push (self &key remote)
43
(:documentation "Push repo SELF to REMOTE."))
45
(defgeneric vc-pull (self &optional remote)
46
(:documentation "Pull repo REMOTE into spec SELF."))
48
(defgeneric vc-update (self &optional branch)
49
(:documentation "Update repo SELF with optional BRANCH."))
51
(defgeneric vc-commit (self msg &key &allow-other-keys)
52
(:documentation "Commit repo object SELF, supplied with message MSG."))
54
(defgeneric vc-add (self &rest files)
55
(:documentation "Add FILES to repo SELF."))
57
(defgeneric vc-remove (self &rest files)
58
(:documentation "Remove FILES from repo SELF."))
60
(defgeneric vc-addremove (self &rest files)
61
(:documentation "Add any untracked files in the current directory and delete tracked files that
64
(defgeneric vc-branch (self)
65
(:documentation "Return the name of the current branch."))
67
(defgeneric vc-status (self &key &allow-other-keys))
69
(defgeneric vc-bundle (self output &key &allow-other-keys))
70
(defgeneric vc-unbundle (self input &key &allow-other-keys))
72
(defgeneric vc-export (self output &key &allow-other-keys))
75
(defgeneric vc-head (self))
76
(defgeneric vc-tags (self))
77
(defgeneric vc-revs (self))
78
(defgeneric vc-branches (self))
79
(defgeneric vc-remotes (self))
80
(defgeneric vc-remote (self cmd &key &allow-other-keys))
81
(defgeneric vc-submodule (self cmd &key &allow-other-keys))
82
(defgeneric vc-config (self))
83
(defgeneric (setf vc-config) (new self))
84
(defgeneric vc-type (self))
85
;; IDEA 2023-12-29: :ediff t
86
(defgeneric vc-diff (a b &key &allow-other-keys))
92
;; usually parsed from .gitconfig or .hgrc
93
(defconfig vc-config () ())
97
;; Basically we treat HG and GIT ignore files the same - just lines of string
98
;; patterns. HG uses regexp and GIT is globs - an IGNOREFILE has a line parser
99
;; slot for selecting the appropriate function.
101
(defun map-lines (fn path)
102
"Call FN on each line of file PATH and collect the result."
103
(with-open-file (file path)
104
(loop for line = (read-line file nil)
106
unless (or (= (length line) 0) (char= (aref line 0) #\#))
107
collect (funcall fn line))))
109
(defstruct vc-ignore path patterns)
111
(defgeneric vc-path-ignored-p (obj path)
112
(:documentation "Check PATH against the patterns in OBJ. If there is a match, return non-nil.")
113
(:method ((obj vc-ignore) (path t))
114
(let ((len (length path)))
115
(loop for pat in (vc-ignore-patterns obj)
116
when (funcall pat path 0 len)
117
return (values path pat)))))
119
(defstruct vc-branch name rev)
121
(defstruct vc-commit id message)
123
(defstruct vc-tag name id)
125
(deftype vc-designator () `(or (member :hg :git) null)) ;; maybe: :sp (sapling)
128
(type nil :type vc-designator)
132
(defaccessor name ((self vc-remote)) (vc-remote-name self))
134
(defmethod print-object ((self vc-remote) stream)
135
(let ((name (vc-remote-name self))
136
(url (vc-remote-url self)))
137
(format stream "(~A . ~A)" (string-downcase name) url)))
139
(defstruct vc-rev num id)
142
((path :initform nil :type (or null string pathname) :accessor path
144
:documentation "AKA working-directory or working-copy")
145
(head :initform nil :initarg :head :type (or null vc-rev) :accessor vc-head)
146
(branches :initform (make-array 0 :element-type 'vc-branch :fill-pointer 0)
147
:type (vector vc-branch) :accessor vc-branches)
148
(submodules :type (vector vc-repo) :accessor vc-submodules)
149
(tags :initform (make-array 0 :element-type 'vc-tag :fill-pointer 0) :type (vector vc-tag) :accessor vc-tags)
150
(revisions :initform (make-array 0 :element-type 'vc-rev :fill-pointer 0)
151
:type (vector vc-rev) :accessor vc-revs)
152
(remotes :initform (make-array 0 :element-type 'vc-remote :fill-pointer 0)
153
:type (vector vc-remote) :accessor vc-remotes)
154
(config :initform nil :type (or null vc-config) :accessor vc-config))
155
(:documentation "generic Repository object backed by one of VC-DESIGNATOR."))
157
(defun register-repo (repo)
158
"Register a repo, collecting information from the filesystem and
159
creating a repo object which is stored in *REPO-REGISTRY*."
160
(setf (gethash (path repo) *repo-registry*) repo))
162
(defun find-repo (name)
163
"Find a repo in *REPO-REGISTRY*."
164
(gethash name *repo-registry*))
166
(defmethod initialize-instance :after ((self vc-repo) &key)
167
(when *repo-auto-register* (register-repo self)))
169
(defmethod name ((self vc-repo))
170
(car (last (pathname-directory (path self)))))
172
(defmethod vc-type ((self vc-repo)) t)
174
(defmethod write-ast ((self vc-repo) stream &key (pretty t) (case :downcase))
175
(if (= 0 (length (vc-remotes self)))
176
(write (vc-type self) :stream stream :pretty pretty :case case :readably t :array t :escape t)
179
(write (vc-type self) :stream stream :pretty pretty :case case :readably t :array t :escape t)
181
(loop for x in (vc-remotes self)
183
(write `(,(vc-type self) ,(coerce (vc-remotes self) 'list)) :stream stream :pretty pretty :case case :readably t :array t :escape t))
184
(format stream ")"))))
186
;; (defmethod print-object ((self vc-repo) stream)
187
;; (print-unreadable-object (self stream)
188
;; (write "vc-repo" :stream stream)
189
;; (std:when-let ((remotes (vc-remotes self)))
190
;; (write " " :stream stream)
191
;; (pprint-tabular stream remotes nil nil 2))))
193
(defun find-repo-root (&optional (path *default-pathname-defaults*))
194
"Check PATH for evidence of a VCS and continue walking up the filesystem until
195
we find one, else return NIL."
196
(labels ((%check (dir)
198
(return-from find-repo-root)
199
(if (probe-file (merge-pathnames ".hg/" dir))
201
(when (probe-file (merge-pathnames ".git/" dir))
203
(let ((%path (directory-path (or path *default-pathname-defaults*))))
204
(loop for x = (%check %path)
205
for parent = (when-let ((parent (butlast (pathname-directory %path))))
206
(make-pathname :directory parent))
208
return (values %path x)
209
else if (or (not parent) (sequence:emptyp (namestring parent)))
212
do (setf %path parent)))))