| 1 | ;;; magit.el --- A Git porcelain inside Emacs -*- lexical-binding:t; coding:utf-8 -*- |
| 2 | |
| 3 | ;; Copyright (C) 2008-2026 The Magit Project Contributors |
| 4 | |
| 5 | ;; Author: Marius Vollmer <marius.vollmer@gmail.com> |
| 6 | ;; Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev> |
| 7 | ;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev> |
| 8 | ;; Kyle Meyer <kyle@kyleam.com> |
| 9 | ;; Former-Maintainers: |
| 10 | ;; Nicolas Dudebout <nicolas.dudebout@gatech.edu> |
| 11 | ;; Noam Postavsky <npostavs@users.sourceforge.net> |
| 12 | ;; Peter J. Weisberg <pj@irregularexpressions.net> |
| 13 | ;; Phil Jackson <phil@shellarchive.co.uk> |
| 14 | ;; Rémi Vanicat <vanicat@debian.org> |
| 15 | ;; Yann Hodique <yann.hodique@gmail.com> |
| 16 | |
| 17 | ;; Homepage: https://github.com/magit/magit |
| 18 | ;; Keywords: git tools vc |
| 19 | |
| 20 | ;; Package-Version: 20261101.2154 |
| 21 | ;; Package-Revision: 942d61d5de37 |
| 22 | ;; Package-Requires: ( |
| 23 | ;; (emacs "28.1") |
| 24 | ;; (compat "31.0") |
| 25 | ;; (cond-let "1.1") |
| 26 | ;; (llama "1.0") |
| 27 | ;; (magit-section "4.7") |
| 28 | ;; (seq "2.24") |
| 29 | ;; (transient "0.13") |
| 30 | ;; (with-editor "3.5")) |
| 31 | |
| 32 | ;; SPDX-License-Identifier: GPL-3.0-or-later |
| 33 | |
| 34 | ;; Magit is free software: you can redistribute it and/or modify |
| 35 | ;; it under the terms of the GNU General Public License as published |
| 36 | ;; by the Free Software Foundation, either version 3 of the License, |
| 37 | ;; or (at your option) any later version. |
| 38 | ;; |
| 39 | ;; Magit is distributed in the hope that it will be useful, |
| 40 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 41 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 42 | ;; GNU General Public License for more details. |
| 43 | ;; |
| 44 | ;; You should have received a copy of the GNU General Public License |
| 45 | ;; along with Magit. If not, see <https://www.gnu.org/licenses/>. |
| 46 | |
| 47 | ;; You should have received a copy of the AUTHORS.md file, which |
| 48 | ;; lists all contributors. If not, see https://magit.vc/authors. |
| 49 | |
| 50 | ;;; Commentary: |
| 51 | |
| 52 | ;; Magit is a text-based Git user interface that puts an unmatched focus |
| 53 | ;; on streamlining workflows. Commands are invoked using short mnemonic |
| 54 | ;; key sequences that take the cursor’s position in the highly actionable |
| 55 | ;; interface into account to provide context-sensitive behavior. |
| 56 | |
| 57 | ;; With Magit you can do nearly everything that you can do when using Git |
| 58 | ;; on the command-line, but at greater speed and while taking advantage |
| 59 | ;; of advanced features that previously seemed too daunting to use on a |
| 60 | ;; daily basis. Many users will find that by using Magit they can become |
| 61 | ;; more effective Git user. |
| 62 | |
| 63 | ;;; Code: |
| 64 | |
| 65 | (require 'magit-core) |
| 66 | (require 'magit-diff) |
| 67 | (require 'magit-log) |
| 68 | (require 'magit-wip) |
| 69 | (require 'magit-apply) |
| 70 | (require 'magit-repos) |
| 71 | (require 'git-commit) |
| 72 | |
| 73 | (require 'epa) ;used in magit-read-gpg-{secret,signing}-key |
| 74 | (require 'with-editor) |
| 75 | |
| 76 | ;;; Options |
| 77 | |
| 78 | (defcustom magit-openpgp-default-signing-key nil |
| 79 | "Fingerprint of your default Openpgp key used for signing. |
| 80 | If the specified primary key has signing capacity then it is used |
| 81 | as the value of the \"--gpg-sign\" argument without prompting, even |
| 82 | when other such keys exist. To be able to select another key you |
| 83 | must then use a prefix argument." |
| 84 | :package-version '(magit . "4.0.0") |
| 85 | :group 'magit-commands |
| 86 | :type 'string) |
| 87 | |
| 88 | ;;; Faces |
| 89 | |
| 90 | (defface magit-header-line |
| 91 | '((t :inherit magit-section-heading)) |
| 92 | "Face for the `header-line' in some Magit modes. |
| 93 | Note that some modes, such as `magit-log-select-mode', have their |
| 94 | own faces for the `header-line', or for parts of the |
| 95 | `header-line'." |
| 96 | :group 'magit-faces) |
| 97 | |
| 98 | (defface magit-header-line-key |
| 99 | '((t :inherit font-lock-builtin-face)) |
| 100 | "Face for keys in the `header-line'." |
| 101 | :group 'magit-faces) |
| 102 | |
| 103 | (defface magit-dimmed |
| 104 | '((((class color) (background light)) :foreground "grey50") |
| 105 | (((class color) (background dark)) :foreground "grey50")) |
| 106 | "Face for text that shouldn't stand out." |
| 107 | :group 'magit-faces) |
| 108 | |
| 109 | (defface magit-hash |
| 110 | '((((class color) (background light)) :foreground "grey60") |
| 111 | (((class color) (background dark)) :foreground "grey40")) |
| 112 | "Face for the commit object name in the log output." |
| 113 | :group 'magit-faces) |
| 114 | |
| 115 | (defface magit-tag |
| 116 | '((((class color) (background light)) :foreground "Goldenrod4") |
| 117 | (((class color) (background dark)) :foreground "LightGoldenrod2")) |
| 118 | "Face for tag labels shown in log buffer." |
| 119 | :group 'magit-faces) |
| 120 | |
| 121 | (defface magit-branch-remote |
| 122 | '((((class color) (background light)) :foreground "DarkOliveGreen4") |
| 123 | (((class color) (background dark)) :foreground "DarkSeaGreen2")) |
| 124 | "Face for remote branch head labels shown in log buffer." |
| 125 | :group 'magit-faces) |
| 126 | |
| 127 | (defface magit-branch-remote-head |
| 128 | '((((supports (:box t))) :inherit magit-branch-remote :box t) |
| 129 | (t :inherit magit-branch-remote :inverse-video t)) |
| 130 | "Face for current branch." |
| 131 | :group 'magit-faces) |
| 132 | |
| 133 | (defface magit-branch-local |
| 134 | '((((class color) (background light)) :foreground "SkyBlue4") |
| 135 | (((class color) (background dark)) :foreground "LightSkyBlue1")) |
| 136 | "Face for local branches." |
| 137 | :group 'magit-faces) |
| 138 | |
| 139 | (defface magit-branch-current |
| 140 | '((((supports (:box t))) :inherit magit-branch-local :box t) |
| 141 | (t :inherit magit-branch-local :inverse-video t)) |
| 142 | "Face for current branch." |
| 143 | :group 'magit-faces) |
| 144 | |
| 145 | (defface magit-branch-upstream |
| 146 | '((t :slant italic)) |
| 147 | "Face for upstream branch. |
| 148 | This face is only used in logs and it gets combined |
| 149 | with `magit-branch-local', `magit-branch-remote' |
| 150 | and/or `magit-branch-remote-head'." |
| 151 | :group 'magit-faces) |
| 152 | |
| 153 | (defface magit-branch-warning |
| 154 | '((t :inherit warning)) |
| 155 | "Face for warning about (missing) branch." |
| 156 | :group 'magit-faces) |
| 157 | |
| 158 | (defface magit-head |
| 159 | '((((class color) (background light)) :inherit magit-branch-local) |
| 160 | (((class color) (background dark)) :inherit magit-branch-local)) |
| 161 | "Face for the symbolic ref `HEAD'." |
| 162 | :group 'magit-faces) |
| 163 | |
| 164 | (defface magit-refname |
| 165 | '((((class color) (background light)) :foreground "grey30") |
| 166 | (((class color) (background dark)) :foreground "grey80")) |
| 167 | "Face for refnames without a dedicated face." |
| 168 | :group 'magit-faces) |
| 169 | |
| 170 | (defface magit-refname-stash |
| 171 | '((t :inherit magit-refname)) |
| 172 | "Face for stash refnames." |
| 173 | :group 'magit-faces) |
| 174 | |
| 175 | (defface magit-refname-wip |
| 176 | '((t :inherit magit-refname)) |
| 177 | "Face for wip refnames." |
| 178 | :group 'magit-faces) |
| 179 | |
| 180 | (defface magit-refname-pullreq |
| 181 | '((t :inherit magit-refname)) |
| 182 | "Face for pullreq refnames." |
| 183 | :group 'magit-faces) |
| 184 | |
| 185 | (defface magit-keyword |
| 186 | '((t :inherit font-lock-string-face)) |
| 187 | "Face for parts of commit messages inside brackets." |
| 188 | :group 'magit-faces) |
| 189 | |
| 190 | (defface magit-keyword-squash |
| 191 | '((t :inherit font-lock-warning-face)) |
| 192 | "Face for squash! and similar keywords in commit messages." |
| 193 | :group 'magit-faces) |
| 194 | |
| 195 | (defface magit-signature-good |
| 196 | '((t :foreground "green")) |
| 197 | "Face for good signatures." |
| 198 | :group 'magit-faces) |
| 199 | |
| 200 | (defface magit-signature-bad |
| 201 | '((t :foreground "red" :weight bold)) |
| 202 | "Face for bad signatures." |
| 203 | :group 'magit-faces) |
| 204 | |
| 205 | (defface magit-signature-untrusted |
| 206 | '((t :foreground "medium aquamarine")) |
| 207 | "Face for good untrusted signatures." |
| 208 | :group 'magit-faces) |
| 209 | |
| 210 | (defface magit-signature-expired |
| 211 | '((t :foreground "orange")) |
| 212 | "Face for signatures that have expired." |
| 213 | :group 'magit-faces) |
| 214 | |
| 215 | (defface magit-signature-expired-key |
| 216 | '((t :inherit magit-signature-expired)) |
| 217 | "Face for signatures made by an expired key." |
| 218 | :group 'magit-faces) |
| 219 | |
| 220 | (defface magit-signature-revoked |
| 221 | '((t :foreground "violet red")) |
| 222 | "Face for signatures made by a revoked key." |
| 223 | :group 'magit-faces) |
| 224 | |
| 225 | (defface magit-signature-error |
| 226 | '((t :foreground "light blue")) |
| 227 | "Face for signatures that cannot be checked (e.g., missing key)." |
| 228 | :group 'magit-faces) |
| 229 | |
| 230 | (defface magit-cherry-unmatched |
| 231 | '((t :foreground "cyan")) |
| 232 | "Face for unmatched cherry commits." |
| 233 | :group 'magit-faces) |
| 234 | |
| 235 | (defface magit-cherry-equivalent |
| 236 | '((t :foreground "magenta")) |
| 237 | "Face for equivalent cherry commits." |
| 238 | :group 'magit-faces) |
| 239 | |
| 240 | (defface magit-filename |
| 241 | '((t :weight normal)) |
| 242 | "Face for filenames." |
| 243 | :group 'magit-faces) |
| 244 | |
| 245 | ;;; Global Bindings |
| 246 | |
| 247 | ;;;###autoload |
| 248 | (defcustom magit-define-global-key-bindings 'default |
| 249 | "Which set of key bindings to add to the global keymap, if any. |
| 250 | |
| 251 | This option controls which set of Magit key bindings, if any, may |
| 252 | be added to the global keymap, even before Magit is first used in |
| 253 | the current Emacs session. |
| 254 | |
| 255 | If the value is nil, no bindings are added. |
| 256 | |
| 257 | If \\+`default', maybe add: |
| 258 | |
| 259 | \\`C-x' \\`g' `magit-status' |
| 260 | \\`C-x' \\`M-g' `magit-dispatch' |
| 261 | \\`C-c' \\`M-g' `magit-file-dispatch' |
| 262 | |
| 263 | If `recommended', maybe add: |
| 264 | |
| 265 | \\`C-x' \\`g' `magit-status' |
| 266 | \\`C-c' \\`g' `magit-dispatch' |
| 267 | \\`C-c' \\`f' `magit-file-dispatch' |
| 268 | |
| 269 | These bindings are strongly recommended, but we cannot use |
| 270 | them by default, because the \\`C-c <LETTER>' namespace is |
| 271 | strictly reserved for bindings added by the user. |
| 272 | |
| 273 | The bindings in the chosen set may be added when |
| 274 | `after-init-hook' is run. Each binding is added if, and only |
| 275 | if, at that time no other key is bound to the same command, |
| 276 | and no other command is bound to the same key. In other words |
| 277 | we try to avoid adding bindings that are unnecessary, as well |
| 278 | as bindings that conflict with other bindings. |
| 279 | |
| 280 | Adding these bindings is delayed until `after-init-hook' is |
| 281 | run to allow users to set the variable anywhere in their init |
| 282 | file (without having to make sure to do so before `magit' is |
| 283 | loaded or autoloaded) and to increase the likelihood that all |
| 284 | the potentially conflicting user bindings have already been |
| 285 | added. |
| 286 | |
| 287 | To set this variable use either `setq' or the Custom interface. |
| 288 | Do not use the function `customize-set-variable' because doing |
| 289 | that would cause Magit to be loaded immediately, when that form |
| 290 | is evaluated (this differs from `custom-set-variables', which |
| 291 | doesn't load the libraries that define the customized variables). |
| 292 | |
| 293 | Setting this variable has no effect if `after-init-hook' has |
| 294 | already been run." |
| 295 | :package-version '(magit . "4.0.0") |
| 296 | :group 'magit-essentials |
| 297 | :type '(choice (const :tag "Add no binding" nil) |
| 298 | (const :tag "Use default bindings" default) |
| 299 | (const :tag "Use recommended bindings" recommended))) |
| 300 | |
| 301 | ;;;###autoload |
| 302 | (progn |
| 303 | (defun magit-maybe-define-global-key-bindings (&optional force) |
| 304 | "See variable `magit-define-global-key-bindings'." |
| 305 | (when magit-define-global-key-bindings |
| 306 | (let ((map (current-global-map))) |
| 307 | (pcase-dolist (`(,key . ,def) |
| 308 | (cond ((eq magit-define-global-key-bindings 'recommended) |
| 309 | '(("C-x g" . magit-status) |
| 310 | ("C-c g" . magit-dispatch) |
| 311 | ("C-c f" . magit-file-dispatch))) |
| 312 | ('(("C-x g" . magit-status) |
| 313 | ("C-x M-g" . magit-dispatch) |
| 314 | ("C-c M-g" . magit-file-dispatch))))) |
| 315 | ;; This is autoloaded and thus is used before `compat' is |
| 316 | ;; loaded, so we cannot use `keymap-lookup' and `keymap-set'. |
| 317 | (when (or force |
| 318 | (not (or (lookup-key map (kbd key)) |
| 319 | (where-is-internal def (make-sparse-keymap) t)))) |
| 320 | (define-key map (kbd key) def)))))) |
| 321 | (if after-init-time |
| 322 | (magit-maybe-define-global-key-bindings) |
| 323 | (add-hook 'after-init-hook #'magit-maybe-define-global-key-bindings t))) |
| 324 | |
| 325 | ;;; Dispatch Menu |
| 326 | |
| 327 | ;;;###autoload(autoload 'magit-dispatch "magit" nil t) |
| 328 | (transient-define-prefix magit-dispatch () |
| 329 | "Invoke a Magit command from a list of available commands." |
| 330 | :info-manual "(magit)Top" |
| 331 | ["Transient and dwim commands" |
| 332 | ;; → bound in magit-mode-map or magit-section-mode-map |
| 333 | ;; ↓ bound below |
| 334 | [("A" "Apply" magit-cherry-pick) |
| 335 | ;; a ↓ |
| 336 | ("b" "Branch" magit-branch) |
| 337 | ("B" "Bisect" magit-bisect) |
| 338 | ("c" "Commit" magit-commit) |
| 339 | ("C" "Clone" magit-clone) |
| 340 | ("d" "Diff" magit-diff) |
| 341 | ("D" "Diff (change)" magit-diff-refresh) |
| 342 | ("e" "Ediff (dwim)" magit-ediff-dwim) |
| 343 | ("E" "Ediff" magit-ediff) |
| 344 | ("f" "Fetch" magit-fetch) |
| 345 | ("F" "Pull" magit-pull) |
| 346 | ;; g ↓ |
| 347 | ;; G → magit-refresh-all |
| 348 | ("h" "Help" magit-info) |
| 349 | ("H" "Section info" magit-describe-section :if-derived magit-mode)] |
| 350 | [("i" "Ignore" magit-gitignore) |
| 351 | ("I" "Init" magit-init) |
| 352 | ("j" "Jump to section"magit-status-jump :if-mode magit-status-mode) |
| 353 | ("j" "Display status" magit-status-quick :if-not-mode magit-status-mode) |
| 354 | ("J" "Display buffer" magit-display-repository-buffer) |
| 355 | ;; k ↓ |
| 356 | ;; K → magit-file-untrack |
| 357 | ("l" "Log" magit-log) |
| 358 | ("L" "Log (change)" magit-log-refresh) |
| 359 | ("m" "Merge" magit-merge) |
| 360 | ("M" "Remote" magit-remote) |
| 361 | ;; n → magit-section-forward |
| 362 | ;; N reserved → forge-dispatch |
| 363 | ("o" "Submodule" magit-submodule) |
| 364 | ("O" "Subtree" magit-subtree) |
| 365 | ;; p → magit-section-backward |
| 366 | ("P" "Push" magit-push) |
| 367 | ;; q → magit-mode-bury-buffer |
| 368 | ("Q" "Command" magit-git-command)] |
| 369 | [("r" "Rebase" magit-rebase) |
| 370 | ;; R → magit-file-rename |
| 371 | ;; s ↓ |
| 372 | ;; S ↓ |
| 373 | ("t" "Tag" magit-tag) |
| 374 | ("T" "Note" magit-notes) |
| 375 | ;; u ↓ |
| 376 | ;; U ↓ |
| 377 | ;; v ↓ |
| 378 | ("V" "Revert" magit-revert) |
| 379 | ("w" "Apply patches" magit-am) |
| 380 | ("W" "Format patches" magit-patch) |
| 381 | ;; x → magit-reset-quickly |
| 382 | ("X" "Reset" magit-reset) |
| 383 | ("y" "Show Refs" magit-show-refs) |
| 384 | ("Y" "Cherries" magit-cherry) |
| 385 | ("z" "Stash" magit-stash) |
| 386 | ("Z" "Worktree" magit-worktree) |
| 387 | ("!" "Run" magit-run)]] |
| 388 | ["Applying changes" |
| 389 | :if-derived magit-mode |
| 390 | [("a" "Apply" magit-apply) |
| 391 | ("v" "Reverse" magit-reverse) |
| 392 | ("k" "Discard" magit-discard)] |
| 393 | [("s" "Stage" magit-stage) |
| 394 | ("u" "Unstage" magit-unstage)] |
| 395 | [("S" "Stage all" magit-stage-modified) |
| 396 | ("U" "Unstage all" magit-unstage-all)]] |
| 397 | ["Essential commands" |
| 398 | :if-derived magit-mode |
| 399 | [("g" " Refresh current buffer" magit-refresh) |
| 400 | ("q" " Bury current buffer" magit-mode-bury-buffer) |
| 401 | ("<tab>" " Toggle section at point" magit-section-toggle) |
| 402 | ("<return>" "Visit thing at point" magit-visit-thing)] |
| 403 | [("C-x m" "Show all key bindings" describe-mode) |
| 404 | ("C-x i" "Show Info manual" magit-info)]]) |
| 405 | |
| 406 | ;;; Git Menu |
| 407 | |
| 408 | (defcustom magit-shell-command-verbose-prompt t |
| 409 | "Whether to show the working directory when reading a command. |
| 410 | This affects `magit-git-command', `magit-git-command-topdir', |
| 411 | `magit-shell-command', and `magit-shell-command-topdir'." |
| 412 | :package-version '(magit . "2.11.0") |
| 413 | :group 'magit-commands |
| 414 | :type 'boolean) |
| 415 | |
| 416 | (defvar magit-git-command-history nil) |
| 417 | |
| 418 | ;;;###autoload(autoload 'magit-run "magit" nil t) |
| 419 | (transient-define-prefix magit-run () |
| 420 | "Run git or another command, or launch a graphical utility." |
| 421 | [["Run git subcommand" |
| 422 | ("!" "in repository root" magit-git-command-topdir) |
| 423 | ("p" "in working directory" magit-git-command)] |
| 424 | ["Run shell command" |
| 425 | ("s" "in repository root" magit-shell-command-topdir) |
| 426 | ("S" "in working directory" magit-shell-command)] |
| 427 | ["Launch" |
| 428 | ("k" "gitk" magit-run-gitk) |
| 429 | ("a" "gitk --all" magit-run-gitk-all) |
| 430 | ("b" "gitk --branches" magit-run-gitk-branches) |
| 431 | ("g" "git gui" magit-run-git-gui) |
| 432 | ("m" "git mergetool --gui" magit-git-mergetool)]]) |
| 433 | |
| 434 | ;;;###autoload |
| 435 | (defun magit-git-command (command) |
| 436 | "Execute COMMAND asynchronously; display output. |
| 437 | |
| 438 | Interactively, prompt for COMMAND in the minibuffer. \"git \" is |
| 439 | used as initial input, but can be deleted to run another command. |
| 440 | |
| 441 | With a prefix argument COMMAND is run in the top-level directory |
| 442 | of the current working tree, otherwise in `default-directory'." |
| 443 | (interactive (list (magit-read-shell-command nil "git "))) |
| 444 | (magit--shell-command command)) |
| 445 | |
| 446 | ;;;###autoload |
| 447 | (defun magit-git-command-topdir (command) |
| 448 | "Execute COMMAND asynchronously; display output. |
| 449 | |
| 450 | Interactively, prompt for COMMAND in the minibuffer. \"git \" is |
| 451 | used as initial input, but can be deleted to run another command. |
| 452 | |
| 453 | COMMAND is run in the top-level directory of the current |
| 454 | working tree." |
| 455 | (interactive (list (magit-read-shell-command t "git "))) |
| 456 | (magit--shell-command command (magit-toplevel))) |
| 457 | |
| 458 | ;;;###autoload |
| 459 | (defun magit-shell-command (command) |
| 460 | "Execute COMMAND asynchronously; display output. |
| 461 | |
| 462 | Interactively, prompt for COMMAND in the minibuffer. With a |
| 463 | prefix argument COMMAND is run in the top-level directory of |
| 464 | the current working tree, otherwise in `default-directory'." |
| 465 | (interactive (list (magit-read-shell-command))) |
| 466 | (magit--shell-command command)) |
| 467 | |
| 468 | ;;;###autoload |
| 469 | (defun magit-shell-command-topdir (command) |
| 470 | "Execute COMMAND asynchronously; display output. |
| 471 | |
| 472 | Interactively, prompt for COMMAND in the minibuffer. COMMAND |
| 473 | is run in the top-level directory of the current working tree." |
| 474 | (interactive (list (magit-read-shell-command t))) |
| 475 | (magit--shell-command command (magit-toplevel))) |
| 476 | |
| 477 | (defun magit--shell-command (command &optional directory) |
| 478 | (let ((default-directory (or directory default-directory))) |
| 479 | (with-environment-variables (("GIT_PAGER" "cat")) |
| 480 | (with-connection-local-variables |
| 481 | (magit-with-editor |
| 482 | (magit-start-process shell-file-name nil |
| 483 | shell-command-switch command))))) |
| 484 | (magit-process-buffer)) |
| 485 | |
| 486 | (defun magit-read-shell-command (&optional toplevel initial-input) |
| 487 | (let ((default-directory |
| 488 | (if (or toplevel current-prefix-arg) |
| 489 | (or (magit-toplevel) |
| 490 | (magit--not-inside-repository-error)) |
| 491 | default-directory))) |
| 492 | (read-shell-command (if magit-shell-command-verbose-prompt |
| 493 | (format "Async shell command in %s: " |
| 494 | (abbreviate-file-name default-directory)) |
| 495 | "Async shell command: ") |
| 496 | initial-input 'magit-git-command-history))) |
| 497 | |
| 498 | ;;; Shared Infix Arguments |
| 499 | |
| 500 | (transient-define-argument magit:--signoff () |
| 501 | :description "Add Signed-off-by trailer" |
| 502 | :class 'transient-switch |
| 503 | :key "+s" |
| 504 | :shortarg "-s" |
| 505 | :argument "--signoff" |
| 506 | :level 6) |
| 507 | |
| 508 | (transient-define-argument magit:--gpg-sign () |
| 509 | :description (##concat "Sign using " (or (magit-get "gpg.format") "openpgp")) |
| 510 | :class 'transient-option |
| 511 | :shortarg "-S" |
| 512 | :argument "--gpg-sign=" |
| 513 | :allow-empty t |
| 514 | :reader #'magit-read-gpg-signing-key |
| 515 | :level 5) |
| 516 | |
| 517 | (defvar magit-gpg-secret-key-hist nil) |
| 518 | |
| 519 | (defun magit-read-gpg-secret-key |
| 520 | (prompt &optional initial-input history predicate default) |
| 521 | (require 'epa) |
| 522 | (let* ((keys (seq-keep |
| 523 | (lambda (cert) |
| 524 | (and (or (not predicate) |
| 525 | (funcall predicate cert)) |
| 526 | (let* ((key (car (epg-key-sub-key-list cert))) |
| 527 | (fpr (epg-sub-key-fingerprint key)) |
| 528 | (id (epg-sub-key-id key)) |
| 529 | (author |
| 530 | (and-let ((id-obj |
| 531 | (car (epg-key-user-id-list cert)))) |
| 532 | (let ((id-str (epg-user-id-string id-obj))) |
| 533 | (if (stringp id-str) |
| 534 | id-str |
| 535 | (epg-decode-dn id-obj)))))) |
| 536 | (propertize fpr 'display |
| 537 | (concat (substring fpr 0 (- (length id))) |
| 538 | (propertize id 'face 'highlight) |
| 539 | " " author))))) |
| 540 | (epg-list-keys (epg-make-context epa-protocol) nil t))) |
| 541 | (choice (or (and (not current-prefix-arg) |
| 542 | (or (and (length= keys 1) (car keys)) |
| 543 | (and default (car (member default keys))))) |
| 544 | (completing-read prompt keys nil nil nil |
| 545 | history nil initial-input)))) |
| 546 | (set-text-properties 0 (length choice) nil choice) |
| 547 | choice)) |
| 548 | |
| 549 | (defun magit-read-gpg-signing-key (prompt &optional initial-input history) |
| 550 | (if (member (magit-get "gpg.format") '(nil "openpgp")) |
| 551 | (magit-read-gpg-secret-key |
| 552 | prompt initial-input history |
| 553 | (lambda (cert) |
| 554 | (seq-some (##memq 'sign (epg-sub-key-capability %)) |
| 555 | (epg-key-sub-key-list cert))) |
| 556 | magit-openpgp-default-signing-key) |
| 557 | "")) |
| 558 | |
| 559 | ;;; Font-Lock Keywords |
| 560 | |
| 561 | (defconst magit-font-lock-keywords |
| 562 | (eval-when-compile |
| 563 | `((,(concat "(\\(magit-define-section-jumper\\)\\_>" |
| 564 | "[ \t'(]*" |
| 565 | "\\(\\(?:\\sw\\|\\s_\\)+\\)?") |
| 566 | (1 'font-lock-keyword-face) |
| 567 | (2 'font-lock-function-name-face nil t)) |
| 568 | (,(concat "(" (regexp-opt '("magit-insert-section" |
| 569 | "magit-insert-heading" |
| 570 | "magit-section-case" |
| 571 | "magit-bind-match-strings" |
| 572 | "magit-with-temp-index" |
| 573 | "magit-with-blob" |
| 574 | "magit-with-toplevel") |
| 575 | t) |
| 576 | "\\_>") |
| 577 | . 1)))) |
| 578 | |
| 579 | (font-lock-add-keywords 'emacs-lisp-mode magit-font-lock-keywords) |
| 580 | |
| 581 | ;;; Version |
| 582 | |
| 583 | (defvar magit-version #'undefined |
| 584 | "The version of Magit that you're using. |
| 585 | Use the function by the same name instead of this variable.") |
| 586 | |
| 587 | ;;;###autoload |
| 588 | (defun magit-version (&optional print-dest interactive nowarn) |
| 589 | "Return the version of Magit currently in use. |
| 590 | |
| 591 | If optional argument PRINT-DEST is non-nil, also print the used |
| 592 | versions of Magit, Transient, Git and Emacs to the output stream |
| 593 | selected by that argument. Interactively use the echo area, or |
| 594 | with a prefix argument use the current buffer. Additionally put |
| 595 | the output in the kill ring. |
| 596 | \n(fn &optional PRINT-DEST)" |
| 597 | (interactive (list (if current-prefix-arg (current-buffer) t) t)) |
| 598 | (let ((magit-git-global-arguments nil) |
| 599 | (toplib (or load-file-name buffer-file-name)) |
| 600 | debug) |
| 601 | (unless (and toplib |
| 602 | (member (file-name-nondirectory toplib) |
| 603 | '("magit.el" "magit.el.gz"))) |
| 604 | (let ((load-suffixes (reverse load-suffixes))) ; prefer .el than .elc |
| 605 | (setq toplib (locate-library "magit")))) |
| 606 | (setq toplib (and toplib (magit--chase-links toplib))) |
| 607 | (push toplib debug) |
| 608 | (when toplib |
| 609 | (let* ((topdir (file-name-directory toplib)) |
| 610 | (gitdir (expand-file-name |
| 611 | ".git" (file-name-parent-directory topdir))) |
| 612 | (static (locate-library "magit-version.el" nil (list topdir))) |
| 613 | (static (and static (magit--chase-links static)))) |
| 614 | (or (progn |
| 615 | (push 'repo debug) |
| 616 | (when (and (file-exists-p gitdir) |
| 617 | ;; It is a repo, but is it the Magit repo? |
| 618 | (file-exists-p |
| 619 | (expand-file-name "../lisp/magit.el" gitdir))) |
| 620 | (push t debug) |
| 621 | ;; Inside the repo the version file should only exist |
| 622 | ;; while running make. |
| 623 | (when (and static (not noninteractive)) |
| 624 | (ignore-errors (delete-file static))) |
| 625 | (setq magit-version |
| 626 | (let ((default-directory topdir)) |
| 627 | (magit-git-string "describe" |
| 628 | "--tags" "--dirty" "--always"))))) |
| 629 | (progn |
| 630 | (push 'static debug) |
| 631 | (when (and static (file-exists-p static)) |
| 632 | (push t debug) |
| 633 | (load-file static) |
| 634 | magit-version)) |
| 635 | (when (and (featurep 'package) |
| 636 | (boundp 'package-alist) |
| 637 | (fboundp 'package-version-join)) |
| 638 | (push 'elpa debug) |
| 639 | (ignore-errors |
| 640 | (when-let ((version (cadr (assq 'magit package-alist)))) |
| 641 | (push t debug) |
| 642 | (setq magit-version |
| 643 | (and (fboundp 'package-desc-version) |
| 644 | (package-version-join |
| 645 | (package-desc-version version))))))) |
| 646 | (progn |
| 647 | (push 'dirname debug) |
| 648 | (let ((dirname (file-name-nondirectory |
| 649 | (directory-file-name topdir)))) |
| 650 | (when (string-match "\\`magit-\\([0-9].*\\)" dirname) |
| 651 | (setq magit-version (match-str 1 dirname))))) |
| 652 | ;; If all else fails, just report the commit hash. It's |
| 653 | ;; better than nothing and we cannot do better in the case |
| 654 | ;; of e.g., a shallow clone. |
| 655 | (progn |
| 656 | (push 'hash debug) |
| 657 | ;; Same check as above to see if it's really the Magit repo. |
| 658 | (when (and (file-exists-p gitdir) |
| 659 | (file-exists-p |
| 660 | (expand-file-name "../lisp/magit.el" gitdir))) |
| 661 | (setq magit-version |
| 662 | (let ((default-directory topdir)) |
| 663 | (magit-git-string "rev-parse" "HEAD")))))))) |
| 664 | (if (stringp magit-version) |
| 665 | (when print-dest |
| 666 | (let* ((alt (or (and (ignore-errors |
| 667 | (magit--version>= magit-version "2008")) |
| 668 | (ignore-errors |
| 669 | (require 'lisp-mnt) |
| 670 | (and (fboundp 'lm-header) |
| 671 | (with-temp-buffer |
| 672 | (insert-file-contents |
| 673 | (locate-library "magit.el" t)) |
| 674 | (lm-header "Package-Version"))))))) |
| 675 | (str (format |
| 676 | "Magit %s%s, Transient %s,%s Git %s, Emacs %s, %s" |
| 677 | (or magit-version "(unknown)") |
| 678 | (if (and alt (not (equal alt magit-version))) |
| 679 | (format " [>= %s]" alt) |
| 680 | "") |
| 681 | (or (ignore-errors |
| 682 | (require 'lisp-mnt) |
| 683 | (and (fboundp 'lm-header) |
| 684 | (with-temp-buffer |
| 685 | (insert-file-contents |
| 686 | (locate-library "transient.el" t)) |
| 687 | (lm-header "Package-Version")))) |
| 688 | "(unknown)") |
| 689 | (let ((lib (locate-library "forge.el" t))) |
| 690 | (or (and lib |
| 691 | (format |
| 692 | " Forge %s," |
| 693 | (or (ignore-errors |
| 694 | (require 'lisp-mnt) |
| 695 | (with-temp-buffer |
| 696 | (insert-file-contents lib) |
| 697 | (and (fboundp 'lm-header) |
| 698 | (lm-header "Package-Version")))) |
| 699 | "(unknown)"))) |
| 700 | "")) |
| 701 | (magit--safe-git-version) |
| 702 | emacs-version |
| 703 | system-type))) |
| 704 | (when interactive |
| 705 | (kill-new str)) |
| 706 | (princ str print-dest))) |
| 707 | (setq debug (reverse debug)) |
| 708 | (setq magit-version 'error) |
| 709 | (when magit-version |
| 710 | (push magit-version debug)) |
| 711 | (unless (or nowarn (equal (getenv "CI") "true")) |
| 712 | (message "Cannot determine Magit's version %S" debug))) |
| 713 | magit-version)) |
| 714 | |
| 715 | ;;; Startup Asserts |
| 716 | |
| 717 | (defun magit-startup-asserts () |
| 718 | (when-let ((val (getenv "GIT_DIR"))) |
| 719 | (setenv "GIT_DIR") |
| 720 | (message |
| 721 | "Magit unset $GIT_DIR (was %S). See %s" val |
| 722 | ;; Note: Pass URL as argument rather than embedding in the format |
| 723 | ;; string to prevent the single quote from being rendered |
| 724 | ;; according to `text-quoting-style'. |
| 725 | "https://github.com/magit/magit/wiki/Don't-set-$GIT_DIR-and-alike")) |
| 726 | (when-let ((val (getenv "GIT_WORK_TREE"))) |
| 727 | (setenv "GIT_WORK_TREE") |
| 728 | (message |
| 729 | "Magit unset $GIT_WORK_TREE (was %S). See %s" val |
| 730 | ;; See comment above. |
| 731 | "https://github.com/magit/magit/wiki/Don't-set-$GIT_DIR-and-alike")) |
| 732 | ;; Git isn't required while building Magit. |
| 733 | (unless (bound-and-true-p byte-compile-current-file) |
| 734 | (magit-git-version-assert)) |
| 735 | (when (version< emacs-version magit--minimal-emacs) |
| 736 | (display-warning 'magit (format "\ |
| 737 | Magit requires Emacs >= %s, you are using %s. |
| 738 | |
| 739 | If this comes as a surprise to you, because you do actually have |
| 740 | a newer version installed, then that probably means that the |
| 741 | older version happens to appear earlier on the `$PATH'. If you |
| 742 | always start Emacs from a shell, then that can be fixed in the |
| 743 | shell's init file. If you start Emacs by clicking on an icon, |
| 744 | or using some sort of application launcher, then you probably |
| 745 | have to adjust the environment as seen by graphical interface. |
| 746 | For X11 something like ~/.xinitrc should work.\n" |
| 747 | magit--minimal-emacs emacs-version) |
| 748 | :error))) |
| 749 | |
| 750 | ;;; Loading Libraries |
| 751 | |
| 752 | (provide 'magit) |
| 753 | |
| 754 | (cl-eval-when (load eval) |
| 755 | (require 'magit-status) |
| 756 | (require 'magit-refs) |
| 757 | (require 'magit-files) |
| 758 | (require 'magit-reset) |
| 759 | (require 'magit-branch) |
| 760 | (require 'magit-merge) |
| 761 | (require 'magit-tag) |
| 762 | (require 'magit-worktree) |
| 763 | (require 'magit-notes) |
| 764 | (require 'magit-sequence) |
| 765 | (require 'magit-commit) |
| 766 | (require 'magit-remote) |
| 767 | (require 'magit-clone) |
| 768 | (require 'magit-fetch) |
| 769 | (require 'magit-pull) |
| 770 | (require 'magit-push) |
| 771 | (require 'magit-bisect) |
| 772 | (require 'magit-stash) |
| 773 | (require 'magit-blame) |
| 774 | (require 'magit-submodule) |
| 775 | (unless (or noninteractive |
| 776 | ;; The `provide' form may be missing, so we have to |
| 777 | ;; try harder to ensure this is loaded exactly once. |
| 778 | (featurep 'magit-autoloads) |
| 779 | (autoloadp (symbol-function 'magit-patch)) |
| 780 | (load "magit-autoloads" t t)) |
| 781 | (require 'magit-patch) |
| 782 | (require 'magit-subtree) |
| 783 | (require 'magit-ediff) |
| 784 | (require 'magit-gitignore) |
| 785 | (require 'magit-sparse-checkout) |
| 786 | (require 'magit-extras) |
| 787 | (require 'magit-dired) |
| 788 | (require 'git-rebase) |
| 789 | (require 'magit-bookmark))) |
| 790 | |
| 791 | (with-eval-after-load 'bookmark |
| 792 | (require 'magit-bookmark)) |
| 793 | |
| 794 | (unless (bound-and-true-p byte-compile-current-file) |
| 795 | (if after-init-time |
| 796 | (progn (magit-startup-asserts) |
| 797 | (magit-version nil nil t)) |
| 798 | (add-hook 'after-init-hook #'magit-startup-asserts t) |
| 799 | (add-hook 'after-init-hook #'magit-version t))) |
| 800 | |
| 801 | ;; Local Variables: |
| 802 | ;; read-symbol-shorthands: ( |
| 803 | ;; ("and$" . "cond-let--and$") |
| 804 | ;; ("thread$" . "cond-let--thread$") |
| 805 | ;; ("when$" . "cond-let--when$") |
| 806 | ;; ("and-let*" . "cond-let--and-let*") |
| 807 | ;; ("and-let" . "cond-let--and-let") |
| 808 | ;; ("if-let*" . "cond-let--if-let*") |
| 809 | ;; ("if-let" . "cond-let--if-let") |
| 810 | ;; ("when-let*" . "cond-let--when-let*") |
| 811 | ;; ("when-let" . "cond-let--when-let") |
| 812 | ;; ("while-let*" . "cond-let--while-let*") |
| 813 | ;; ("while-let" . "cond-let--while-let") |
| 814 | ;; ("match-string" . "match-string") |
| 815 | ;; ("match-str" . "match-string-no-properties")) |
| 816 | ;; End: |
| 817 | ;;; magit.el ends here |