gitile/gitile/repo.scm

repo.scm

1
;;;; Copyright (C) 2020 Julien Lepiller <julien@lepiller.eu>
2
;;;;
3
;;;; SPDX-License-Identifier: AGPL-3.0-or-later
4
;;;;
5
;;;; This program is free software: you can redistribute it and/or modify
6
;;;; it under the terms of the GNU Affero General Public License as published by
7
;;;; the Free Software Foundation, either version 3 of the License, or
8
;;;; (at your option) any later version.
9
;;;;
10
;;;; This program is distributed in the hope that it will be useful,
11
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
;;;; GNU Affero General Public License for more details.
14
;;;;
15
;;;; You should have received a copy of the GNU Affero General Public License
16
;;;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
;;;;
18
19
(define-module (gitile repo)
20
  #:use-module (git)
21
  #:use-module (git types)
22
  #:use-module (srfi srfi-9)
23
  #:use-module (system foreign)
24
  #:export (get-branches
25
            get-tags
26
            get-files
27
            get-file-content
28
            get-commits
29
            get-commit
30
            get-description
31
            last-commit
32
33
            <file>
34
            make-file
35
            file?
36
            file-name
37
            file-type
38
39
            <mytag>
40
            make-mytag
41
            mytag-name
42
            mytag-message
43
            mytag-target))
44
45
(define tree-entry-type
46
  (let ((proc (libgit2->procedure int "git_tree_entry_type" '(*))))
47
    (lambda (entry)
48
      (proc (tree-entry->pointer entry)))))
49
50
(define tag-foreach
51
  (let ((proc (libgit2->procedure* "git_tag_foreach"
52
                                   `(* * *))))
53
    (lambda (repository callback)
54
      (let ((callback* (procedure->pointer int
55
                                           (lambda (name oid _)
56
                                             (callback
57
                                               (pointer->string name)
58
                                               (pointer->oid oid)))
59
                                           '(* * *))))
60
        (proc (repository->pointer repository) callback* %null-pointer)))))
61
62
(define (get-branches repo)
63
  (map (lambda (ref) (cons (reference-shorthand ref) (reference-name ref)))
64
       (filter reference-branch? (reference-fold cons '() repo))))
65
66
(define-record-type <mytag>
67
  (make-mytag name message target date)
68
  mytag?
69
  (name    mytag-name)
70
  (message mytag-message)
71
  (target  mytag-target)
72
  (date    mytag-date))
73
74
(define (get-tags repo)
75
  (let ((tags '()))
76
    (tag-foreach
77
      repo
78
      (lambda (name oid)
79
        (let* ((tag (false-if-exception (tag-lookup repo oid)))
80
               (oid (if tag (tag-target-id tag) oid))
81
               (commit (commit-lookup repo oid))
82
               (date (commit-time commit)))
83
          (set! tags
84
            (cons
85
              (make-mytag
86
                (cond
87
                  (tag (tag-name tag))
88
                  ((string-prefix? "refs/tags/" name)
89
                   (substring name (string-length "refs/tags/")))
90
                  (else name))
91
                (if tag (tag-message tag) "")
92
                (oid->string oid)
93
                date)
94
              tags)))
95
        0))
96
    (sort tags (lambda (t1 t2) (> (mytag-date t1) (mytag-date t2))))))
97
98
(define (search-reference repo name)
99
  (reference-fold (lambda (ref acc)
100
                    (if (equal? name (reference-shorthand ref))
101
                        (reference-name ref)
102
                        acc))
103
                  #f
104
                  repo))
105
106
(define (ref->oid repo ref)
107
  (define (ref->oid* repo ref)
108
    (let ((ref-name (or ref
109
                        (false-if-exception (reference-name (repository-head repo))))))
110
      (or (false-if-exception (string->oid ref-name))
111
          (false-if-exception (reference-name->oid repo ref-name))
112
          (false-if-exception (reference-name->oid repo (search-reference repo ref-name))))))
113
  (or (ref->oid* repo ref)
114
      (throw 'not-found
115
             `(content ((p "Ref not found in repository: " ,ref)))
116
             `(ref ,ref))))
117
118
(define-record-type <file>
119
  (make-file name type commit)
120
  file?
121
  (name   file-name)
122
  (type   file-type)
123
  (commit file-commit))
124
125
(define* (get-files repo #:key (path "") (ref #f))
126
  (let* ((oid (ref->oid repo ref))
127
         (commit (commit-lookup repo oid))
128
         (tree (commit-tree commit)))
129
    (let ((result '()))
130
      (tree-walk
131
        tree TREEWALK-PRE
132
        (lambda (root entry)
133
          (when (equal? root path)
134
            (set! result
135
              (cons (make-file (tree-entry-name entry)
136
                               (tree-entry-type entry)
137
                               (last-commit-for-file
138
                                 (string-append root (tree-entry-name entry))
139
                                 commit))
140
                    result)))
141
          0))
142
      result)))
143
144
(define (last-commit repo ref)
145
  (let ((oid (ref->oid repo ref)))
146
    (commit-lookup repo oid)))
147
148
(define* (get-file-content repo path #:key (ref #f))
149
  (let* ((oid (ref->oid repo ref))
150
         (commit (commit-lookup repo oid))
151
         (tree (commit-tree commit))
152
         (entry (false-if-exception (tree-entry-bypath tree path))))
153
    (if entry
154
        (let* ((entry-oid (tree-entry-id entry))
155
               (blob (blob-lookup repo entry-oid)))
156
          (blob-content blob))
157
        (throw 'not-found
158
               `(content ((p "path not found in repository for this commit: " ,path)))
159
               `(ref ,ref)))))
160
161
(define (last-commit-for-file path commit)
162
  (let* ((initial-tree (commit-tree commit))
163
         (initial-entry (false-if-exception (tree-entry-bypath initial-tree path))))
164
    (let loop ((commit commit))
165
      (let ((pcommit (false-if-exception (commit-parent commit))))
166
        (if pcommit
167
            (let* ((ptree (commit-tree pcommit))
168
                   (pentry (false-if-exception (tree-entry-bypath ptree path))))
169
              (if (or (and (not initial-entry) (not pentry))
170
                      (and initial-entry pentry
171
                           (equal? (tree-entry-id initial-entry)
172
                                   (tree-entry-id pentry))))
173
                  (loop pcommit)
174
                  commit))
175
            commit)))))
176
177
(define (get-commits repo ref)
178
  (let* ((oid (ref->oid repo ref))
179
         (commit (commit-lookup repo oid)))
180
    (let loop ((result (list commit)) (commit commit))
181
      (let ((pcommit (false-if-exception (commit-parent commit))))
182
        (if (and pcommit (< (length result) 20))
183
            (loop (cons pcommit result) pcommit)
184
            (cons (reverse result) pcommit))))))
185
186
(define (get-options config)
187
  (let ((out '()))
188
    (config-foreach
189
      config
190
      (lambda (entry)
191
        (set! out (cons (cons (config-entry-name entry)
192
                              (config-entry-value entry))
193
                        out))
194
        0))
195
    out))
196
197
(define (get-description repo)
198
  (let* ((config (repository-config repo))
199
         (options (get-options config)))
200
    (or (assoc-ref options "gitweb.description") "")))
201
202
(define (get-commit repo hash)
203
  (let* ((oid (ref->oid repo hash))
204
         (commit (false-if-exception (commit-lookup repo oid))))
205
    (or commit
206
        (throw 'not-found
207
               `(content ((p "Commit not found in this repository: " ,hash)))
208
               `(ref ,hash)))))
209