aboutsummaryrefslogtreecommitdiff
path: root/etc/committer.scm.in
blob: c49935da6007ab98ea98a7df3207c290a904297e (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
#!@GUILE@ \
--no-auto-compile -s
!#

;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2020, 2021, 2022, 2023 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2021 Sarah Morgensen <iskarian@mgsn.dev>
;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
;;; Copyright © 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; This script stages and commits changes to package definitions.

;;; Code:

(use-modules ((sxml xpath) #:prefix xpath:)
             (srfi srfi-1)
             (srfi srfi-2)
             (srfi srfi-9)
             (srfi srfi-11)
             (srfi srfi-26)
             (ice-9 format)
             (ice-9 popen)
             (ice-9 match)
             (ice-9 rdelim)
             (ice-9 regex)
             (ice-9 textual-ports)
             (guix gexp))

(define* (break-string str #:optional (max-line-length 70))
  "Break the string STR into lines that are no longer than MAX-LINE-LENGTH.
Return a single string."
  (define (restore-line words)
    (string-join (reverse words) " "))
  (if (<= (string-length str) max-line-length)
      str
      (let ((words+lengths (map (lambda (word)
                                  (cons word (string-length word)))
                                (string-tokenize str))))
        (match (fold (match-lambda*
                       (((word . length)
                         (count current lines))
                        (let ((new-count (+ count length 1)))
                          (if (< new-count max-line-length)
                              (list new-count
                                    (cons word current)
                                    lines)
                              (list length
                                    (list word)
                                    (cons (restore-line current) lines))))))
                     '(0 () ())
                     words+lengths)
          ((_ last-words lines)
           (string-join (reverse (cons (restore-line last-words) lines))
                        "\n"))))))

(define* (break-string-with-newlines str #:optional (max-line-length 70))
  "Break the lines of string STR into lines that are no longer than
MAX-LINE-LENGTH. Return a single string."
  (string-join (map (cut break-string <> max-line-length)
                    (string-split str #\newline))
               "\n"))

(define (read-excursion port)
  "Read an expression from PORT and reset the port position before returning
the expression."
  (let ((start (ftell port))
        (result (read port)))
    (seek port start SEEK_SET)
    result))

(define (lines+offsets-with-opening-parens port)
  "Record all line numbers (and their offsets) where an opening parenthesis is
found in column 0.  The resulting list is in reverse order."
  (let loop ((acc '())
             (number 0))
    (let ((line (read-line port)))
      (cond
       ((eof-object? line) acc)
       ((string-prefix? "(" line)
        (loop (cons (cons number                      ;line number
                          (- (ftell port)
                             (string-length line) 1)) ;offset
                    acc)
              (1+ number)))
       (else (loop acc (1+ number)))))))

(define (surrounding-sexp port target-line-no)
  "Return the top-level S-expression surrounding the change at line number
TARGET-LINE-NO in PORT."
  (let* ((line-numbers+offsets
          (lines+offsets-with-opening-parens port))
         (closest-offset
          (or (and=> (list-index (match-lambda
                                   ((line-number . offset)
                                    (< line-number target-line-no)))
                                 line-numbers+offsets)
                     (lambda (index)
                       (match (list-ref line-numbers+offsets index)
                         ((line-number . offset) offset))))
              (error "Could not find surrounding S-expression for line"
                     target-line-no))))
    (seek port closest-offset SEEK_SET)
    (read port)))

;;; Whether the hunk contains a newly added package (definition), a removed
;;; package (removal) or something else (#false).
(define hunk-types '(addition removal #false))

(define-record-type <hunk>
  (make-hunk file-name
             old-line-number
             new-line-number
             diff-lines
             type)
  hunk?
  (file-name       hunk-file-name)
  ;; Line number before the change
  (old-line-number hunk-old-line-number)
  ;; Line number after the change
  (new-line-number hunk-new-line-number)
  ;; The full diff to be used with "git apply --cached"
  (diff-lines hunk-diff-lines)
  ;; Does this hunk add or remove a package?
  (type hunk-type))                     ;one of 'hunk-types'

(define* (hunk->patch hunk #:optional (port (current-output-port)))
  (let ((file-name (hunk-file-name hunk)))
    (format port
            "diff --git a/~a b/~a~%--- a/~a~%+++ b/~a~%~a"
            file-name file-name file-name file-name
            (string-join (hunk-diff-lines hunk) ""))))

(define (diff-info)
  "Read the diff and return a list of <hunk> values."
  (let ((port (open-pipe* OPEN_READ
                          "git" "diff-files"
                          "--no-prefix"
                          ;; Only include one context line to avoid lumping in
                          ;; new definitions with changes to existing
                          ;; definitions.
                          "--unified=1"
                          "--" "gnu")))
    (define (extract-line-number line-tag)
      (abs (string->number
            (car (string-split line-tag #\,)))))
    (define (read-hunk)
      (let loop ((lines '())
                 (type #false))
        (let ((line (read-line port 'concat)))
          (cond
           ((eof-object? line)
            (values (reverse lines) type))
           ((or (string-prefix? "@@ " line)
                (string-prefix? "diff --git" line))
            (unget-string port line)
            (values (reverse lines) type))
           (else
            (loop (cons line lines)
                  (or type
                      (cond
                       ((string-prefix? "+(define" line)
                        'addition)
                       ((string-prefix? "-(define" line)
                        'removal)
                       (else #false)))))))))
    (define info
      (let loop ((acc '())
                 (file-name #f))
        (let ((line (read-line port)))
          (cond
           ((eof-object? line) acc)
           ((string-prefix? "--- " line)
            (match (string-split line #\space)
              ((_ file-name)
               (loop acc file-name))))
           ((string-prefix? "@@ " line)
            (match (string-split line #\space)
              ((_ old-start new-start . _)
               (let-values
                   (((diff-lines type) (read-hunk)))
                 (loop (cons (make-hunk file-name
                                        (extract-line-number old-start)
                                        (extract-line-number new-start)
                                        (cons (string-append line "\n")
                                              diff-lines)
                                        type) acc)
                       file-name)))))
           (else (loop acc file-name))))))
    (close-pipe port)
    info))

(define (lines-to-first-change hunk)
  "Return the number of diff lines until the first change."
  (1- (count (lambda (line)
               ((negate char-set-contains?)
                (char-set #\+ #\-)
                (string-ref line 0)))
             (hunk-diff-lines hunk))))

(define %original-file-cache
  (make-hash-table))

(define (read-original-file file-name)
  "Return the contents of FILE-NAME prior to any changes."
  (let* ((port (open-pipe* OPEN_READ
                           "git" "cat-file" "-p" (string-append
                                                  "HEAD:" file-name)))
         (contents (get-string-all port)))
    (close-pipe port)
    contents))

(define (read-original-file* file-name)
  "Caching variant of READ-ORIGINAL-FILE."
  (or (hashv-ref %original-file-cache file-name)
      (let ((value (read-original-file file-name)))
        (hashv-set! %original-file-cache file-name value)
        value)))

(define (old-sexp hunk)
  "Using the diff information in HUNK return the unmodified S-expression
corresponding to the top-level definition containing the staged changes."
  ;; TODO: We can't seek with a pipe port...
  (call-with-input-string (read-original-file* (hunk-file-name hunk))
    (lambda (port)
      (surrounding-sexp port
                        (+ (lines-to-first-change hunk)
                           (hunk-old-line-number hunk))))))

(define (new-sexp hunk)
  "Using the diff information in HUNK return the modified S-expression
corresponding to the top-level definition containing the staged changes."
  (call-with-input-file (hunk-file-name hunk)
    (lambda (port)
      (surrounding-sexp port
                        (+ (lines-to-first-change hunk)
                           (hunk-new-line-number hunk))))))

(define* (change-commit-message file-name old new #:optional (port (current-output-port)))
  "Print ChangeLog commit message for changes between OLD and NEW."
  (define (get-values expr field)
    (match ((xpath:node-or
             (xpath:sxpath `(*any* *any* package ,field quasiquote *))
             ;; For let binding
             (xpath:sxpath `(*any* *any* (*any*) package ,field quasiquote *)))
            (cons '*TOP* expr))
      (()
       ;; New-style plain lists
       (match ((xpath:node-or
                (xpath:sxpath `(*any* *any* package ,field list *))
                ;; For let binding
                (xpath:sxpath `(*any* *any* (*any*) package ,field list *)))
               (cons '*TOP* expr))
         ((inner) inner)
         (_ '())))
      ;; Old-style labelled inputs
      ((first . rest)
       (map cadadr first))))
  (define (listify items)
    (match items
      ((one) one)
      ((one two)
       (string-append one " and " two))
      ((one two . more)
       (string-append (string-join (drop-right items 1) ", ")
                      ", and " (first (take-right items 1))))))
  (define variable-name
    (second old))
  (define version
    (and=> ((xpath:node-or
             (xpath:sxpath '(*any* *any* package version *any*))
             ;; For let binding
             (xpath:sxpath '(*any* *any* (*any*) package version *any*)))
            (cons '*TOP* new))
           first))
  (format port
          "gnu: ~a: Update to ~a.~%~%* ~a (~a): Update to ~a.~%"
          variable-name version file-name variable-name version)
  (for-each (lambda (field)
              (let ((old-values (get-values old field))
                    (new-values (get-values new field)))
                (or (equal? old-values new-values)
                    (let ((removed (lset-difference equal? old-values new-values))
                          (added (lset-difference equal? new-values old-values)))
                      (format port
                              "[~a]: ~a~%" field
                              (break-string
                               (match (list (map symbol->string removed)
                                            (map symbol->string added))
                                 ((() added)
                                  (format #f "Add ~a."
                                          (listify added)))
                                 ((removed ())
                                  (format #f "Remove ~a."
                                          (listify removed)))
                                 ((removed added)
                                  (format #f "Remove ~a; add ~a."
                                          (listify removed)
                                          (listify added))))))))))
            '(inputs propagated-inputs native-inputs)))

(define* (add-commit-message file-name variable-name
                             #:optional (port (current-output-port)))
  "Print ChangeLog commit message for a change to FILE-NAME adding a
definition."
  (format port "gnu: Add ~a.~%~%* ~a (~a): New variable.~%"
          variable-name file-name variable-name))

(define* (remove-commit-message file-name variable-name
                                #:optional (port (current-output-port)))
  "Print ChangeLog commit message for a change to FILE-NAME removing a
definition."
  (format port "gnu: Remove ~a.~%~%* ~a (~a): Delete variable.~%"
          variable-name file-name variable-name))

(define* (custom-commit-message file-name variable-name message changelog
                                #:optional (port (current-output-port)))
  "Print custom commit message for a change to VARIABLE-NAME in FILE-NAME, using
MESSAGE as the commit message and CHANGELOG as the body of the ChangeLog
entry. If CHANGELOG is #f, the commit message is reused. If CHANGELOG already
contains ': ', no colon is inserted between the location and body of the
ChangeLog entry."
  (define (trim msg)
    (string-trim-right (string-trim-both msg) (char-set #\.)))

  (define (changelog-has-location? changelog)
    (->bool (string-match "^[[:graph:]]+:[[:blank:]]" changelog)))

  (let* ((message (trim message))
         (changelog (if changelog (trim changelog) message))
         (message/f (format #f "gnu: ~a: ~a." variable-name message))
         (changelog/f (if (changelog-has-location? changelog)
                          (format #f "* ~a (~a)~a."
                                  file-name variable-name changelog)
                          (format #f "* ~a (~a): ~a."
                                  file-name variable-name changelog))))
    (format port
            "~a~%~%~a~%"
            (break-string-with-newlines message/f 72)
            (break-string-with-newlines changelog/f 72))))

(define (add-copyright-line line)
  "Add the copyright line on LINE to the previous commit."
  (let ((author (match:substring
                 (string-match "^\\+;;; Copyright ©[^[:alpha:]]+(.*)$" line)
                 1)))
    (format
     (current-output-port) "Amend and add copyright line for ~a~%" author)
    (system* "git" "commit" "--amend" "--no-edit")))

(define (group-hunks-by-sexp hunks)
  "Return a list of pairs associating all hunks with the S-expression they are
modifying."
  (fold (lambda (sexp hunk acc)
          (match acc
            (((previous-sexp . hunks) . rest)
             (if (equal? sexp previous-sexp)
                 (cons (cons previous-sexp
                             (cons hunk hunks))
                       rest)
                 (cons (cons sexp (list hunk))
                       acc)))
            (_
             (cons (cons sexp (list hunk))
                   acc))))
        '()
        (map new-sexp hunks)
        hunks))

(define (new+old+hunks hunks)
  (map (match-lambda
         ((new . hunks)
          (cons* new (old-sexp (first hunks)) hunks)))
       (group-hunks-by-sexp hunks)))

(define %delay 1000)

(define (main . args)
  (define* (change-commit-message* file-name old new #:rest rest)
    (let ((changelog #f))
      (match args
        ((or (message changelog) (message))
         (apply custom-commit-message
                file-name (second old) message changelog rest))
        (_
         (apply change-commit-message file-name old new rest)))))

  (read-disable 'positions)
  (match (diff-info)
    (()
     (display "Nothing to be done.\n" (current-error-port)))
    (hunks
     (let-values (((definitions changes) (partition hunk-type hunks)))
       ;; Additions/removals.
       (for-each
        (lambda (hunk)
          (and-let* ((define-line (find (cut string-match "(\\+|-)\\(define" <>)
                                        (hunk-diff-lines hunk)))
                     (variable-name (and=> (string-tokenize define-line)
                                           second))
                     (commit-message-proc (match (hunk-type hunk)
                                            ('addition add-commit-message)
                                            ('removal remove-commit-message))))
            (commit-message-proc (hunk-file-name hunk) variable-name)
            (let ((port (open-pipe* OPEN_WRITE
                                    "git" "apply"
                                    "--cached"
                                    "--unidiff-zero")))
              (hunk->patch hunk port)
              (unless (eqv? 0 (status:exit-val (close-pipe port)))
                (error "Cannot apply")))

            (let ((port (open-pipe* OPEN_WRITE "git" "commit" "-F" "-")))
              (commit-message-proc (hunk-file-name hunk) variable-name port)
              (usleep %delay)
              (unless (eqv? 0 (status:exit-val (close-pipe port)))
                (error "Cannot commit"))))
          (usleep %delay))
        definitions)

       ;; Changes.
       (for-each
        (match-lambda
          ((new old . hunks)
           (for-each (lambda (hunk)
                       (let ((port (open-pipe* OPEN_WRITE
                                               "git" "apply"
                                               "--cached"
                                               "--unidiff-zero")))
                         (hunk->patch hunk port)
                         (unless (eqv? 0 (status:exit-val (close-pipe port)))
                           (error "Cannot apply")))
                       (usleep %delay))
                     hunks)
           (define copyright-line
             (any (lambda (line) (and=> (string-prefix? "+;;; Copyright ©" line)
                                   (const line)))
                  (hunk-diff-lines (first hunks))))
           (cond
            (copyright-line
             (add-copyright-line copyright-line))
            (else
             (let ((port (open-pipe* OPEN_WRITE "git" "commit" "-F" "-")))
               (change-commit-message* (hunk-file-name (first hunks))
                                       old new)
               (change-commit-message* (hunk-file-name (first hunks))
                                       old new
                                       port)
               (usleep %delay)
               (unless (eqv? 0 (status:exit-val (close-pipe port)))
                 (error "Cannot commit")))))))
        (new+old+hunks (match definitions
                         ('() changes) ;reuse
                         (_
                          ;; XXX: we recompute the hunks here because previous
                          ;; insertions lead to offsets.
                          (let-values (((definitions changes)
                                        (partition hunk-type (diff-info))))
                            changes)))))))))

(apply main (cdr (command-line)))
o do so.
+
+10. Automatic Licensing of Downstream Recipients.
+
+Each time you convey a covered work, the recipient automatically receives a
+license from the original licensors, to run, modify and propagate that
+work, subject to this License. You are not responsible for enforcing
+compliance by third parties with this License.
+
+An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered work
+results from an entity transaction, each party to that transaction who
+receives a copy of the work also receives whatever licenses to the work the
+party's predecessor in interest had or could give under the previous
+paragraph, plus a right to possession of the Corresponding Source of the
+work from the predecessor in interest, if the predecessor has it or can get
+it with reasonable efforts.
+
+You may not impose any further restrictions on the exercise of the rights
+granted or affirmed under this License. For example, you may not impose a
+license fee, royalty, or other charge for exercise of rights granted under
+this License, and you may not initiate litigation (including a cross-claim
+or counterclaim in a lawsuit) alleging that any patent claim is infringed
+by making, using, selling, offering for sale, or importing the Program or
+any portion of it.
+
+11. Patents.
+
+A "contributor" is a copyright holder who authorizes use under this License
+of the Program or a work on which the Program is based. The work thus
+licensed is called the contributor's "contributor version".
+
+A contributor's "essential patent claims" are all patent claims owned or
+controlled by the contributor, whether already acquired or hereafter
+acquired, that would be infringed by some manner, permitted by this
+License, of making, using, or selling its contributor version, but do not
+include claims that would be infringed only as a consequence of further
+modification of the contributor version. For purposes of this definition,
+"control" includes the right to grant patent sublicenses in a manner
+consistent with the requirements of this License.
+
+Each contributor grants you a non-exclusive, worldwide, royalty-free patent
+license under the contributor's essential patent claims, to make, use,
+sell, offer for sale, import and otherwise run, modify and propagate the
+contents of its contributor version.
+
+In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent (such
+as an express permission to practice a patent or covenant not to sue for
+patent infringement). To "grant" such a patent license to a party means to
+make such an agreement or commitment not to enforce a patent against the
+party.
+
+If you convey a covered work, knowingly relying on a patent license, and
+the Corresponding Source of the work is not available for anyone to copy,
+free of charge and under the terms of this License, through a publicly
+available network server or other readily accessible means, then you must
+either (1) cause the Corresponding Source to be so available, or (2)
+arrange to deprive yourself of the benefit of the patent license for this
+particular work, or (3) arrange, in a manner consistent with the
+requirements of this License, to extend the patent license to downstream
+recipients. "Knowingly relying" means you have actual knowledge that, but
+for the patent license, your conveying the covered work in a country, or
+your recipient's use of the covered work in a country, would infringe one
+or more identifiable patents in that country that you have reason to
+believe are valid.
+
+If, pursuant to or in connection with a single transaction or arrangement,
+you convey, or propagate by procuring conveyance of, a covered work, and
+grant a patent license to some of the parties receiving the covered work
+authorizing them to use, propagate, modify or convey a specific copy of the
+covered work, then the patent license you grant is automatically extended
+to all recipients of the covered work and works based on it.
+
+A patent license is "discriminatory" if it does not include within the
+scope of its coverage, prohibits the exercise of, or is conditioned on the
+non-exercise of one or more of the rights that are specifically granted
+under this License. You may not convey a covered work if you are a party to
+an arrangement with a third party that is in the business of distributing
+software, under which you make payment to the third party based on the
+extent of your activity of conveying the work, and under which the third
+party grants, to any of the parties who would receive the covered work from
+you, a discriminatory patent license (a) in connection with copies of the
+covered work conveyed by you (or copies made from those copies), or (b)
+primarily for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement, or that
+patent license was granted, prior to 28 March 2007.
+
+Nothing in this License shall be construed as excluding or limiting any
+implied license or other defenses to infringement that may otherwise be
+available to you under applicable patent law.
+
+12. No Surrender of Others' Freedom.
+
+If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+13. Remote Network Interaction; Use with the GNU General Public License.
+
+Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users interacting
+with it remotely through a computer network (if your version supports such
+interaction) an opportunity to receive the Corresponding Source of your
+version by providing access to the Corresponding Source from a network
+server at no charge, through some standard or customary means of
+facilitating copying of software. This Corresponding Source shall include
+the Corresponding Source for any work covered by version 3 of the GNU
+General Public License that is incorporated pursuant to the following
+paragraph.
+
+Notwithstanding any other provision of this License, you have permission to
+link or combine any covered work with a work licensed under version 3 of
+the GNU General Public License into a single combined work, and to convey
+the resulting work. The terms of this License will continue to apply to the
+part which is the covered work, but the work with which it is combined will
+remain governed by version 3 of the GNU General Public License.
+
+14. Revised Versions of this License.
+
+The Free Software Foundation may publish revised and/or new versions of the
+GNU Affero General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies that a certain numbered version of the GNU Affero General Public
+License "or any later version" applies to it, you have the option of
+following the terms and conditions either of that numbered version or of
+any later version published by the Free Software Foundation. If the Program
+does not specify a version number of the GNU Affero General Public License,
+you may choose any version ever published by the Free Software Foundation.
+
+If the Program specifies that a proxy can decide which future versions of
+the GNU Affero General Public License can be used, that proxy's public
+statement of acceptance of a version permanently authorizes you to choose
+that version for the Program.
+
+Later license versions may give you additional or different permissions.
+However, no additional obligations are imposed on any author or copyright
+holder as a result of your choosing to follow a later version.
+
+15. Disclaimer of Warranty.
+
+THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE
+LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND,
+EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.
+SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY
+SERVICING, REPAIR OR CORRECTION.
+
+16. Limitation of Liability.
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
+ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE
+PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+17. Interpretation of Sections 15 and 16.
+
+If the disclaimer of warranty and limitation of liability provided above
+cannot be given local legal effect according to their terms, reviewing
+courts shall apply local law that most closely approximates an absolute
+waiver of all civil liability in connection with the Program, unless a
+warranty or assumption of liability accompanies a copy of the Program in
+return for a fee.
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it free
+software which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest to
+attach them to the start of each source file to most effectively state the
+exclusion of warranty; and each file should have at least the "copyright"
+line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to get
+its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive of
+the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary. For
+more information on this, and how to apply and follow the GNU AGPL, see <
+http://www.gnu.org/licenses/>.
+