git.fiddlerwoaroof.com
Raw Blame History
(in-package :fwoar.cl-git)

;; TODO: Update the code so this uses an object instead of a path.
(defvar *git-repository*)
(setf (documentation '*git-repository* 'variable)
      "The git repository path for porcelain commands to operate on.")

(defvar *git-encoding* :utf-8
  "The encoding to use when parsing git objects")

(defun co.fwoar.git:in-repository (root)
  (setf *git-repository*
        (ensure-repository
         (truename root))))

(defun co.fwoar.git:repository ()
  *git-repository*)

(defmacro co.fwoar.git:with-repository ((root) &body body)
  `(let ((*git-repository* (ensure-repository ,root)))
     ,@body))

(defun co.fwoar.git:show-repository ()
  *git-repository*)

(defun in-git-package (symbol)
  (intern (symbol-name symbol)
          :co.fwoar.git))

(defun handle-list (_1)
  (case (in-git-package (car _1))
    (co.fwoar.git::unwrap `(uiop:nest (car)
                                      (mapcar ,@(cdr _1))))
    (t (cons (in-git-package (car _1))
             (cdr _1)))))

(defun co.fwoar.git::resolve-refish (it)
  (flet ((hash-p (it)
           (and (> (length it) 32)
                (every (serapeum:op
                         (digit-char-p _1 16))
                       it))))
    (cond
      ((block is-branch
         (mapc (fw.lu:destructuring-lambda ((name hash))
                 (when (equal it name)
                   (return-from is-branch
                     (ensure-ref hash))))
               (branches *git-repository*))
         nil))
      ((hash-p it) (ensure-ref it)))))

(defmacro co.fwoar.git:git (&rest commands)
  `(uiop:nest ,@(reverse
                 (funcall (data-lens:<>1
                           (data-lens:over (serapeum:op
                                             (typecase _1
                                               (string `(identity ,_1))
                                               (list (handle-list _1)))))
                           (data-lens:transform-head (serapeum:op
                                                       (etypecase _1
                                                         (string `(co.fwoar.git::resolve-refish ,_1))
                                                         (t _1)))))
                          commands))))

(defun co.fwoar.git::ensure-ref (it)
  (ensure-ref it))

(defun co.fwoar.git::decode (it)
  (babel:octets-to-string it :encoding *git-encoding*))

(defun co.fwoar.git::<<= (fun &rest args)
  (apply #'mapcan fun args))

(defmacro co.fwoar.git::map (fun list)
  (alexandria:once-only (list)
    (alexandria:with-gensyms (it)
      `(mapcar ,(if (consp fun)
                    `(lambda (,it)
                       (,(in-git-package (car fun))
                        ,@(cdr fun)
                        ,it))
                    `',(in-git-package fun))
               ,list))))

(defmacro co.fwoar.git::juxt (&rest args)
  (let ((funs (butlast args))
        (arg (car (last args))))
    (alexandria:once-only (arg)
      `(list ,@(mapcar (lambda (f)
                         `(,@(alexandria:ensure-list f) ,arg))
                       funs)))))

(defmacro co.fwoar.git::pipe (&rest funs)
  (let ((funs (reverse (butlast funs)))
        (var (car (last funs))))
    `(uiop:nest ,@(mapcar (lambda (it)
                            (if (consp it)
                                `(,(in-git-package (car it)) ,@(cdr it))
                                `(,(in-git-package it))))
                          funs)
                ,var)))

(defun co.fwoar.git::filter (fun &rest args)
  (apply #'remove-if-not fun args))

(defun co.fwoar.git::object (thing)
  (extract-object thing))

(defun co.fwoar.git:show (object)
  (extract-object object))

(defun co.fwoar.git:contents (object)
  (co.fwoar.git:show object))

(defun co.fwoar.git:component (&rest args)
  (let ((component-list (butlast args))
        (target (car (last args))))
    (fwoar.cl-git::component component-list target)))

(defun co.fwoar.git:tree (commit-object)
  (component :tree
             commit-object))

(defun co.fwoar.git::filter-tree (name-pattern tree)
  #+lispworks
  (declare (notinline serapeum:string-prefix-p))
  (let* ((tree-entries (component :entries tree))
         (scanner (cl-ppcre:create-scanner name-pattern)))
    (remove-if-not (serapeum:op
                     (cl-ppcre:scan scanner _))
                   tree-entries
                   :key #'te-name)))

(defun co.fwoar.git:branch (&optional (branch :master))
  #+lispworks
  (declare (notinline serapeum:assocadr))
  (let ((branches (branches *git-repository*)))
    (ref *git-repository*
         (serapeum:assocadr (etypecase branch
                              (string branch)
                              (keyword (string-downcase branch)))
                            branches
                            :test 'equal))))

(defun co.fwoar.git:branches ()
  (branches *git-repository*))

(defun co.fwoar.git::parents (commit)
  (mapcar 'ensure-ref
          (component :parents commit)))
(defun co.fwoar.git:commit-parents (commit)
  (co.fwoar.git::parents commit))

(defun co.fwoar.git:rev-list (ref-id &optional (limit nil limit-p))
  "Return the commits reachable from the ref."
  (when limit-p
    (rotatef ref-id limit))
  (let ((seen (make-hash-table)))
    (labels ((iterate (queue accum &optional (count 0))
               (if (or (when limit-p
                         (= limit count))
                       (null queue))
                   accum
                   (destructuring-bind (next . rest) queue
                     (let ((parents (co.fwoar.git::parents next)))
                       (iterate (append rest parents)
                         (if (gethash next seen)
                             accum
                             (progn
                               (setf (gethash next seen) t)
                               (cons next accum)))
                         (1+ count)))))))
      (iterate (list (ensure-ref ref-id))
        ()))))