git.scm 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (guix tests git)
  20. #:use-module (git)
  21. #:use-module ((guix git) #:select (with-repository))
  22. #:use-module (guix utils)
  23. #:use-module (guix build utils)
  24. #:use-module (ice-9 match)
  25. #:use-module ((ice-9 control) #:select (let/ec))
  26. #:export (git-command
  27. with-temporary-git-repository
  28. with-git-repository
  29. find-commit))
  30. (define git-command
  31. (make-parameter "git"))
  32. (define (populate-git-repository directory directives)
  33. "Initialize a new Git checkout and repository in DIRECTORY and apply
  34. DIRECTIVES. Each element of DIRECTIVES is an sexp like:
  35. (add \"foo.txt\" \"hi!\")
  36. Return DIRECTORY on success."
  37. ;; Note: As of version 0.2.0, Guile-Git lacks the necessary bindings to do
  38. ;; all this, so resort to the "git" command.
  39. (define (git command . args)
  40. ;; Make sure Git doesn't rely on the user's config.
  41. (call-with-temporary-directory
  42. (lambda (home)
  43. (call-with-output-file (string-append home "/.gitconfig")
  44. (lambda (port)
  45. (display "[user]
  46. email = charlie@example.org\n name = Charlie Guix\n"
  47. port)))
  48. (with-environment-variables
  49. `(("GIT_CONFIG_NOSYSTEM" "1")
  50. ("GIT_ATTR_NOSYSTEM" "1")
  51. ("GIT_CONFIG_GLOBAL" ,(string-append home "/.gitconfig"))
  52. ("HOME" ,home))
  53. (apply invoke (git-command) "-C" directory
  54. command args)))))
  55. (unless (directory-exists? (string-append directory "/.git"))
  56. (mkdir-p directory)
  57. (git "init"))
  58. (let loop ((directives directives))
  59. (match directives
  60. (()
  61. directory)
  62. ((('add file contents) rest ...)
  63. (let ((file (string-append directory "/" file)))
  64. (mkdir-p (dirname file))
  65. (call-with-output-file file
  66. (lambda (port)
  67. (set-port-encoding! port "UTF-8")
  68. (display (if (string? contents)
  69. contents
  70. (with-repository directory repository
  71. (contents repository)))
  72. port)))
  73. (git "add" file)
  74. (loop rest)))
  75. ((('add file-name-and-content) rest ...)
  76. (loop (cons `(add ,file-name-and-content ,file-name-and-content)
  77. rest)))
  78. ((('remove file) rest ...)
  79. (git "rm" "-f" file)
  80. (loop rest))
  81. ((('commit text) rest ...)
  82. (git "commit" "-m" text)
  83. (loop rest))
  84. ((('commit text ('signer fingerprint)) rest ...)
  85. (git "commit" "-m" text (string-append "--gpg-sign=" fingerprint))
  86. (loop rest))
  87. ((('tag name) rest ...)
  88. (git "tag" name)
  89. (loop rest))
  90. ((('tag name text) rest ...)
  91. (git "tag" "-m" text name)
  92. (loop rest))
  93. ((('branch name) rest ...)
  94. (git "branch" name)
  95. (loop rest))
  96. ((('checkout branch) rest ...)
  97. (git "checkout" branch)
  98. (loop rest))
  99. ((('checkout branch 'orphan) rest ...)
  100. (git "checkout" "--orphan" branch)
  101. (loop rest))
  102. ((('merge branch message) rest ...)
  103. (git "merge" branch "-m" message)
  104. (loop rest))
  105. ((('merge branch message ('signer fingerprint)) rest ...)
  106. (git "merge" branch "-m" message
  107. (string-append "--gpg-sign=" fingerprint))
  108. (loop rest))
  109. ((('reset to) rest ...)
  110. (git "reset" "--hard" to)
  111. (loop rest)))))
  112. (define (call-with-temporary-git-repository directives proc)
  113. (call-with-temporary-directory
  114. (lambda (directory)
  115. (populate-git-repository directory directives)
  116. (proc directory))))
  117. (define-syntax-rule (with-temporary-git-repository directory
  118. directives exp ...)
  119. "Evaluate EXP in a context where DIRECTORY contains a checkout populated as
  120. per DIRECTIVES."
  121. (call-with-temporary-git-repository directives
  122. (lambda (directory)
  123. exp ...)))
  124. (define-syntax-rule (with-git-repository directory
  125. directives exp ...)
  126. "Evaluate EXP in a context where DIRECTORY is (further) populated as
  127. per DIRECTIVES."
  128. (begin
  129. (populate-git-repository directory directives)
  130. exp ...))
  131. (define (find-commit repository message)
  132. "Return the commit in REPOSITORY whose message includes MESSAGE, a string."
  133. (let/ec return
  134. (fold-commits (lambda (commit _)
  135. (and (string-contains (commit-message commit)
  136. message)
  137. (return commit)))
  138. #f
  139. repository)
  140. (error "commit not found" message)))