Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

239 lines
8.8 KiB

преди 4 години
  1. ;;; magit-bisect.el --- bisect support for Magit -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2011-2019 The Magit Project Contributors
  3. ;;
  4. ;; You should have received a copy of the AUTHORS.md file which
  5. ;; lists all contributors. If not, see http://magit.vc/authors.
  6. ;; Author: Jonas Bernoulli <jonas@bernoul.li>
  7. ;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
  8. ;; Magit is free software; you can redistribute it and/or modify it
  9. ;; under the terms of the GNU General Public License as published by
  10. ;; the Free Software Foundation; either version 3, or (at your option)
  11. ;; any later version.
  12. ;;
  13. ;; Magit is distributed in the hope that it will be useful, but WITHOUT
  14. ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  15. ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
  16. ;; License for more details.
  17. ;;
  18. ;; You should have received a copy of the GNU General Public License
  19. ;; along with Magit. If not, see http://www.gnu.org/licenses.
  20. ;;; Commentary:
  21. ;; Use a binary search to find the commit that introduced a bug.
  22. ;;; Code:
  23. (require 'magit)
  24. ;;; Options
  25. (defcustom magit-bisect-show-graph t
  26. "Whether to use `--graph' in the log showing commits yet to be bisected."
  27. :package-version '(magit . "2.8.0")
  28. :group 'magit-status
  29. :type 'boolean)
  30. (defface magit-bisect-good
  31. '((t :foreground "DarkOliveGreen"))
  32. "Face for good bisect revisions."
  33. :group 'magit-faces)
  34. (defface magit-bisect-skip
  35. '((t :foreground "DarkGoldenrod"))
  36. "Face for skipped bisect revisions."
  37. :group 'magit-faces)
  38. (defface magit-bisect-bad
  39. '((t :foreground "IndianRed4"))
  40. "Face for bad bisect revisions."
  41. :group 'magit-faces)
  42. ;;; Commands
  43. ;;;###autoload (autoload 'magit-bisect "magit-bisect" nil t)
  44. (define-transient-command magit-bisect ()
  45. "Narrow in on the commit that introduced a bug."
  46. :man-page "git-bisect"
  47. ["Actions"
  48. :if-not magit-bisect-in-progress-p
  49. ("B" "Start" magit-bisect-start)
  50. ("s" "Start script" magit-bisect-run)]
  51. ["Actions"
  52. :if magit-bisect-in-progress-p
  53. ("B" "Bad" magit-bisect-bad)
  54. ("g" "Good" magit-bisect-good)
  55. ("k" "Skip" magit-bisect-skip)
  56. ("r" "Reset" magit-bisect-reset)
  57. ("s" "Run script" magit-bisect-run)])
  58. ;;;###autoload
  59. (defun magit-bisect-start (bad good)
  60. "Start a bisect session.
  61. Bisecting a bug means to find the commit that introduced it.
  62. This command starts such a bisect session by asking for a know
  63. good and a bad commit. To move the session forward use the
  64. other actions from the bisect transient command (\
  65. \\<magit-status-mode-map>\\[magit-bisect])."
  66. (interactive (if (magit-bisect-in-progress-p)
  67. (user-error "Already bisecting")
  68. (magit-bisect-start-read-args)))
  69. (unless (magit-rev-ancestor-p good bad)
  70. (user-error
  71. "The good revision (%s) has to be an ancestor of the bad one (%s)"
  72. good bad))
  73. (when (magit-anything-modified-p)
  74. (user-error "Cannot bisect with uncommitted changes"))
  75. (magit-git-bisect "start" (list bad good) t))
  76. (defun magit-bisect-start-read-args ()
  77. (let ((b (magit-read-branch-or-commit "Start bisect with bad revision")))
  78. (list b (magit-read-other-branch-or-commit "Good revision" b))))
  79. ;;;###autoload
  80. (defun magit-bisect-reset ()
  81. "After bisecting, cleanup bisection state and return to original `HEAD'."
  82. (interactive)
  83. (magit-confirm 'reset-bisect)
  84. (magit-run-git "bisect" "reset")
  85. (ignore-errors (delete-file (magit-git-dir "BISECT_CMD_OUTPUT"))))
  86. ;;;###autoload
  87. (defun magit-bisect-good ()
  88. "While bisecting, mark the current commit as good.
  89. Use this after you have asserted that the commit does not contain
  90. the bug in question."
  91. (interactive)
  92. (magit-git-bisect "good"))
  93. ;;;###autoload
  94. (defun magit-bisect-bad ()
  95. "While bisecting, mark the current commit as bad.
  96. Use this after you have asserted that the commit does contain the
  97. bug in question."
  98. (interactive)
  99. (magit-git-bisect "bad"))
  100. ;;;###autoload
  101. (defun magit-bisect-skip ()
  102. "While bisecting, skip the current commit.
  103. Use this if for some reason the current commit is not a good one
  104. to test. This command lets Git choose a different one."
  105. (interactive)
  106. (magit-git-bisect "skip"))
  107. ;;;###autoload
  108. (defun magit-bisect-run (cmdline &optional bad good)
  109. "Bisect automatically by running commands after each step.
  110. Unlike `git bisect run' this can be used before bisecting has
  111. begun. In that case it behaves like `git bisect start; git
  112. bisect run'."
  113. (interactive (let ((args (and (not (magit-bisect-in-progress-p))
  114. (magit-bisect-start-read-args))))
  115. (cons (read-shell-command "Bisect shell command: ") args)))
  116. (when (and bad good)
  117. (magit-bisect-start bad good))
  118. (magit-git-bisect "run" (list shell-file-name shell-command-switch cmdline)))
  119. (defun magit-git-bisect (subcommand &optional args no-assert)
  120. (unless (or no-assert (magit-bisect-in-progress-p))
  121. (user-error "Not bisecting"))
  122. (message "Bisecting...")
  123. (magit-with-toplevel
  124. (magit-run-git-async "bisect" subcommand args))
  125. (set-process-sentinel
  126. magit-this-process
  127. (lambda (process event)
  128. (when (memq (process-status process) '(exit signal))
  129. (if (> (process-exit-status process) 0)
  130. (magit-process-sentinel process event)
  131. (process-put process 'inhibit-refresh t)
  132. (magit-process-sentinel process event)
  133. (when (buffer-live-p (process-buffer process))
  134. (with-current-buffer (process-buffer process)
  135. (when-let ((section (get-text-property (point) 'magit-section))
  136. (output (buffer-substring-no-properties
  137. (oref section content)
  138. (oref section end))))
  139. (with-temp-file (magit-git-dir "BISECT_CMD_OUTPUT")
  140. (insert output)))))
  141. (magit-refresh))
  142. (message "Bisecting...done")))))
  143. ;;; Sections
  144. (defun magit-bisect-in-progress-p ()
  145. (file-exists-p (magit-git-dir "BISECT_LOG")))
  146. (defun magit-insert-bisect-output ()
  147. "While bisecting, insert section with output from `git bisect'."
  148. (when (magit-bisect-in-progress-p)
  149. (let* ((lines
  150. (or (magit-file-lines (magit-git-dir "BISECT_CMD_OUTPUT"))
  151. (list "Bisecting: (no saved bisect output)"
  152. "It appears you have invoked `git bisect' from a shell."
  153. "There is nothing wrong with that, we just cannot display"
  154. "anything useful here. Consult the shell output instead.")))
  155. (done-re "^\\([a-z0-9]\\{40\\}\\) is the first bad commit$")
  156. (bad-line (or (and (string-match done-re (car lines))
  157. (pop lines))
  158. (--first (string-match done-re it) lines))))
  159. (magit-insert-section ((eval (if bad-line 'commit 'bisect-output))
  160. (and bad-line (match-string 1 bad-line)))
  161. (magit-insert-heading
  162. (propertize (or bad-line (pop lines))
  163. 'font-lock-face 'magit-section-heading))
  164. (dolist (line lines)
  165. (insert line "\n"))))
  166. (insert "\n")))
  167. (defun magit-insert-bisect-rest ()
  168. "While bisecting, insert section visualizing the bisect state."
  169. (when (magit-bisect-in-progress-p)
  170. (magit-insert-section (bisect-view)
  171. (magit-insert-heading "Bisect Rest:")
  172. (magit-git-wash (apply-partially 'magit-log-wash-log 'bisect-vis)
  173. "bisect" "visualize" "git" "log"
  174. "--format=%h%x00%D%x00%s" "--decorate=full"
  175. (and magit-bisect-show-graph "--graph")))))
  176. (defun magit-insert-bisect-log ()
  177. "While bisecting, insert section logging bisect progress."
  178. (when (magit-bisect-in-progress-p)
  179. (magit-insert-section (bisect-log)
  180. (magit-insert-heading "Bisect Log:")
  181. (magit-git-wash #'magit-wash-bisect-log "bisect" "log")
  182. (insert ?\n))))
  183. (defun magit-wash-bisect-log (_args)
  184. (let (beg)
  185. (while (progn (setq beg (point-marker))
  186. (re-search-forward "^\\(git bisect [^\n]+\n\\)" nil t))
  187. (magit-bind-match-strings (heading) nil
  188. (magit-delete-match)
  189. (save-restriction
  190. (narrow-to-region beg (point))
  191. (goto-char (point-min))
  192. (magit-insert-section (bisect-item heading t)
  193. (insert (propertize heading 'font-lock-face
  194. 'magit-section-secondary-heading))
  195. (magit-insert-heading)
  196. (magit-wash-sequence
  197. (apply-partially 'magit-log-wash-rev 'bisect-log
  198. (magit-abbrev-length)))
  199. (insert ?\n)))))
  200. (when (re-search-forward
  201. "# first bad commit: \\[\\([a-z0-9]\\{40\\}\\)\\] [^\n]+\n" nil t)
  202. (magit-bind-match-strings (hash) nil
  203. (magit-delete-match)
  204. (magit-insert-section (bisect-item)
  205. (insert hash " is the first bad commit\n"))))))
  206. ;;; _
  207. (provide 'magit-bisect)
  208. ;;; magit-bisect.el ends here