Fosi Fossil Service
Small teams. Open source. Built on Fossil.

CaaS / Lamina

Unified Management for Fossil Repositories

Public | Owner: CaaS | Default branch: trunk | Last update: 2026-10-25 00:41

Artifact 8f3a8d973882af7319af9eb63fa3e2df1debba2a6ea310b3c87165112d98fc14:


;; 仓库业务:创建(本地)、编辑、删除、查询。
(defmodule lamina_repo_ctl
  (export all))

(defun repo-dir ()
  (lamina_config:get 'repo_dir "data/repos"))

(defun fossil-path (slug)
  (fossil-path 'none slug))

(defun fossil-path (org-id slug)
  (let ((base (case org-id
                ('none (repo-dir))
                (_ (filename:join (repo-dir) (org-name org-id))))))
    (filename:join base (concat-slug slug))))

(defun concat-slug (slug)
  (lists:flatten (io_lib:format "~s.fossil" (list (to-str slug)))))

;;---- 创建(本地)----

(defun create-local (org-id slug display-name description setup-user setup-password)
  (let ((slug1 (to-str slug))
        (display-name1 (default-display display-name slug))
        (description1 (to-str description))
        (setup-user1 (to-str setup-user))
        (setup-password1 (to-bin setup-password)))
    (cond
      ((not (=:= 'ok (lamina_validate:check-slug slug1)))
       (lamina_validate:check-slug slug1))
      ((=:= "" setup-user1) `#(error "Setup 用户名不能为空"))
      ((=:= #"" setup-password1) `#(error "Setup 密码不能为空"))
      ((not (=:= 'ok (valid-org? org-id)))
       (valid-org? org-id))
      ('true
       (let ((availability
              (lamina_validate:check-availability
               (case org-id ('none 'none) (_ (org-name org-id))) slug1)))
         (case availability
           ('ok (create-local-files org-id slug1 display-name1 description1
                                   setup-user1 setup-password1))
           (`#(error ,message) `#(error ,message))
           (_ `#(error "无法检查仓库路径"))))))))

;;---- 创建(远程导入)----

(defun create-remote (org-id slug display-name description remote-url sync-interval
                      setup-user setup-password)
  (let ((slug1 (to-str slug))
        (display-name1 (default-display display-name slug))
        (description1 (to-str description))
        (remote-url1 (to-str remote-url))
        (setup-user1 (to-str setup-user))
        (setup-password1 (to-bin setup-password))
        (interval (normalize-sync-interval sync-interval)))
    (cond
      ((not (=:= 'ok (lamina_validate:check-slug slug1)))
       (lamina_validate:check-slug slug1))
      ((=:= "" remote-url1) `#(error "远程 Fossil 地址不能为空"))
      ((=:= "" setup-user1) `#(error "Setup 用户名不能为空"))
      ((=:= #"" setup-password1) `#(error "Setup 密码不能为空"))
      ((=:= 'error interval) `#(error "同步周期必须是正整数分钟"))
      ((not (=:= 'ok (valid-org? org-id))) (valid-org? org-id))
      ('true
       (let ((availability
              (lamina_validate:check-availability
               (case org-id ('none 'none) (_ (org-name org-id))) slug1)))
         (case availability
           ('ok (create-remote-files org-id slug1 display-name1 description1
                                    remote-url1 interval setup-user1 setup-password1))
           (`#(error ,message) `#(error ,message))
           (_ `#(error "无法检查仓库路径"))))))))

(defun create-remote-files (org-id slug display-name description remote-url interval
                            setup-user setup-password)
  (let ((result (create-remote-files-unsafe org-id slug display-name description
                                            remote-url interval setup-user setup-password)))
    (case result
      ('ok 'ok)
      (`#(error ,message)
       (delete-row-by-path (fossil-path org-id slug))
       `#(error ,message))
      (_
       (delete-row-by-path (fossil-path org-id slug))
       `#(error "远程仓库导入失败")))))

(defun create-remote-files-unsafe (org-id slug display-name description remote-url interval
                                   setup-user setup-password)
  (let ((path (fossil-path org-id slug))
        (clone-url (remote-credential-url remote-url setup-user setup-password)))
    (filelib:ensure_dir path)
    (case (lamina_fossil_repo:clone clone-url path setup-user)
      (`#(ok ,_) 
       (case (configure-repo org-id path slug description setup-user setup-password)
         ('ok (insert-remote-and-start org-id slug display-name description setup-user
                                       setup-password path remote-url interval))
         (`#(error ,reason) (cleanup-files path) `#(error ,(format-error "远程仓库配置失败" reason)))
         (_ (cleanup-files path) `#(error "远程仓库配置失败"))))
      (`#(error ,reason)
       (cleanup-files path)
       `#(error ,(format-error "fossil clone 失败" reason)))
      (_
       (cleanup-files path)
       `#(error "fossil clone 失败")))))

(defun insert-remote-and-start (org-id slug display-name description setup-user setup-password
                                path remote-url interval)
  (case (insert-remote-repo org-id slug display-name description setup-user setup-password
                            path remote-url interval)
    ('ok (start-created org-id slug path))
    (`#(error ,reason)
     (cleanup-files path)
     `#(error ,(format-error "写远程仓库记录失败" reason)))
    (_
     (cleanup-files path)
     `#(error "写远程仓库记录失败"))))

(defun insert-remote-repo (org-id slug display-name description setup-user setup-password
                           path remote-url interval)
  (let ((now (lamina_setup:iso8601 (os:system_time 'second)))
        (encrypted (encrypt-pw setup-password)))
    (case org-id
      ('none
       (case (catch
              (lamina_db:q
               "INSERT INTO repositories(name,organization_id,display_name,description,source,remote_url,sync_interval_minutes,setup_username,setup_password_enc,fossil_path,created_at,updated_at) VALUES(?,NULL,?,?, 'remote',?,?,?, ?,?,?,?);"
               (list slug display-name description remote-url interval setup-user
                     encrypted path now now)))
         ('ok 'ok)
         ('() 'ok)
         (`#(error ,reason) `#(error ,reason))
         (_ `#(error "写库操作失败"))))
      (_
       (case (catch
              (lamina_db:q
               "INSERT INTO repositories(name,organization_id,display_name,description,source,remote_url,sync_interval_minutes,setup_username,setup_password_enc,fossil_path,created_at,updated_at) VALUES(?,?,?,?,'remote',?,?,?, ?,?,?,?);"
               (list slug org-id display-name description remote-url interval setup-user
                     encrypted path now now)))
         ('ok 'ok)
         ('() 'ok)
         (`#(error ,reason) `#(error ,reason))
         (_ `#(error "写库操作失败")))))))

(defun create-local-files (org-id slug display-name description setup-user setup-password)
  (let ((result (create-local-files-unsafe org-id slug display-name description
                                           setup-user setup-password)))
    ;; 任何失败路径都再次清理 DB 记录,保证 init/配置/启动失败不留半成品。
    (case result
      ('ok 'ok)
      (`#(error ,message)
       (delete-row-by-path (fossil-path org-id slug))
       `#(error ,message))
      (_
       (delete-row-by-path (fossil-path org-id slug))
       `#(error "创建仓库失败")))))

(defun create-local-files-unsafe (org-id slug display-name description setup-user setup-password)
  (let ((path (fossil-path org-id slug)))
    (filelib:ensure_dir path)
    (case (lamina_fossil_repo:init path setup-user)
      (`#(ok ,_) (configure-and-insert org-id slug display-name description
                                       setup-user setup-password path))
      (`#(error ,reason)
       (cleanup-files path)
       `#(error ,(format-error "fossil init 失败" reason)))
      (_
       (cleanup-files path)
       `#(error "fossil init 失败")))))

(defun configure-and-insert (org-id slug display-name description setup-user setup-password path)
  (case (configure-repo org-id path slug description setup-user setup-password)
    ('ok (insert-and-start org-id slug display-name description setup-user setup-password path))
    (`#(error ,reason)
     (cleanup-files path)
     `#(error ,(format-error "仓库配置失败" reason)))
    (_
     (cleanup-files path)
     `#(error "仓库配置失败"))))

(defun insert-and-start (org-id slug display-name description setup-user setup-password path)
  (case (insert-repo org-id slug display-name description setup-user setup-password path)
    ('ok (start-created org-id slug path))
    (`#(error ,reason)
     (cleanup-files path)
     `#(error ,(format-error "写库失败" reason)))
    (_
     (cleanup-files path)
     `#(error "写库失败"))))

(defun start-created (org-id slug path)
  (let ((org-name (case org-id
                    ('none 'none)
                    (_ (org-name org-id)))))
    (lamina_sup:ensure-runtime)
    (case (catch (lamina_repo_mgr:start-repo org-name slug path))
      ('ok 'ok)
      (`#(error ,reason) (fail-start path reason))
      (`#(EXIT ,reason) (fail-start path reason))
      (other (fail-start path other)))))

(defun fail-start (path reason)
  (delete-row-by-path path)
  (cleanup-files path)
  `#(error ,(start-error-text reason)))

(defun start-error-text (reason)
  (let ((text (lists:flatten (io_lib:format "~p" (list reason)))))
    (cond
      ((/= 'nomatch (string:find text "noproc"))
       "仓库服务未就绪。若刚完成安装,请重启服务后再创建仓库。")
      ((/= 'nomatch (string:find text "timeout"))
       "仓库进程启动超时")
      ('true (lamina_fossil:error-message reason)))))

(defun configure-repo (org-id path slug description setup-user setup-password)
  (case (lamina_fossil_repo:set-user-pw path setup-user setup-password)
    (`#(ok ,_)
     (case (maybe-apply-skin path org-id slug description)
       ('ok
        (case (lamina_fossil_repo:set-description path slug description)
          (`#(ok ,_) 'ok)
          (`#(error ,reason) `#(error ,reason))
          (_ `#(error "仓库描述操作失败"))))
       (`#(error ,reason) `#(error ,reason))))
    (`#(error ,reason) `#(error ,reason))
    (_ `#(error "仓库配置操作失败"))))

;; 每次创建时按当前站点设置生成并导入皮肤;配置文件只在导入期间存在。
(defun maybe-apply-skin (path org-id slug)
  (maybe-apply-skin path org-id slug ""))

(defun maybe-apply-skin (path org-id slug description)
  (let ((org-name (case org-id
                    ('none "")
                    (_ (org-name org-id)))))
    (case (catch (lamina_fossil_skin:render org-name slug description))
      (`#(ok ,skin-path)
       (let ((result (catch (lamina_fossil_repo:apply-skin path skin-path))))
         (catch (file:delete skin-path))
         (case result
           (`#(ok ,_) (after-skin path))
           (`#(error ,reason) `#(error ,reason))
           (`#(EXIT ,reason) `#(error ,reason))
           (_ `#(error "皮肤导入失败")))))
      (`#(error ,reason) `#(error ,reason))
      (`#(EXIT ,reason) `#(error ,reason))
      (_ `#(error "皮肤生成失败")))))

(defun after-skin (path)
  (case (lamina_fossil_repo:configure-web path)
    (`#(ok ,_) 'ok)
    (`#(error ,reason) `#(error ,reason))
    (_ `#(error "仓库首页配置失败"))))

(defun insert-repo (org-id slug display-name description setup-user setup-password path)
  (let ((now (lamina_setup:iso8601 (os:system_time 'second)))
        (encrypted (encrypt-pw setup-password)))
    (case org-id
      ('none
       (case (catch
              (lamina_db:q
               "INSERT INTO repositories(name,organization_id,display_name,description,source,setup_username,setup_password_enc,fossil_path,created_at,updated_at) VALUES(?,NULL,?,?, 'local',?,?,?, ?,?);"
               (list slug display-name description setup-user encrypted path now now)))
         ('ok 'ok)
         ('() 'ok)
         (`#(error ,reason) `#(error ,reason))
         (_ `#(error "写库操作失败"))))
      (_
       (case (catch
              (lamina_db:q
               "INSERT INTO repositories(name,organization_id,display_name,description,source,setup_username,setup_password_enc,fossil_path,created_at,updated_at) VALUES(?,?,?,?, 'local',?,?,?, ?,?);"
               (list slug org-id display-name description setup-user encrypted path now now)))
         ('ok 'ok)
         ('() 'ok)
         (`#(error ,reason) `#(error ,reason))
         (_ `#(error "写库操作失败")))))))

;;---- 编辑 ----

;; slug 从数据库读取,调用者不能提交或修改 URL 段。
(defun update-repo (repo-id display-name description setup-user setup-password)
  (case (catch (lamina_db:q
                "SELECT name,fossil_path,setup_username FROM repositories WHERE id=?;"
                (list repo-id)))
    (`((,name ,path-bin ,old-user))
     (update-existing repo-id (to-str name) (to-str path-bin) (to-str old-user)
                      display-name description setup-user setup-password))
    (_ `#(error "仓库不存在"))))

;; M6 编辑页使用的扩展更新入口:保留 M4 的兼容 API,同时更新远程同步与 Git 设置。
(defun update-repo-settings (repo-id display-name description setup-user setup-password
                             remote-url sync-interval git-enabled git-url git-username
                             git-token git-auto-push)
  (case (get-repo-detail repo-id)
    ('undefined `#(error "仓库不存在"))
    (detail
     (let* ((source (maps:get 'source detail "local"))
            (remote-url1 (to-str remote-url))
            (interval (normalize-sync-interval sync-interval)))
       (cond
         ((and (=:= "remote" source) (=:= "" remote-url1))
          `#(error "远程 Fossil 地址不能为空"))
         ((=:= 'error interval) `#(error "同步周期必须是正整数分钟"))
         ('true
          (case (update-repo repo-id display-name description setup-user setup-password)
            ('ok (persist-remote-git repo-id source remote-url1 interval git-enabled
                                     git-url git-username git-token git-auto-push))
            (error error)
            (_ `#(error "更新仓库失败")))))))))

(defun persist-remote-git (repo-id source remote-url interval git-enabled git-url
                           git-username git-token git-auto-push)
  (let ((token (to-bin git-token))
        (enabled (flag-value git-enabled))
        (auto (flag-value git-auto-push)))
    (let ((sql-result
           (if (=:= #"" token)
               (lamina_db:q
                "UPDATE repositories SET remote_url=?,sync_interval_minutes=?,git_enabled=?,git_url=?,git_username=?,git_auto_push=?,updated_at=? WHERE id=?;"
                (list (if (=:= "remote" source) remote-url 'null)
                      (if (=:= "remote" source) interval 'null)
                      enabled (to-str git-url) (to-str git-username) auto
                      (lamina_setup:iso8601 (os:system_time 'second)) repo-id))
               (lamina_db:q
                "UPDATE repositories SET remote_url=?,sync_interval_minutes=?,git_enabled=?,git_url=?,git_username=?,git_token_enc=?,git_auto_push=?,updated_at=? WHERE id=?;"
                (list (if (=:= "remote" source) remote-url 'null)
                      (if (=:= "remote" source) interval 'null)
                      enabled (to-str git-url) (to-str git-username)
                      (encrypt-token token) auto
                      (lamina_setup:iso8601 (os:system_time 'second)) repo-id)))))
      (case sql-result
        ('ok 'ok)
        ('() 'ok)
        (`#(error ,reason) `#(error ,(format-error "保存同步/Git 设置失败" reason)))
        (_ `#(error "保存同步/Git 设置失败"))))))

(defun get-repo-detail (repo-id)
  (case (catch
         (lamina_db:q
          "SELECT r.id,r.name,r.display_name,r.description,r.source,COALESCE(r.remote_url,''),COALESCE(r.sync_interval_minutes,0),r.setup_username,r.git_enabled,COALESCE(r.git_url,''),COALESCE(r.git_username,''),r.git_auto_push,COALESCE(r.last_timeline_at,''),COALESCE(r.last_synced_at,''),COALESCE(r.last_sync_status,''),COALESCE(r.last_pushed_at,''),COALESCE(r.last_push_status,''),r.fossil_path,COALESCE(r.repo_status,'up'),COALESCE(o.name,'') FROM repositories r LEFT JOIN organizations o ON o.id=r.organization_id WHERE r.id=?;"
          (list repo-id)))
    (`((,id ,name ,display ,description ,source ,remote-url ,interval ,username
        ,git-enabled ,git-url ,git-username ,git-auto ,last-timeline ,last-synced ,sync-status
        ,last-pushed ,push-status ,path ,repo-status ,org-name))
     (maps:from_list
      (list (tuple 'id id) (tuple 'name (to-str name))
            (tuple 'display_name (to-str display))
            (tuple 'description (to-str description))
            (tuple 'source (to-str source)) (tuple 'remote_url (to-str remote-url))
            (tuple 'sync_interval (to-int interval)) (tuple 'setup_username (to-str username))
            (tuple 'git_enabled (to-int git-enabled)) (tuple 'git_url (to-str git-url))
            (tuple 'git_username (to-str git-username)) (tuple 'git_auto_push (to-int git-auto))
            (tuple 'last_timeline_at (to-str last-timeline))
            (tuple 'last_synced_at (to-str last-synced))
            (tuple 'last_sync_status (to-str sync-status))
            (tuple 'last_pushed_at (to-str last-pushed))
            (tuple 'last_push_status (to-str push-status))
            (tuple 'fossil_path (to-str path))
            (tuple 'repo_status (to-str repo-status))
            (tuple 'org_name (to-str org-name)))))
    (_ 'undefined)))

(defun flag-value (value)
  (case value
    ('true 1)
    ('false 0)
    (#"on" 1)
    ("on" 1)
    (1 1)
    (#"1" 1)
    ("1" 1)
    (_ 0)))

(defun to-int (value)
  (cond
    ((is_integer value) value)
    ((is_binary value) (case (catch (binary_to_integer value)) (`#(EXIT ,_) 0) (i i)))
    ((is_list value) (case (catch (list_to_integer value)) (`#(EXIT ,_) 0) (i i)))
    ('true 0)))

(defun encrypt-token (token)
  (term_to_binary (lamina_crypto:encrypt token (lamina_auth:secret-key))))

(defun update-existing (repo-id slug path old-user display-name description setup-user setup-password)
  (let ((password (to-bin setup-password))
        (username (if (=:= "" (to-str setup-user)) old-user (to-str setup-user))))
    (cond
      ((=:= "" username) `#(error "Setup 用户名不能为空"))
      ((and (=:= #"" password) (not (=:= username old-user)))
       `#(error "修改 Setup 用户名时必须填写新密码"))
      ('true
       (case (if (=:= #"" password)
                 'ok
                 (lamina_fossil_repo:set-user-pw path username password))
         ('ok (update-fossil-description repo-id slug path old-user username
                                          display-name description password))
         (`#(ok ,_) (update-fossil-description repo-id slug path old-user username
                                               display-name description password))
         (`#(error ,reason) `#(error ,(format-error "更新 Setup 凭据失败" reason)))
         (_ `#(error "更新仓库操作失败")))))))

(defun update-fossil-description (repo-id slug path _old-user username
                                  display-name description password)
  (case (lamina_fossil_repo:set-description path slug description)
    (`#(ok ,_)
     (case (maybe-apply-skin path (org-id-of repo-id) slug description)
       ('ok (persist-update repo-id username display-name description password))
       (`#(error ,reason) `#(error ,(format-error "更新仓库皮肤失败" reason)))
       (_ (persist-update repo-id username display-name description password))))
    (`#(error ,reason) `#(error ,(format-error "更新仓库描述失败" reason)))
    (_ `#(error "更新仓库描述失败"))))

(defun persist-update (repo-id username display-name description password)
  (let ((now (lamina_setup:iso8601 (os:system_time 'second))))
    (case (if (=:= #"" password)
              (lamina_db:q
               "UPDATE repositories SET display_name=?,description=?,setup_username=?,updated_at=? WHERE id=?;"
               (list display-name description username now repo-id))
              (lamina_db:q
               "UPDATE repositories SET display_name=?,description=?,setup_username=?,setup_password_enc=?,updated_at=? WHERE id=?;"
               (list display-name description username (encrypt-pw password) now repo-id)))
      ('ok 'ok)
      ('() 'ok)
      (`#(error ,reason) `#(error ,(format-error "更新失败" reason)))
      (_ `#(error "更新数据库失败")))))

;;---- 删除 ----

(defun delete (repo-id)
  (case (catch (lamina_db:q "SELECT r.name,r.fossil_path,COALESCE(o.name,'') FROM repositories r LEFT JOIN organizations o ON o.id=r.organization_id WHERE r.id=?;" (list repo-id)))
    (`((,name ,path-bin ,org-bin))
     (let ((slug (to-str name))
           (path (to-str path-bin))
           (org-name (to-str org-bin)))
       (case (catch (if (=:= "" org-name)
                        (lamina_repo_mgr:stop-repo slug)
                        (lamina_repo_mgr:stop-repo org-name slug)))
         (`#(EXIT ,_) 'ok)
         (_ 'ok))
       (case (catch (lamina_db:q "DELETE FROM repositories WHERE id=?;" (list repo-id)))
         ('ok
          (cleanup-files path)
          'ok)
         ('()
          (cleanup-files path)
          'ok)
         (`#(error ,reason) `#(error ,(format-error "删除失败" reason)))
         (_ `#(error "删除数据库记录失败")))))
    (_ `#(error "仓库不存在"))))

;;---- 查询 ----

(defun list-repos ()
  (let ((result (catch (lamina_db:q
                       "SELECT r.id,r.name,r.display_name,r.description,r.source,COALESCE(o.name,''),r.last_timeline_at,r.fossil_path,COALESCE(r.last_sync_status,''),COALESCE(r.last_push_status,''),COALESCE(r.git_enabled,0),COALESCE(r.repo_status,'up') FROM repositories r LEFT JOIN organizations o ON o.id=r.organization_id ORDER BY r.updated_at DESC;"))))
    (if (is_list result) result '())))

(defun get-repo (repo-id)
  (case (catch (lamina_db:q
                "SELECT r.id,r.name,r.display_name,r.description,r.source,COALESCE(o.name,''),r.last_timeline_at,r.fossil_path FROM repositories r LEFT JOIN organizations o ON o.id=r.organization_id WHERE r.id=?;"
                (list repo-id)))
    (`(,row) row)
    (_ 'undefined)))

;;---- helpers ----

(defun org-id-of (repo-id)
  (case (catch (lamina_db:q "SELECT organization_id FROM repositories WHERE id=?;"
                             (list repo-id)))
    (`((,id))
     (if (or (=:= id 'undefined) (=:= id 'null) (=:= id 'nil))
         'none
         id))
    (_ 'none)))

(defun org-name (org-id)
  (case (catch (lamina_db:q "SELECT name FROM organizations WHERE id=?;" (list org-id)))
    (`((,name)) (to-str name))
    (_ 'none)))

(defun valid-org? (org-id)
  (case org-id
    ('none 'ok)
    (id (if (=:= 'true (lamina_org_mgr:exists? id))
            'ok
            `#(error "组织不存在")))
    (_ `#(error "组织选择无效"))))

(defun normalize-sync-interval (value)
  (let ((s (to-str value)))
    (if (=:= "" s)
        60
        (case (catch (list_to_integer s))
          (`#(EXIT ,_) 'error)
          (minutes (if (and (> minutes 0) (=< minutes 525600)) minutes 'error))))))

;; HTTP(S) 远程源用表单中的 Setup 凭据访问;file:// 等本地测试源保持原样。
(defun remote-credential-url (url username password)
  (let ((s (to-str url)))
    (if (or (lists:prefix "http://" s) (lists:prefix "https://" s))
        (let* ((scheme-len (if (lists:prefix "https://" s) 8 7))
               (scheme (lists:sublist s scheme-len))
               (rest (lists:nthtail scheme-len s)))
          (case (string:split rest "/" 'leading)
            (`(,authority ,path)
             (if (lists:member 64 authority)
                 s
                 (lists:flatten
                  (list scheme (url-encode username) ":" (url-encode password) "@"
                        authority "/" path))))
            (_ s)))
        s)))

(defun url-encode (value)
  (url-encode (to-str value) '()))

(defun url-encode (chars acc)
  (case chars
    ('() (lists:flatten (lists:reverse acc)))
    (`(,char . ,rest)
     (if (url-safe? char)
         (url-encode rest (cons (list char) acc))
         (url-encode rest (cons (percent-char char) acc))))))

(defun url-safe? (char)
  (or (and (>= char 48) (=< char 57))
      (and (>= char 65) (=< char 90))
      (and (>= char 97) (=< char 122))
      (lists:member char (list 45 46 95 126))))

(defun percent-char (char)
  (let ((hex "0123456789ABCDEF"))
    (list 37 (lists:nth (+ 1 (div char 16)) hex)
          (lists:nth (+ 1 (rem char 16)) hex))))

(defun encrypt-pw (password)
  ;; SQLite BLOB 存 term_to_binary,避免直接把 {IV,CT,TAG} 当成参数。
  (term_to_binary (lamina_crypto:encrypt (to-bin password) (lamina_auth:secret-key))))

(defun cleanup-files (path)
  (lists:foreach
   (lambda (suffix) (catch (file:delete (lists:append (list path suffix)))))
   (list "" "-wal" "-shm" "-journal")))

(defun delete-row-by-path (path)
  (catch (lamina_db:q "DELETE FROM repositories WHERE fossil_path=?;" (list path))))

(defun default-display (display slug)
  (let ((s (to-str display)))
    (if (=:= "" s) (to-str slug) s)))

(defun format-error (prefix reason)
  (lists:flatten (io_lib:format "~s: ~s" (list prefix (reason-text reason)))))

(defun reason-text (reason)
  (case reason
    (`#(exit_status ,_code ,_out) (lamina_fossil:error-message reason))
    (`#(enoent ,_) (lamina_fossil:error-message reason))
    (`#(eacces ,_) (lamina_fossil:error-message reason))
    ('timeout (lamina_fossil:error-message reason))
    (_ (lists:flatten (io_lib:format "~p" (list reason))))))

(defun to-bin (value)
  (cond
    ((is_binary value) value)
    ((is_list value) (list_to_binary value))
    ('true (list_to_binary (to-str value)))))

(defun to-str (value)
  (cond
    ((is_binary value) (binary_to_list value))
    ((is_list value) value)
    ((=:= 'undefined value) "")
    ('true (lists:flatten (io_lib:format "~p" (list value))))))

Clone

You can clone this repository with the following command:

fossil clone https://fosi.dev/CaaS/Lamina

Contribute

The best way to contribute is through e-mail. See the project documentation for more details.